commit f465c9072c7eeefb5cc7239940ab10e8a3f88963 Author: eroncero Date: Wed Feb 26 13:42:34 2025 +0100 First commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..358c822 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +SabrehavenServer.tar.gz +ZnoteAAC.tar.gz diff --git a/SabrehavenServer-arm64.tar.gz b/SabrehavenServer-arm64.tar.gz new file mode 100644 index 0000000..82022b5 Binary files /dev/null and b/SabrehavenServer-arm64.tar.gz differ diff --git a/app/SabrehavenServer.tar.gz b/app/SabrehavenServer.tar.gz new file mode 100644 index 0000000..7a42e2e Binary files /dev/null and b/app/SabrehavenServer.tar.gz differ diff --git a/app/SabrehavenServer/.gitignore b/app/SabrehavenServer/.gitignore new file mode 100644 index 0000000..dd86769 --- /dev/null +++ b/app/SabrehavenServer/.gitignore @@ -0,0 +1,25 @@ +vc14/.vs/theforgottenserver/v15/Browse.VC.db +vc14/.vs/theforgottenserver/v15/ipch/ba8106b03bee8153.ipch +<<<<<<< HEAD +*.lastbuildstate +*.tlog +*.ipch +======= +vc14/.vs/theforgottenserver/v15/Browse.VC.opendb +*.ipch +vc14/x64/ +>>>>>>> stored_players +*.exe +vc14/theforgottenserver.vcxproj.user +vc14/.vs/ +*.pdb +*.dll +/.vs +/vc14/Debug +/vc14/UpgradeLog.htm +/TibianusOTClientEncrypted +/vc14/UpgradeLog2.htm +/Sabrehaven.zip +/theforgottenserver.7z +/theforgottenserver.zip +/SabrehavenOTClient/tibianus.log diff --git a/app/SabrehavenServer/CMakeLists.txt b/app/SabrehavenServer/CMakeLists.txt new file mode 100644 index 0000000..0262f47 --- /dev/null +++ b/app/SabrehavenServer/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 2.8) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(tfs) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(cotire) + +add_compile_options(-Wall -Werror -pipe -fvisibility=hidden) +set(CMAKE_CXX_FLAGS_PERFORMANCE "${CMAKE_CXX_FLAGS_RELEASE} -march=native") + +if (CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-fno-strict-aliasing) +endif() + +include(FindCXX11) +include(FindLTO) + +# Find packages. +find_package(Crypto++ REQUIRED) +find_package(GMP REQUIRED) +find_package(PugiXML REQUIRED) +find_package(MySQL) +find_package(Threads) + +# Selects LuaJIT if user defines or auto-detected +if(DEFINED USE_LUAJIT AND NOT USE_LUAJIT) + set(FORCE_LUAJIT ${USE_LUAJIT}) +else() + find_package(LuaJIT) + set(FORCE_LUAJIT ${LuaJIT_FOUND}) +endif() +option(USE_LUAJIT "Use LuaJIT" ${FORCE_LUAJIT}) + +if(FORCE_LUAJIT) + if(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-pagezero_size 10000 -image_base 100000000") + endif() +else() + find_package(Lua REQUIRED) +endif() + +find_package(Boost 1.53.0 COMPONENTS system filesystem iostreams REQUIRED) + +add_subdirectory(src) +add_executable(tfs ${tfs_SRC}) + +include_directories(${MYSQL_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${Crypto++_INCLUDE_DIR}) +target_link_libraries(tfs ${MYSQL_CLIENT_LIBS} ${LUA_LIBRARIES} ${Boost_LIBRARIES} ${Boost_FILESYSTEM_LIBRARY} ${PUGIXML_LIBRARIES} ${GMP_LIBRARIES} ${Crypto++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + +set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h") +set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) +cotire(tfs) diff --git a/app/SabrehavenServer/New Text Document.txt b/app/SabrehavenServer/New Text Document.txt new file mode 100644 index 0000000..668e9a5 --- /dev/null +++ b/app/SabrehavenServer/New Text Document.txt @@ -0,0 +1 @@ +next: 17743 \ No newline at end of file diff --git a/app/SabrehavenServer/README.md b/app/SabrehavenServer/README.md new file mode 100644 index 0000000..f4d696e --- /dev/null +++ b/app/SabrehavenServer/README.md @@ -0,0 +1,13321 @@ +# What is Sabrehaven +I wish you the best use of the Sabrehaven server and its features. Server is based on the Nostalrius fork which uses original cipsoft resources which were leaked- however I would say this is super expansion features wise than Nostalrius fork I have started with and there we have lots of old crash bugs fixed like aleta sio regex crash bug, creatures talk for no spectators crash bug fix and etc. All in all, I will try to make a little documentation for you. To start with, take a look at the Good reads for you about my and Sabrehaven life: +- https://otland.net/threads/sabrehaven-7-8-project-discussion.267527/ [My journey started from this discussion thread] +- https://otland.net/threads/sabrehaven-7-92-project-discussion.268469/ [After I hosted 7.8 server without great success I though why not to make it compatible with 7.92 and so I did] +- https://otland.net/threads/netherlands-7-92-sabrehaven-reverse-engineering-real-tibia-ots.268035/ [This is my very first advertisement thread] +- https://otland.net/threads/france-7-4-tibianus-the-enchanted-real-ots-starting-08-05-saturday-20-00-cet.276212/ [This is the 7.4 attempt which wasn't very great success as wel] +- Lastly in private with CustomTibia OTLand user I started to do 8.0 branch which never saw the players from the open tibia world. + +# Sabrehaven features or whats new not in the Nostalrius fork + - 7.8-8.0 cities and quests including POI, INQ and other. + - Outfits, Outfit Addons, Mounts, Shaders, Auras, Wings... + - Market system. + - Built in client shop system. + - Offline training implementation. + - Stamina implementation. + - Guild wars with bounties implementation. + - Quest log support. + - And quite many more for you to discover including many bugfixes. + +# How to find things in this mess +The very first and the very imporant thing is to take a look at the commit history! You can find the whole progress of the development which may be very useful for you. +Now lets speak about branches: + - https://github.com/ErikasKontenis/SabrehavenServer/tree/master - The master branch is the stable 7.92 implementation of sabrehaven server before the idea of lets make it 8.0 + - https://github.com/ErikasKontenis/SabrehavenServer/tree/36-introduce-7-40-tibia-support - this branch contains the 7.92 downgraded sabrehaven to support 7.4 protocol version + - https://github.com/ErikasKontenis/SabrehavenServer/tree/32-introduce-7-92-client-support - this branch contains the whole progress of 7.8 upgrade to 7.92 + - https://github.com/ErikasKontenis/SabrehavenServer/tree/38-introduce-8-0-support - finally this branch contains the most features and is an upgrade from 7.92 to 8.0 protocol support + +Compile the sources just as you would compile any other TFS server. Here is also sabrehaven.sql which you must use to create clean schema and little schema changes for znoteAAC support sabrehaven_znote.sql. + +# Info regarding Sabrehaven 8.0 + - You should use 800OTClient folder to login with the very specific OTClient which is based OTCv8 + - There is the most majority of the new cool features: mounts and shaders, in-game shop system, market system (there is a little snipper in www library to generate json for items), colourful loot message based on items.srv cost property, the implementation of inq, svargrond arena and other cool things. However many things are missing like no mount outfits for all outfits except citizen outfit, majority of items missing its description in items.srv. + +# Info regarding Sabrehaven 7.92 +It is quite precise replica of how 7.92 tibia worked only with original cipsoft client and it does not contain too much custom features. + +# Info regarding Sabrehaven 7.8 +It is supreme replica of 7.8 server- I was taking care of the every detail how the map and features differ from leaked 7.72 and 7.8 however didin't receive too much interest from players and I though 7.9 with POI has to be the thing I need to make server great success. Only 7.8 original client required to login. + +# Info regarding Sabrehaven 7.4 +Nothing much to say- wasn't a big success, it is basically a copy of 7.92 sabrehaven but without hotkeys and map has deleted PZs in boats to abuse newbies. And good old uh trap and other traps to abuse the newbies even more. Login with OTCv8. + +# The little QA session for you because I know what you want to ask + - Can I ask for server support? No. + - It is too difficult to add new NPC or map or something. Its OK when you get used to this. + - How to import maps from other open tibia maps which does not use cipsoft map and its item ids? Yes, it is hard. But you have to bless the guy which made this: https://github.com/Inconcessus/OTBM2JSON I bless him and you bless him. I can share my own snippet how I ussually converted TFS item ids to cipsoft item ids +
+ export.js +
+    // FROM_ID: TO_ID
+let replaceArray = {
+100:100,
+101:101,
+102:0,
+103:103,
+104:104,
+105:0,
+106:106,
+107:0,
+108:108,
+109:109,
+110:0,
+111:0,
+112:0,
+113:0,
+114:0,
+115:0,
+116:0,
+117:0,
+118:0,
+119:0,
+120:0,
+121:0,
+122:0,
+123:0,
+124:0,
+125:0,
+126:0,
+127:0,
+128:0,
+129:0,
+130:0,
+131:0,
+132:0,
+133:0,
+134:0,
+135:0,
+136:0,
+137:0,
+138:0,
+139:0,
+140:0,
+141:0,
+142:0,
+143:0,
+144:0,
+145:0,
+146:0,
+147:0,
+148:0,
+149:0,
+150:0,
+151:0,
+152:0,
+153:0,
+154:0,
+155:0,
+156:0,
+157:0,
+158:0,
+159:0,
+160:0,
+161:0,
+162:0,
+163:0,
+164:0,
+165:0,
+166:0,
+167:0,
+168:0,
+169:0,
+170:0,
+171:0,
+172:0,
+173:0,
+174:0,
+175:0,
+176:0,
+177:0,
+178:0,
+179:0,
+180:0,
+181:0,
+182:0,
+183:0,
+184:0,
+185:0,
+186:0,
+187:0,
+188:0,
+189:0,
+190:0,
+191:0,
+192:0,
+193:0,
+194:194,
+195:0,
+196:0,
+197:0,
+198:0,
+199:0,
+200:0,
+201:0,
+202:0,
+203:0,
+204:0,
+205:0,
+206:0,
+207:0,
+208:0,
+209:0,
+210:0,
+211:0,
+212:0,
+213:0,
+214:0,
+215:0,
+216:0,
+217:0,
+218:0,
+219:0,
+220:0,
+221:0,
+222:0,
+223:0,
+224:0,
+225:0,
+226:0,
+227:0,
+228:0,
+229:0,
+230:0,
+231:231,
+232:0,
+233:0,
+234:0,
+235:0,
+236:0,
+237:0,
+238:0,
+239:0,
+240:0,
+241:0,
+242:0,
+243:0,
+244:0,
+245:0,
+246:0,
+247:0,
+248:0,
+249:0,
+250:0,
+251:0,
+252:0,
+253:0,
+254:0,
+255:0,
+256:0,
+257:0,
+258:0,
+259:0,
+260:0,
+261:0,
+262:0,
+263:0,
+264:0,
+265:0,
+266:0,
+267:0,
+268:0,
+269:0,
+270:0,
+271:0,
+272:0,
+273:0,
+274:0,
+275:0,
+276:0,
+277:0,
+278:0,
+279:0,
+280:280,
+281:0,
+282:0,
+283:0,
+284:0,
+285:0,
+286:0,
+287:0,
+288:0,
+289:0,
+290:0,
+291:0,
+292:0,
+293:293,
+294:294,
+295:0,
+296:0,
+297:0,
+298:0,
+299:0,
+300:0,
+301:0,
+302:0,
+303:0,
+304:0,
+305:0,
+306:0,
+307:0,
+308:0,
+309:0,
+310:0,
+311:0,
+312:0,
+313:0,
+314:0,
+315:0,
+316:0,
+317:0,
+318:0,
+319:0,
+320:0,
+321:0,
+322:0,
+323:0,
+324:0,
+325:0,
+326:0,
+327:0,
+328:0,
+329:0,
+330:0,
+331:0,
+332:0,
+333:0,
+334:0,
+335:0,
+336:0,
+337:0,
+338:0,
+339:0,
+340:0,
+341:0,
+342:0,
+343:0,
+344:0,
+345:0,
+346:0,
+347:0,
+348:0,
+349:0,
+350:0,
+351:351,
+352:352,
+353:353,
+354:354,
+355:355,
+356:356,
+357:357,
+358:358,
+359:359,
+360:360,
+361:361,
+362:362,
+363:363,
+364:364,
+365:365,
+366:366,
+367:367,
+368:368,
+369:369,
+370:370,
+371:373,
+372:374,
+373:375,
+374:376,
+375:377,
+376:378,
+377:379,
+378:380,
+379:381,
+380:382,
+381:383,
+382:384,
+383:385,
+384:386,
+385:387,
+386:388,
+387:389,
+388:390,
+389:391,
+390:392,
+391:393,
+392:394,
+393:0,
+394:0,
+395:0,
+396:0,
+397:0,
+398:0,
+399:0,
+400:0,
+401:0,
+402:0,
+403:0,
+404:0,
+405:408,
+406:409,
+407:410,
+408:411,
+409:412,
+410:413,
+411:414,
+412:415,
+413:416,
+414:417,
+415:418,
+416:419,
+417:420,
+418:421,
+419:422,
+420:423,
+421:424,
+422:425,
+423:428,
+424:429,
+425:430,
+426:431,
+427:432,
+428:433,
+429:434,
+430:435,
+431:436,
+432:437,
+433:438,
+434:439,
+435:440,
+436:441,
+437:442,
+438:443,
+439:444,
+440:445,
+441:446,
+442:447,
+443:448,
+444:449,
+445:450,
+446:452,
+447:453,
+448:454,
+449:455,
+450:456,
+451:457,
+452:458,
+453:459,
+454:460,
+455:461,
+456:462,
+457:463,
+458:464,
+459:469,
+460:470,
+461:475,
+462:476,
+463:477,
+464:478,
+465:479,
+466:480,
+467:481,
+468:593,
+469:594,
+470:595,
+471:596,
+472:597,
+473:598,
+474:599,
+475:601,
+476:600,
+477:602,
+478:603,
+479:604,
+480:605,
+481:606,
+482:607,
+483:608,
+484:609,
+485:610,
+486:611,
+487:612,
+488:613,
+489:615,
+490:0,
+491:0,
+492:0,
+493:622,
+494:0,
+495:0,
+496:0,
+497:0,
+498:0,
+499:0,
+500:0,
+501:0,
+502:0,
+503:0,
+504:0,
+505:0,
+506:0,
+507:0,
+508:0,
+509:0,
+510:0,
+511:0,
+512:0,
+513:0,
+514:0,
+515:0,
+516:0,
+517:0,
+518:0,
+519:0,
+520:0,
+521:0,
+522:0,
+523:0,
+524:0,
+525:0,
+526:0,
+527:0,
+528:0,
+529:0,
+530:0,
+531:0,
+532:0,
+533:0,
+534:0,
+535:0,
+536:0,
+537:0,
+538:0,
+539:0,
+540:0,
+541:0,
+542:0,
+543:0,
+544:0,
+545:0,
+546:0,
+547:0,
+548:0,
+549:0,
+550:0,
+551:0,
+552:0,
+553:0,
+554:0,
+555:0,
+556:0,
+557:0,
+558:0,
+559:0,
+560:0,
+561:0,
+562:0,
+563:0,
+564:0,
+565:0,
+566:0,
+567:0,
+568:0,
+569:0,
+570:0,
+571:0,
+572:0,
+573:0,
+574:0,
+575:0,
+576:0,
+577:0,
+578:0,
+579:0,
+580:0,
+581:0,
+582:0,
+583:0,
+584:0,
+585:0,
+586:0,
+587:0,
+588:0,
+589:0,
+590:0,
+591:0,
+592:0,
+593:0,
+594:0,
+595:0,
+596:0,
+597:0,
+598:727,
+599:728,
+600:729,
+601:730,
+602:0,
+603:0,
+604:0,
+605:0,
+606:0,
+607:0,
+608:0,
+609:0,
+610:0,
+611:0,
+612:0,
+613:0,
+614:0,
+615:0,
+616:0,
+617:0,
+618:0,
+619:0,
+620:0,
+621:0,
+622:0,
+623:0,
+624:0,
+625:0,
+626:0,
+627:0,
+628:0,
+629:0,
+630:0,
+631:0,
+632:0,
+633:0,
+634:0,
+635:0,
+636:0,
+637:0,
+638:0,
+639:0,
+640:0,
+641:0,
+642:0,
+643:0,
+644:0,
+645:0,
+646:0,
+647:0,
+648:0,
+649:0,
+650:0,
+651:0,
+652:0,
+653:0,
+654:0,
+655:0,
+656:0,
+657:0,
+658:0,
+659:0,
+660:0,
+661:0,
+662:0,
+663:0,
+664:0,
+665:0,
+666:0,
+667:0,
+668:0,
+669:0,
+670:799,
+671:800,
+672:0,
+673:0,
+674:0,
+675:0,
+676:0,
+677:0,
+678:0,
+679:0,
+680:0,
+681:0,
+682:0,
+683:0,
+684:0,
+685:0,
+686:0,
+687:0,
+688:0,
+689:0,
+690:0,
+691:0,
+692:0,
+693:0,
+694:0,
+695:0,
+696:0,
+697:0,
+698:0,
+699:0,
+700:0,
+701:0,
+702:0,
+703:0,
+704:0,
+705:0,
+706:0,
+707:0,
+708:837,
+709:838,
+710:839,
+711:840,
+712:0,
+713:0,
+714:0,
+715:0,
+716:0,
+717:0,
+718:0,
+719:0,
+720:0,
+721:0,
+722:0,
+723:0,
+724:870,
+725:0,
+726:0,
+727:0,
+728:0,
+729:0,
+730:0,
+731:0,
+732:0,
+733:0,
+734:0,
+735:0,
+736:0,
+737:0,
+738:0,
+739:0,
+740:0,
+741:0,
+742:0,
+743:0,
+744:0,
+745:0,
+746:0,
+747:0,
+748:0,
+749:0,
+750:0,
+751:0,
+752:0,
+753:0,
+754:0,
+755:0,
+756:0,
+757:0,
+758:0,
+759:0,
+760:0,
+761:0,
+762:0,
+763:0,
+764:0,
+765:0,
+766:0,
+767:0,
+768:0,
+769:0,
+770:0,
+771:0,
+772:0,
+773:0,
+774:0,
+775:0,
+776:0,
+777:923,
+778:924,
+779:925,
+780:926,
+781:927,
+782:928,
+783:929,
+784:930,
+785:931,
+786:932,
+787:933,
+788:934,
+789:935,
+790:936,
+791:937,
+792:0,
+793:0,
+794:0,
+795:0,
+796:0,
+797:0,
+798:0,
+799:0,
+800:0,
+801:0,
+802:0,
+803:0,
+804:950,
+805:0,
+806:952,
+807:0,
+808:0,
+809:0,
+810:0,
+811:0,
+812:0,
+813:0,
+814:0,
+815:0,
+816:0,
+817:0,
+818:0,
+819:0,
+820:0,
+821:0,
+822:0,
+823:0,
+824:0,
+825:0,
+826:0,
+827:0,
+828:0,
+829:0,
+830:0,
+831:0,
+832:0,
+833:0,
+834:0,
+835:0,
+836:982,
+837:0,
+838:0,
+839:0,
+840:0,
+841:0,
+842:0,
+843:0,
+844:0,
+845:0,
+846:0,
+847:0,
+848:0,
+849:0,
+850:0,
+851:0,
+852:0,
+853:0,
+854:0,
+855:0,
+856:0,
+857:0,
+858:0,
+859:0,
+860:0,
+861:0,
+862:0,
+863:0,
+864:0,
+865:0,
+866:0,
+867:0,
+868:0,
+869:0,
+870:0,
+871:0,
+872:0,
+873:1081,
+874:1082,
+875:1083,
+876:1084,
+877:1085,
+878:1086,
+879:0,
+880:0,
+881:0,
+882:0,
+883:0,
+884:0,
+885:0,
+886:0,
+887:0,
+888:0,
+889:0,
+890:0,
+891:1100,
+892:1101,
+893:1102,
+894:1103,
+895:1104,
+896:1105,
+897:1106,
+898:1107,
+899:1108,
+900:1109,
+901:1110,
+902:1111,
+903:1112,
+904:1113,
+905:1114,
+906:1115,
+907:1116,
+908:1117,
+909:1118,
+910:1119,
+911:1120,
+912:1121,
+913:1122,
+914:1123,
+915:1124,
+916:1125,
+917:1126,
+918:1127,
+919:1128,
+920:1152,
+921:1153,
+922:1154,
+923:1155,
+924:1156,
+925:1157,
+926:1158,
+927:1159,
+928:1160,
+929:1161,
+930:1162,
+931:1163,
+932:1164,
+933:1165,
+934:1166,
+935:1167,
+936:1168,
+937:1169,
+938:1170,
+939:1171,
+940:1172,
+941:1173,
+942:1174,
+943:1175,
+944:1176,
+945:1177,
+946:1178,
+947:1179,
+948:1180,
+949:1181,
+950:1182,
+951:1183,
+952:1184,
+953:1185,
+954:1186,
+955:1187,
+956:1188,
+957:1189,
+958:1190,
+959:1191,
+960:1192,
+961:1193,
+962:1194,
+963:1195,
+964:1196,
+965:1210,
+966:1211,
+967:1212,
+968:1213,
+969:1214,
+970:1215,
+971:1216,
+972:1217,
+973:1218,
+974:1219,
+975:1220,
+976:1221,
+977:1222,
+978:1223,
+979:1224,
+980:1225,
+981:1226,
+982:1227,
+983:1228,
+984:1229,
+985:1230,
+986:1231,
+987:1232,
+988:1233,
+989:1234,
+990:1235,
+991:1236,
+992:1237,
+993:1238,
+994:1239,
+995:1240,
+996:1241,
+997:1242,
+998:1243,
+999:1244,
+1000:1245,
+1001:1246,
+1002:1247,
+1003:1248,
+1004:1249,
+1005:1250,
+1006:1251,
+1007:1252,
+1008:1253,
+1009:1254,
+1010:1255,
+1011:1256,
+1012:1257,
+1013:1258,
+1014:1259,
+1015:1260,
+1016:1261,
+1017:1262,
+1018:1263,
+1019:1264,
+1020:1265,
+1021:1266,
+1022:1267,
+1023:1268,
+1024:1269,
+1025:1270,
+1026:1271,
+1027:1272,
+1028:1273,
+1029:1274,
+1030:1275,
+1031:1276,
+1032:1277,
+1033:1278,
+1034:1279,
+1035:1280,
+1036:1281,
+1037:1282,
+1038:1283,
+1039:1284,
+1040:1285,
+1041:1286,
+1042:1287,
+1043:1288,
+1044:1289,
+1045:1290,
+1046:1291,
+1047:1292,
+1048:1293,
+1049:1294,
+1050:1295,
+1051:1296,
+1052:1297,
+1053:1298,
+1054:1299,
+1055:1300,
+1056:1301,
+1057:1302,
+1058:1303,
+1059:1304,
+1060:1305,
+1061:1306,
+1062:1307,
+1063:1308,
+1064:1309,
+1065:1310,
+1066:1311,
+1067:1312,
+1068:1313,
+1069:1314,
+1070:1315,
+1071:1316,
+1072:1317,
+1073:1318,
+1074:1319,
+1075:1320,
+1076:1321,
+1077:1322,
+1078:1323,
+1079:1324,
+1080:1325,
+1081:1326,
+1082:1327,
+1083:1328,
+1084:1329,
+1085:1330,
+1086:1331,
+1087:1332,
+1088:1333,
+1089:1334,
+1090:1335,
+1091:1336,
+1092:1337,
+1093:1338,
+1094:168,
+1095:1340,
+1096:1341,
+1097:1342,
+1098:1343,
+1099:1344,
+1100:1345,
+1101:1346,
+1102:1347,
+1103:1348,
+1104:1349,
+1105:1350,
+1106:1351,
+1107:1352,
+1108:1353,
+1109:1354,
+1110:1355,
+1111:1356,
+1112:1357,
+1113:1358,
+1114:1359,
+1115:1360,
+1116:1361,
+1117:1362,
+1118:1363,
+1119:1364,
+1120:1365,
+1121:1366,
+1122:1367,
+1123:1368,
+1124:1369,
+1125:1370,
+1126:1371,
+1127:1372,
+1128:1373,
+1129:1374,
+1130:1375,
+1131:1376,
+1132:1377,
+1133:1378,
+1134:1379,
+1135:1380,
+1136:1381,
+1137:1382,
+1138:1383,
+1139:1384,
+1140:1385,
+1141:1386,
+1142:1387,
+1143:1388,
+1144:1389,
+1145:1390,
+1146:1391,
+1147:1392,
+1148:1393,
+1149:1394,
+1150:1395,
+1151:1396,
+1152:1397,
+1153:1398,
+1154:1399,
+1155:1400,
+1156:1401,
+1157:1402,
+1158:1403,
+1159:1404,
+1160:1405,
+1161:1406,
+1162:1407,
+1163:1408,
+1164:1409,
+1165:1410,
+1166:1411,
+1167:1412,
+1168:1413,
+1169:1414,
+1170:1415,
+1171:1416,
+1172:1417,
+1173:1418,
+1174:1419,
+1175:1420,
+1176:1421,
+1177:1422,
+1178:1423,
+1179:1424,
+1180:1425,
+1181:1426,
+1182:1427,
+1183:1428,
+1184:1429,
+1185:1430,
+1186:1431,
+1187:1432,
+1188:1433,
+1189:1434,
+1190:1435,
+1191:1436,
+1192:1437,
+1193:1438,
+1194:1439,
+1195:1440,
+1196:1441,
+1197:1442,
+1198:1443,
+1199:1444,
+1200:1445,
+1201:1446,
+1202:1447,
+1203:1448,
+1204:1449,
+1205:1624,
+1206:1625,
+1207:1626,
+1208:1627,
+1209:1628,
+1210:1629,
+1211:1630,
+1212:1631,
+1213:1632,
+1214:1633,
+1215:1634,
+1216:1635,
+1217:1636,
+1218:1637,
+1219:1638,
+1220:1639,
+1221:1640,
+1222:1641,
+1223:1642,
+1224:1643,
+1225:1644,
+1226:1645,
+1227:1646,
+1228:1647,
+1229:1648,
+1230:1649,
+1231:1650,
+1232:1651,
+1233:1652,
+1234:1653,
+1235:1654,
+1236:1655,
+1237:1656,
+1238:1657,
+1239:1658,
+1240:1659,
+1241:1660,
+1242:1661,
+1243:1662,
+1244:1663,
+1245:1664,
+1246:1665,
+1247:1666,
+1248:1667,
+1249:1668,
+1250:1669,
+1251:1670,
+1252:1671,
+1253:1672,
+1254:1673,
+1255:1674,
+1256:1675,
+1257:1676,
+1258:1677,
+1259:1678,
+1260:1679,
+1261:1680,
+1262:1681,
+1263:1734,
+1264:1735,
+1265:1736,
+1266:1737,
+1267:1738,
+1268:1739,
+1269:1740,
+1270:1741,
+1271:1742,
+1272:1743,
+1273:1744,
+1274:1745,
+1275:1746,
+1276:1747,
+1277:1748,
+1278:1749,
+1279:1750,
+1280:1751,
+1281:1752,
+1282:1753,
+1283:1754,
+1284:1771,
+1285:1772,
+1286:1773,
+1287:1774,
+1288:1775,
+1289:1776,
+1290:1777,
+1291:1778,
+1292:1779,
+1293:1780,
+1294:1781,
+1295:1782,
+1296:1783,
+1297:1784,
+1298:1785,
+1299:1786,
+1300:1787,
+1301:1788,
+1302:1789,
+1303:1790,
+1304:1791,
+1305:1792,
+1306:1793,
+1307:1794,
+1308:1795,
+1309:1796,
+1310:1797,
+1311:1798,
+1312:1799,
+1313:1800,
+1314:1801,
+1315:1802,
+1316:1803,
+1317:1804,
+1318:1805,
+1319:1806,
+1320:1807,
+1321:1808,
+1322:1809,
+1323:1810,
+1324:1811,
+1325:1812,
+1326:1813,
+1327:1814,
+1328:1815,
+1329:1816,
+1330:1817,
+1331:1818,
+1332:1819,
+1333:1820,
+1334:1821,
+1335:1822,
+1336:1823,
+1337:1824,
+1338:1825,
+1339:1826,
+1340:1827,
+1341:1828,
+1342:1829,
+1343:1830,
+1344:1831,
+1345:1832,
+1346:1833,
+1347:1834,
+1348:1835,
+1349:1836,
+1350:1837,
+1351:1838,
+1352:1839,
+1353:1840,
+1354:1841,
+1355:1842,
+1356:1843,
+1357:1844,
+1358:1845,
+1359:1846,
+1360:1922,
+1361:1923,
+1362:1924,
+1363:1925,
+1364:1926,
+1365:1927,
+1366:1928,
+1367:1929,
+1368:1930,
+1369:1931,
+1370:1932,
+1371:1933,
+1372:1934,
+1373:1935,
+1374:1936,
+1375:1937,
+1376:1938,
+1377:1939,
+1378:1940,
+1379:1941,
+1380:1942,
+1381:1943,
+1382:1944,
+1383:1945,
+1384:1946,
+1385:1947,
+1386:1948,
+1387:1949,
+1388:1950,
+1389:1951,
+1390:1952,
+1391:1953,
+1392:1954,
+1393:1955,
+1394:1956,
+1395:1957,
+1396:1958,
+1397:775,
+1398:1960,
+1399:1961,
+1400:1962,
+1401:1963,
+1402:1964,
+1403:1965,
+1404:1966,
+1405:1967,
+1406:134,
+1407:1980,
+1408:1981,
+1409:1982,
+1410:1983,
+1411:1984,
+1412:1985,
+1413:1986,
+1414:1987,
+1415:1988,
+1416:1989,
+1417:1992,
+1418:1993,
+1419:1994,
+1420:1995,
+1421:1996,
+1422:1997,
+1423:1998,
+1424:1999,
+1425:2000,
+1426:2001,
+1427:2002,
+1428:2003,
+1429:2012,
+1430:2013,
+1431:2014,
+1432:2015,
+1433:2016,
+1434:2017,
+1435:2018,
+1436:2019,
+1437:2020,
+1438:2021,
+1439:2022,
+1440:2023,
+1441:2024,
+1442:2025,
+1443:2026,
+1444:2027,
+1445:2028,
+1446:2029,
+1447:2030,
+1448:2031,
+1449:2032,
+1450:2033,
+1451:2034,
+1452:2035,
+1453:2036,
+1454:2037,
+1455:2038,
+1456:2039,
+1457:2040,
+1458:2041,
+1459:2042,
+1460:2043,
+1461:2044,
+1462:2045,
+1463:2046,
+1464:2047,
+1465:2048,
+1466:2049,
+1467:2050,
+1468:2051,
+1469:2052,
+1470:2053,
+1471:2054,
+1472:2055,
+1473:2056,
+1474:2057,
+1475:2058,
+1476:2059,
+1477:2060,
+1478:2061,
+1479:2108,
+1480:2109,
+1481:2110,
+1482:2111,
+1483:2112,
+1484:2113,
+1485:2114,
+1486:2115,
+1487:2118,
+1488:2119,
+1489:2120,
+1490:105,
+1491:2122,
+1492:2123,
+1493:2124,
+1494:2125,
+1495:2126,
+1496:2121,
+1497:2129,
+1498:2129,
+1499:2130,
+1500:2131,
+1501:2132,
+1502:2133,
+1503:2134,
+1504:2135,
+1505:2136,
+1506:2137,
+1507:2138,
+1508:2140,
+1509:2144,
+1510:2145,
+1511:2146,
+1512:2147,
+1513:2148,
+1514:2152,
+1515:2153,
+1516:2154,
+1517:2155,
+1518:2156,
+1519:2157,
+1520:2158,
+1521:2159,
+1522:2160,
+1523:2161,
+1524:2162,
+1525:2163,
+1526:2164,
+1527:2165,
+1528:2166,
+1529:2167,
+1530:2168,
+1531:2169,
+1532:2170,
+1533:2171,
+1534:2172,
+1535:2173,
+1536:2174,
+1537:2175,
+1538:2176,
+1539:2177,
+1540:2178,
+1541:2179,
+1542:2180,
+1543:2181,
+1544:2182,
+1545:2183,
+1546:2184,
+1547:2185,
+1548:2187,
+1549:2188,
+1550:2189,
+1551:2190,
+1552:2191,
+1553:2192,
+1554:2193,
+1555:2194,
+1556:2195,
+1557:2196,
+1558:2197,
+1559:2198,
+1560:2199,
+1561:2200,
+1562:2201,
+1563:2202,
+1564:2203,
+1565:2204,
+1566:2205,
+1567:2206,
+1568:2207,
+1569:2208,
+1570:2209,
+1571:2210,
+1572:2211,
+1573:2212,
+1574:2213,
+1575:2214,
+1576:2215,
+1577:2216,
+1578:2218,
+1579:2219,
+1580:2220,
+1581:2221,
+1582:2222,
+1583:2223,
+1584:2224,
+1585:2225,
+1586:2226,
+1587:2227,
+1588:2228,
+1589:2229,
+1590:2230,
+1591:2231,
+1592:2232,
+1593:2233,
+1594:2234,
+1595:2235,
+1596:2236,
+1597:2237,
+1598:2238,
+1599:2239,
+1600:2240,
+1601:2241,
+1602:2302,
+1603:2303,
+1604:2304,
+1605:2305,
+1606:2306,
+1607:2307,
+1608:2308,
+1609:2309,
+1610:2310,
+1611:2311,
+1612:2312,
+1613:2313,
+1614:2314,
+1615:2315,
+1616:2316,
+1617:2317,
+1618:2318,
+1619:2319,
+1620:2320,
+1621:2321,
+1622:2322,
+1623:2323,
+1624:2324,
+1625:2325,
+1626:2326,
+1627:2327,
+1628:2328,
+1629:2329,
+1630:2330,
+1631:2331,
+1632:2332,
+1633:2333,
+1634:2334,
+1635:2335,
+1636:2336,
+1637:2337,
+1638:2338,
+1639:2339,
+1640:2340,
+1641:2341,
+1642:2342,
+1643:2343,
+1644:2344,
+1645:2345,
+1646:2354,
+1647:2355,
+1648:2356,
+1649:2357,
+1650:2358,
+1651:2359,
+1652:2360,
+1653:2361,
+1654:2362,
+1655:2363,
+1656:2364,
+1657:2365,
+1658:2366,
+1659:2367,
+1660:2368,
+1661:2369,
+1662:2370,
+1663:2371,
+1664:2372,
+1665:2373,
+1666:2374,
+1667:2375,
+1668:2376,
+1669:2377,
+1670:2378,
+1671:2379,
+1672:2380,
+1673:2381,
+1674:2382,
+1675:2383,
+1676:2384,
+1677:2385,
+1678:2386,
+1679:2387,
+1680:2388,
+1681:2389,
+1682:2390,
+1683:2391,
+1684:2392,
+1685:2393,
+1686:2394,
+1687:2395,
+1688:2396,
+1689:900,
+1690:2398,
+1691:2399,
+1692:2400,
+1693:2401,
+1694:2402,
+1695:2403,
+1696:2404,
+1697:2405,
+1698:2406,
+1699:2407,
+1700:2408,
+1701:2409,
+1702:2410,
+1703:2411,
+1704:2412,
+1705:2413,
+1706:2414,
+1707:2415,
+1708:2416,
+1709:2417,
+1710:2427,
+1711:2428,
+1712:2429,
+1713:2430,
+1714:2431,
+1715:2432,
+1716:2433,
+1717:2434,
+1718:2435,
+1719:2436,
+1720:2437,
+1721:2438,
+1722:2439,
+1723:2440,
+1724:2441,
+1725:2442,
+1726:2443,
+1727:2444,
+1728:2445,
+1729:2446,
+1730:2447,
+1731:2448,
+1732:2449,
+1733:2450,
+1734:2451,
+1735:2452,
+1736:2453,
+1737:2454,
+1738:2469,
+1739:2471,
+1740:2472,
+1741:2473,
+1742:2474,
+1743:2475,
+1744:2476,
+1745:2477,
+1746:2478,
+1747:2480,
+1748:2481,
+1749:2482,
+1750:2483,
+1751:2484,
+1752:2485,
+1753:2486,
+1754:2487,
+1755:2488,
+1756:2489,
+1757:2490,
+1758:2491,
+1759:2492,
+1760:2493,
+1761:2494,
+1762:2495,
+1763:695,
+1764:2497,
+1765:115,
+1766:2499,
+1767:2500,
+1768:2501,
+1769:2502,
+1770:2519,
+1771:137,
+1772:2520,
+1773:2521,
+1774:2523,
+1775:2524,
+1776:2522,
+1777:2526,
+1778:2527,
+1779:2528,
+1780:2529,
+1781:2530,
+1782:2531,
+1783:2532,
+1784:2533,
+1785:2534,
+1786:2535,
+1787:2536,
+1788:2537,
+1789:2538,
+1790:2539,
+1791:2540,
+1792:2541,
+1793:2542,
+1794:2572,
+1795:2573,
+1796:2574,
+1797:2575,
+1798:2576,
+1799:2577,
+1800:2578,
+1801:2579,
+1802:2580,
+1803:2581,
+1804:2582,
+1805:2583,
+1806:2584,
+1807:2585,
+1808:2586,
+1809:2596,
+1810:2597,
+1811:2598,
+1812:2599,
+1813:2600,
+1814:2601,
+1815:2602,
+1816:2603,
+1817:2604,
+1818:2605,
+1819:2606,
+1820:2607,
+1821:2608,
+1822:2609,
+1823:2610,
+1824:2611,
+1825:2612,
+1826:2613,
+1827:2614,
+1828:2615,
+1829:2616,
+1830:2617,
+1831:2618,
+1832:2619,
+1833:2620,
+1834:2621,
+1835:2622,
+1836:2623,
+1837:2624,
+1838:2625,
+1839:2626,
+1840:2627,
+1841:2628,
+1842:2629,
+1843:2630,
+1844:2631,
+1845:2632,
+1846:2633,
+1847:2634,
+1848:2635,
+1849:2636,
+1850:2637,
+1851:2638,
+1852:2639,
+1853:2640,
+1854:2641,
+1855:2642,
+1856:2643,
+1857:2644,
+1858:2645,
+1859:2646,
+1860:2647,
+1861:2648,
+1862:2649,
+1863:2650,
+1864:2651,
+1865:2652,
+1866:2653,
+1867:2654,
+1868:2655,
+1869:2656,
+1870:2657,
+1871:2658,
+1872:2659,
+1873:2660,
+1874:2661,
+1875:2662,
+1876:2663,
+1877:2664,
+1878:2665,
+1879:2666,
+1880:2667,
+1881:2668,
+1882:2669,
+1883:2670,
+1884:2671,
+1885:2672,
+1886:2673,
+1887:2674,
+1888:2675,
+1889:2676,
+1890:2677,
+1891:2678,
+1892:2679,
+1893:2680,
+1894:2681,
+1895:2682,
+1896:2683,
+1897:2684,
+1898:2685,
+1899:2686,
+1900:2693,
+1901:2694,
+1902:2695,
+1903:2696,
+1904:2697,
+1905:2698,
+1906:2699,
+1907:2700,
+1908:2701,
+1909:2702,
+1910:2703,
+1911:2704,
+1912:2705,
+1913:2706,
+1914:2707,
+1915:2708,
+1916:2709,
+1917:2710,
+1918:2711,
+1919:2712,
+1920:2713,
+1921:2714,
+1922:2715,
+1923:2716,
+1924:2717,
+1925:2718,
+1926:2719,
+1927:2720,
+1928:2721,
+1929:2722,
+1930:2723,
+1931:2724,
+1932:2725,
+1933:2726,
+1934:371,
+1935:2728,
+1936:2729,
+1937:2730,
+1938:372,
+1939:2732,
+1940:2733,
+1941:2734,
+1942:2735,
+1943:2736,
+1944:2737,
+1945:2772,
+1946:2773,
+1947:2813,
+1948:2814,
+1949:2815,
+1950:2816,
+1951:2817,
+1952:2818,
+1953:2819,
+1954:2820,
+1955:2821,
+1956:2822,
+1957:2823,
+1958:2824,
+1959:2825,
+1960:2826,
+1961:2827,
+1962:2828,
+1963:2829,
+1964:2830,
+1965:2831,
+1966:2832,
+1967:2833,
+1968:2834,
+1969:2835,
+1970:2836,
+1971:2837,
+1972:2838,
+1973:2839,
+1974:2840,
+1975:2841,
+1976:2842,
+1977:2843,
+1978:2844,
+1979:2845,
+1980:2846,
+1981:2847,
+1982:2848,
+1983:2849,
+1984:2850,
+1985:2851,
+1986:2852,
+1987:2853,
+1988:2854,
+1989:2855,
+1990:2856,
+1991:2857,
+1992:2858,
+1993:2859,
+1994:2860,
+1995:2861,
+1996:2862,
+1997:2863,
+1998:2865,
+1999:2866,
+2000:2867,
+2001:2868,
+2002:2869,
+2003:2870,
+2004:2871,
+2005:2873,
+2006:110,
+2007:138,
+2008:2876,
+2009:2877,
+2010:2878,
+2011:2879,
+2012:2880,
+2013:2881,
+2014:7243,
+2015:2885,
+2016:2886,
+2017:2887,
+2018:2888,
+2019:2889,
+2020:2890,
+2021:2891,
+2022:2892,
+2023:2893,
+2024:2894,
+2025:2895,
+2026:2896,
+2027:2897,
+2028:2898,
+2029:2899,
+2030:2900,
+2031:5938,
+2032:2902,
+2033:2903,
+2034:2904,
+2035:2905,
+2036:112,
+2037:2907,
+2038:2908,
+2039:2909,
+2040:2910,
+2041:2911,
+2042:2912,
+2043:2913,
+2044:2914,
+2045:2915,
+2046:2916,
+2047:2917,
+2048:2918,
+2049:2919,
+2050:2920,
+2051:2921,
+2052:2922,
+2053:2923,
+2054:2924,
+2055:2925,
+2056:2926,
+2057:2927,
+2058:2928,
+2059:2929,
+2060:2930,
+2061:2931,
+2062:2932,
+2063:2933,
+2064:2934,
+2065:2935,
+2066:2936,
+2067:2937,
+2068:2938,
+2069:2939,
+2070:2948,
+2071:2949,
+2072:2950,
+2073:2952,
+2074:2953,
+2075:2954,
+2076:2955,
+2077:2956,
+2078:2957,
+2079:2958,
+2080:2959,
+2081:2960,
+2082:2961,
+2083:2962,
+2084:2963,
+2085:2964,
+2086:2967,
+2087:2968,
+2088:2969,
+2089:2970,
+2090:2971,
+2091:2972,
+2092:2973,
+2093:2974,
+2094:2975,
+2095:2976,
+2096:2977,
+2097:2978,
+2098:2979,
+2099:2980,
+2100:2981,
+2101:2982,
+2102:2983,
+2103:2984,
+2104:2985,
+2105:2986,
+2106:2987,
+2107:2988,
+2108:2989,
+2109:2990,
+2110:2991,
+2111:2992,
+2112:2993,
+2113:2994,
+2114:2995,
+2115:2996,
+2116:2997,
+2117:2998,
+2118:2999,
+2119:3000,
+2120:3003,
+2121:3004,
+2122:3005,
+2123:3006,
+2124:3007,
+2125:3008,
+2126:3009,
+2127:3010,
+2128:3011,
+2129:3012,
+2130:3013,
+2131:3014,
+2132:3015,
+2133:3016,
+2134:3017,
+2135:3018,
+2136:3019,
+2137:3020,
+2138:3021,
+2139:3022,
+2140:3023,
+2141:3024,
+2142:3025,
+2143:3026,
+2144:3027,
+2145:3028,
+2146:3029,
+2147:3030,
+2148:3031,
+2149:3032,
+2150:3033,
+2151:3034,
+2152:3035,
+2153:3036,
+2154:3037,
+2155:3038,
+2156:3039,
+2157:3040,
+2158:3041,
+2159:3042,
+2160:3043,
+2161:3045,
+2162:3046,
+2163:3047,
+2164:3048,
+2165:3049,
+2166:3050,
+2167:3051,
+2168:3052,
+2169:3053,
+2170:3054,
+2171:3055,
+2172:3056,
+2173:3057,
+2174:3058,
+2175:648,
+2176:3060,
+2177:3061,
+2178:3062,
+2179:3063,
+2180:3064,
+2181:3065,
+2182:3066,
+2183:3067,
+2184:3068,
+2185:3069,
+2186:3070,
+2187:3071,
+2188:3072,
+2189:3073,
+2190:3074,
+2191:3075,
+2192:3076,
+2193:3077,
+2194:3078,
+2195:3079,
+2196:3080,
+2197:3081,
+2198:3082,
+2199:3083,
+2200:3084,
+2201:3085,
+2202:3086,
+2203:3087,
+2204:3088,
+2205:3089,
+2206:3090,
+2207:3091,
+2208:3092,
+2209:3093,
+2210:3094,
+2211:3095,
+2212:3096,
+2213:3097,
+2214:3098,
+2215:3099,
+2216:3100,
+2217:3101,
+2218:3102,
+2219:3104,
+2220:3105,
+2221:3106,
+2222:3107,
+2223:3108,
+2224:3109,
+2225:3110,
+2226:3111,
+2227:3112,
+2228:3113,
+2229:3114,
+2230:3115,
+2231:3116,
+2232:3117,
+2233:3118,
+2234:3119,
+2235:3120,
+2236:3121,
+2237:3122,
+2238:3123,
+2239:3124,
+2240:3125,
+2241:3126,
+2242:3127,
+2243:3128,
+2244:120,
+2245:3130,
+2246:3131,
+2247:3132,
+2248:3133,
+2249:3134,
+2250:3135,
+2251:3136,
+2252:3137,
+2253:3138,
+2254:3139,
+2255:3140,
+2256:3141,
+2257:3142,
+2258:3143,
+2259:3144,
+2260:3147,
+2261:3148,
+2262:3149,
+2263:3150,
+2264:3151,
+2265:3152,
+2266:3153,
+2267:3154,
+2268:3155,
+2269:3156,
+2270:3157,
+2271:3158,
+2272:3159,
+2273:3160,
+2274:3161,
+2275:3162,
+2276:3163,
+2277:3164,
+2278:3165,
+2279:3166,
+2280:3167,
+2281:3168,
+2282:3169,
+2283:3170,
+2284:3171,
+2285:3172,
+2286:3173,
+2287:3174,
+2288:3175,
+2289:3176,
+2290:3177,
+2291:3178,
+2292:3179,
+2293:3180,
+2294:3181,
+2295:3182,
+2296:3183,
+2297:3184,
+2298:3185,
+2299:3186,
+2300:3187,
+2301:3188,
+2302:3189,
+2303:3190,
+2304:3191,
+2305:3192,
+2306:3193,
+2307:3194,
+2308:3195,
+2309:3196,
+2310:3197,
+2311:3198,
+2312:3199,
+2313:3200,
+2314:3201,
+2315:3202,
+2316:3203,
+2317:3204,
+2318:3205,
+2319:3206,
+2320:3207,
+2321:3208,
+2322:3209,
+2323:3210,
+2324:3211,
+2325:3212,
+2326:3213,
+2327:3214,
+2328:3606,
+2329:3216,
+2330:3217,
+2331:3218,
+2332:3219,
+2333:402,
+2334:3221,
+2335:3222,
+2336:3223,
+2337:3224,
+2338:3225,
+2339:3226,
+2340:3227,
+2341:3228,
+2342:3229,
+2343:3230,
+2344:3231,
+2345:3232,
+2346:3233,
+2347:3234,
+2348:3235,
+2349:3236,
+2350:3237,
+2351:3238,
+2352:3239,
+2353:3240,
+2354:3241,
+2355:3242,
+2356:3243,
+2357:3245,
+2358:3246,
+2359:3247,
+2360:3248,
+2361:3249,
+2362:3250,
+2363:3251,
+2364:3252,
+2365:3253,
+2366:3254,
+2367:3255,
+2368:3256,
+2369:3257,
+2370:3258,
+2371:3259,
+2372:3260,
+2373:3261,
+2374:3262,
+2375:3263,
+2376:3264,
+2377:3265,
+2378:3266,
+2379:3267,
+2380:3268,
+2381:3269,
+2382:3270,
+2383:3271,
+2384:3272,
+2385:3273,
+2386:3274,
+2387:3275,
+2388:3276,
+2389:3277,
+2390:3278,
+2391:3279,
+2392:3280,
+2393:3281,
+2394:3282,
+2395:3283,
+2396:3284,
+2397:3285,
+2398:3286,
+2399:3287,
+2400:3288,
+2401:3289,
+2402:3290,
+2403:3291,
+2404:3292,
+2405:3293,
+2406:3294,
+2407:3295,
+2408:3296,
+2409:3297,
+2410:3298,
+2411:3299,
+2412:3300,
+2413:3301,
+2414:3302,
+2415:3303,
+2416:3304,
+2417:3305,
+2418:3306,
+2419:3307,
+2420:3308,
+2421:3309,
+2422:3310,
+2423:3311,
+2424:3312,
+2425:3313,
+2426:3314,
+2427:3315,
+2428:3316,
+2429:3317,
+2430:3318,
+2431:3319,
+2432:3320,
+2433:3321,
+2434:3322,
+2435:3323,
+2436:3324,
+2437:3325,
+2438:3326,
+2439:3327,
+2440:3328,
+2441:3329,
+2442:3330,
+2443:3331,
+2444:3332,
+2445:3333,
+2446:3334,
+2447:3335,
+2448:3336,
+2449:3337,
+2450:3338,
+2451:3339,
+2452:3340,
+2453:3341,
+2454:3342,
+2455:3349,
+2456:3350,
+2457:3351,
+2458:3352,
+2459:3353,
+2460:3354,
+2461:3355,
+2462:3356,
+2463:3357,
+2464:3358,
+2465:3359,
+2466:3360,
+2467:3361,
+2468:3362,
+2469:3363,
+2470:3364,
+2471:3365,
+2472:3366,
+2473:3367,
+2474:3368,
+2475:3369,
+2476:3370,
+2477:3371,
+2478:3372,
+2479:3373,
+2480:3374,
+2481:3375,
+2482:3376,
+2483:3377,
+2484:3378,
+2485:3379,
+2486:3380,
+2487:3381,
+2488:3382,
+2489:3383,
+2490:3384,
+2491:3385,
+2492:3386,
+2493:3387,
+2494:3388,
+2495:3389,
+2496:3390,
+2497:3391,
+2498:3392,
+2499:3393,
+2500:3394,
+2501:3395,
+2502:3396,
+2503:3397,
+2504:3398,
+2505:3399,
+2506:3400,
+2507:3401,
+2508:3402,
+2509:3409,
+2510:3410,
+2511:3411,
+2512:3412,
+2513:3413,
+2514:3414,
+2515:3415,
+2516:3416,
+2517:3417,
+2518:3418,
+2519:3419,
+2520:3420,
+2521:3421,
+2522:3422,
+2523:3423,
+2524:3424,
+2525:3425,
+2526:3426,
+2527:3427,
+2528:3428,
+2529:3429,
+2530:3430,
+2531:3431,
+2532:3432,
+2533:3433,
+2534:3434,
+2535:3435,
+2536:3436,
+2537:3437,
+2538:3438,
+2539:3439,
+2540:3440,
+2541:3441,
+2542:3442,
+2543:3446,
+2544:3447,
+2545:3448,
+2546:3449,
+2547:3450,
+2548:3451,
+2549:3452,
+2550:3453,
+2551:3454,
+2552:3455,
+2553:3456,
+2554:3457,
+2555:3458,
+2556:3459,
+2557:3460,
+2558:3461,
+2559:3462,
+2560:3463,
+2561:3464,
+2562:3465,
+2563:3466,
+2564:3467,
+2565:3468,
+2566:3469,
+2567:3470,
+2568:3471,
+2569:3472,
+2570:3473,
+2571:3474,
+2572:3475,
+2573:3476,
+2574:3477,
+2575:3478,
+2576:3479,
+2577:3480,
+2578:3481,
+2579:3482,
+2580:3483,
+2581:3484,
+2582:3485,
+2583:3486,
+2584:3487,
+2585:3488,
+2586:3489,
+2587:3490,
+2588:3491,
+2589:3497,
+2590:3498,
+2591:3499,
+2592:3500,
+2593:3501,
+2594:3502,
+2595:140,
+2596:3504,
+2597:3505,
+2598:3506,
+2599:3507,
+2600:127,
+2601:3510,
+2602:3511,
+2603:3512,
+2604:3513,
+2605:3514,
+2606:3515,
+2607:3516,
+2608:3517,
+2609:3518,
+2610:3519,
+2611:3520,
+2612:3521,
+2613:3522,
+2614:3523,
+2615:3524,
+2616:3525,
+2617:3526,
+2618:3527,
+2619:3528,
+2620:3529,
+2621:3530,
+2622:3531,
+2623:3532,
+2624:3533,
+2625:3534,
+2626:3535,
+2627:3536,
+2628:3537,
+2629:3538,
+2630:3539,
+2631:3540,
+2632:3541,
+2633:3542,
+2634:3543,
+2635:3544,
+2636:3545,
+2637:3546,
+2638:3547,
+2639:3548,
+2640:3549,
+2641:3550,
+2642:3551,
+2643:3552,
+2644:3553,
+2645:3554,
+2646:3555,
+2647:3557,
+2648:3558,
+2649:3559,
+2650:3561,
+2651:3562,
+2652:3563,
+2653:3564,
+2654:3565,
+2655:3566,
+2656:3567,
+2657:3568,
+2658:3569,
+2659:3570,
+2660:3571,
+2661:3572,
+2662:3573,
+2663:3574,
+2664:3575,
+2665:3576,
+2666:3577,
+2667:3578,
+2668:3579,
+2669:3580,
+2670:3581,
+2671:3582,
+2672:3583,
+2673:3584,
+2674:3585,
+2675:3586,
+2676:3587,
+2677:3588,
+2678:3589,
+2679:3590,
+2680:3591,
+2681:3592,
+2682:3593,
+2683:3594,
+2684:3595,
+2685:3596,
+2686:3597,
+2687:130,
+2688:3599,
+2689:3600,
+2690:3601,
+2691:3602,
+2692:3603,
+2693:3604,
+2694:3605,
+2695:3606,
+2696:169,
+2697:3608,
+2698:3609,
+2699:3613,
+2700:3614,
+2701:3615,
+2702:3616,
+2703:3617,
+2704:3618,
+2705:3619,
+2706:3620,
+2707:3621,
+2708:3622,
+2709:3623,
+2710:3624,
+2711:3625,
+2712:3626,
+2713:3627,
+2714:3628,
+2715:3629,
+2716:3630,
+2717:3631,
+2718:3632,
+2719:3633,
+2720:3634,
+2721:3635,
+2722:3636,
+2723:3637,
+2724:3638,
+2725:3639,
+2726:3640,
+2727:3641,
+2728:3642,
+2729:3643,
+2730:3644,
+2731:3645,
+2732:3646,
+2733:3647,
+2734:3648,
+2735:3649,
+2736:3650,
+2737:3651,
+2738:3652,
+2739:3653,
+2740:3654,
+2741:3655,
+2742:3656,
+2743:3657,
+2744:3658,
+2745:3659,
+2746:3660,
+2747:3661,
+2748:3662,
+2749:3663,
+2750:3664,
+2751:3665,
+2752:3666,
+2753:3667,
+2754:3668,
+2755:3669,
+2756:3670,
+2757:3671,
+2758:3672,
+2759:3673,
+2760:3674,
+2761:3675,
+2762:3676,
+2763:3677,
+2764:3678,
+2765:3679,
+2766:3680,
+2767:3681,
+2768:3682,
+2769:3683,
+2770:3684,
+2771:3685,
+2772:3686,
+2773:3687,
+2774:3688,
+2775:3689,
+2776:3690,
+2777:3691,
+2778:3692,
+2779:3693,
+2780:3694,
+2781:3695,
+2782:3696,
+2783:3697,
+2784:3698,
+2785:3699,
+2786:3700,
+2787:3723,
+2788:3724,
+2789:3725,
+2790:3726,
+2791:3727,
+2792:3728,
+2793:3729,
+2794:3730,
+2795:3731,
+2796:3732,
+2797:3733,
+2798:3734,
+2799:3735,
+2800:3736,
+2801:3737,
+2802:3738,
+2803:3739,
+2804:3740,
+2805:3741,
+2806:3987,
+2807:3988,
+2808:3989,
+2809:3990,
+2810:4252,
+2811:864,
+2812:395,
+2813:3994,
+2814:3995,
+2815:3996,
+2816:3996,
+2817:3998,
+2818:3999,
+2819:4000,
+2820:4001,
+2821:4002,
+2822:4003,
+2823:4004,
+2824:4005,
+2825:4006,
+2826:4007,
+2827:4008,
+2828:4009,
+2829:4009,
+2830:4011,
+2831:4012,
+2832:4013,
+2833:4275,
+2834:4276,
+2835:4016,
+2836:4017,
+2837:4018,
+2838:4280,
+2839:4020,
+2840:4021,
+2841:4022,
+2842:4022,
+2843:4024,
+2844:4025,
+2845:4026,
+2846:4027,
+2847:4064,
+2848:4029,
+2849:4030,
+2850:4031,
+2851:4032,
+2852:4032,
+2853:4034,
+2854:4296,
+2855:4036,
+2856:4036,
+2857:4038,
+2858:4039,
+2859:4040,
+2860:4041,
+2861:4042,
+2862:4043,
+2863:4044,
+2864:4045,
+2865:4046,
+2866:4047,
+2867:4048,
+2868:4013,
+2869:4050,
+2870:4051,
+2871:4052,
+2872:4053,
+2873:4274,
+2874:4055,
+2875:4056,
+2876:4057,
+2877:4058,
+2878:4059,
+2879:4060,
+2880:4061,
+2881:4062,
+2882:4063,
+2883:4064,
+2884:4289,
+2885:4066,
+2886:4067,
+2887:4068,
+2888:4069,
+2889:4070,
+2890:4071,
+2891:4072,
+2892:4072,
+2893:4074,
+2894:4075,
+2895:4076,
+2896:4076,
+2897:4078,
+2898:4079,
+2899:4080,
+2900:4081,
+2901:4082,
+2902:4083,
+2903:4084,
+2904:4085,
+2905:4086,
+2906:4087,
+2907:4088,
+2908:4089,
+2909:4090,
+2910:4091,
+2911:4092,
+2912:4093,
+2913:4094,
+2914:4095,
+2915:4096,
+2916:4097,
+2917:4098,
+2918:4099,
+2919:4099,
+2920:4101,
+2921:4102,
+2922:4103,
+2923:4104,
+2924:4105,
+2925:4106,
+2926:4107,
+2927:4108,
+2928:4109,
+2929:4110,
+2930:3992,
+2931:4112,
+2932:4113,
+2933:4114,
+2934:4114,
+2935:4116,
+2936:4117,
+2937:4118,
+2938:4119,
+2939:4120,
+2940:4121,
+2941:4122,
+2942:4123,
+2943:4123,
+2944:4125,
+2945:4126,
+2946:4127,
+2947:4128,
+2948:4129,
+2949:144,
+2950:4131,
+2951:4132,
+2952:4133,
+2953:4134,
+2954:4135,
+2955:4136,
+2956:4137,
+2957:4138,
+2958:4139,
+2959:4139,
+2960:4141,
+2961:4142,
+2962:4143,
+2963:4143,
+2964:4145,
+2965:4146,
+2966:4147,
+2967:4148,
+2968:4149,
+2969:4150,
+2970:4151,
+2971:4152,
+2972:4153,
+2973:4154,
+2974:4155,
+2975:4156,
+2976:4157,
+2977:4158,
+2978:4159,
+2979:4160,
+2980:4161,
+2981:4162,
+2982:4163,
+2983:4164,
+2984:4165,
+2985:4166,
+2986:4167,
+2987:4168,
+2988:4169,
+2989:4170,
+2990:4171,
+2991:4172,
+2992:4173,
+2993:4174,
+2994:4175,
+2995:4176,
+2996:4177,
+2997:4111,
+2998:4179,
+2999:4180,
+3000:4181,
+3001:4182,
+3002:4183,
+3003:4184,
+3004:4185,
+3005:4186,
+3006:4187,
+3007:4188,
+3008:4189,
+3009:4190,
+3010:4191,
+3011:4192,
+3012:4193,
+3013:4194,
+3014:4195,
+3015:4196,
+3016:4197,
+3017:4198,
+3018:4199,
+3019:4200,
+3020:4201,
+3021:4202,
+3022:4203,
+3023:4204,
+3024:4205,
+3025:4206,
+3026:4207,
+3027:4208,
+3028:4209,
+3029:4210,
+3030:4211,
+3031:4212,
+3032:4213,
+3033:4214,
+3034:4215,
+3035:4216,
+3036:4217,
+3037:4218,
+3038:4219,
+3039:4220,
+3040:4221,
+3041:4222,
+3042:4223,
+3043:4224,
+3044:4225,
+3045:4226,
+3046:4227,
+3047:4228,
+3048:4229,
+3049:4230,
+3050:4231,
+3051:4232,
+3052:4233,
+3053:4234,
+3054:4235,
+3055:4236,
+3056:4237,
+3057:4238,
+3058:4240,
+3059:4241,
+3060:4242,
+3061:4243,
+3062:4244,
+3063:4245,
+3064:4246,
+3065:4247,
+3066:4248,
+3067:4249,
+3068:4250,
+3069:4251,
+3070:4252,
+3071:4178,
+3072:4253,
+3073:4255,
+3074:4256,
+3075:4257,
+3076:4258,
+3077:4259,
+3078:4260,
+3079:4261,
+3080:4262,
+3081:4263,
+3082:4264,
+3083:4265,
+3084:4266,
+3085:4267,
+3086:4268,
+3087:4269,
+3088:4270,
+3089:4271,
+3090:4272,
+3091:4273,
+3092:4274,
+3093:4275,
+3094:4276,
+3095:4277,
+3096:4278,
+3097:4279,
+3098:4280,
+3099:4281,
+3100:4282,
+3101:4283,
+3102:4284,
+3103:4285,
+3104:4286,
+3105:4287,
+3106:4288,
+3107:4289,
+3108:4290,
+3109:4291,
+3110:4292,
+3111:4293,
+3112:4294,
+3113:4295,
+3114:4296,
+3115:4297,
+3116:4298,
+3117:4299,
+3118:4300,
+3119:4301,
+3120:4302,
+3121:4303,
+3122:4304,
+3123:4305,
+3124:4306,
+3125:4307,
+3126:4308,
+3127:4309,
+3128:4311,
+3129:4312,
+3130:4313,
+3131:4314,
+3132:4315,
+3133:4316,
+3134:4317,
+3135:482,
+3136:483,
+3137:484,
+3138:485,
+3139:486,
+3140:487,
+3141:488,
+3142:489,
+3143:490,
+3144:491,
+3145:492,
+3146:493,
+3147:494,
+3148:495,
+3149:496,
+3150:497,
+3151:498,
+3152:499,
+3153:500,
+3154:501,
+3155:502,
+3156:503,
+3157:504,
+3158:505,
+3159:506,
+3160:507,
+3161:508,
+3162:509,
+3163:510,
+3164:511,
+3165:512,
+3166:513,
+3167:514,
+3168:515,
+3169:516,
+3170:517,
+3171:518,
+3172:519,
+3173:520,
+3174:521,
+3175:522,
+3176:523,
+3177:524,
+3178:525,
+3179:526,
+3180:527,
+3181:528,
+3182:529,
+3183:530,
+3184:531,
+3185:532,
+3186:533,
+3187:534,
+3188:535,
+3189:536,
+3190:537,
+3191:538,
+3192:539,
+3193:540,
+3194:541,
+3195:542,
+3196:543,
+3197:544,
+3198:545,
+3199:546,
+3200:547,
+3201:548,
+3202:549,
+3203:550,
+3204:551,
+3205:552,
+3206:553,
+3207:554,
+3208:555,
+3209:556,
+3210:557,
+3211:558,
+3212:559,
+3213:560,
+3214:561,
+3215:562,
+3216:563,
+3217:564,
+3218:565,
+3219:566,
+3220:567,
+3221:568,
+3222:569,
+3223:570,
+3224:571,
+3225:572,
+3226:573,
+3227:574,
+3228:575,
+3229:576,
+3230:577,
+3231:578,
+3232:579,
+3233:580,
+3234:581,
+3235:582,
+3236:583,
+3237:584,
+3238:585,
+3239:586,
+3240:587,
+3241:588,
+3242:589,
+3243:590,
+3244:591,
+3245:592,
+3246:0,
+3247:0,
+3248:0,
+3249:0,
+3250:0,
+3251:0,
+3252:0,
+3253:0,
+3254:0,
+3255:0,
+3256:0,
+3257:0,
+3258:0,
+3259:0,
+3260:0,
+3261:0,
+3262:0,
+3263:1019,
+3264:1020,
+3265:1021,
+3266:0,
+3267:0,
+3268:0,
+3269:0,
+3270:0,
+3271:0,
+3272:0,
+3273:0,
+3274:0,
+3275:0,
+3276:0,
+3277:0,
+3278:0,
+3279:0,
+3280:0,
+3281:0,
+3282:0,
+3283:0,
+3284:0,
+3285:0,
+3286:0,
+3287:0,
+3288:0,
+3289:0,
+3290:0,
+3291:0,
+3292:0,
+3293:0,
+3294:0,
+3295:0,
+3296:0,
+3297:0,
+3298:1054,
+3299:1055,
+3300:1056,
+3301:1057,
+3302:1058,
+3303:1059,
+3304:1060,
+3305:1061,
+3306:1062,
+3307:1063,
+3308:1064,
+3309:1065,
+3310:1066,
+3311:1067,
+3312:1068,
+3313:876,
+3314:1070,
+3315:1071,
+3316:1072,
+3317:1073,
+3318:1074,
+3319:1075,
+3320:1076,
+3321:1077,
+3322:1078,
+3323:1079,
+3324:1080,
+3325:1129,
+3326:1130,
+3327:1131,
+3328:1132,
+3329:1133,
+3330:1134,
+3331:1135,
+3332:1136,
+3333:1137,
+3334:1138,
+3335:1139,
+3336:1140,
+3337:1141,
+3338:1142,
+3339:1143,
+3340:1144,
+3341:1145,
+3342:1146,
+3343:1147,
+3344:1148,
+3345:1149,
+3346:1150,
+3347:1151,
+3348:1197,
+3349:1198,
+3350:1199,
+3351:1200,
+3352:1201,
+3353:1202,
+3354:1203,
+3355:1204,
+3356:1205,
+3357:1206,
+3358:1207,
+3359:1208,
+3360:1209,
+3361:1450,
+3362:1451,
+3363:1452,
+3364:1453,
+3365:1454,
+3366:1455,
+3367:1456,
+3368:1457,
+3369:1458,
+3370:1459,
+3371:1460,
+3372:1461,
+3373:1462,
+3374:1463,
+3375:1464,
+3376:1465,
+3377:1466,
+3378:1467,
+3379:1468,
+3380:1469,
+3381:1470,
+3382:1471,
+3383:1472,
+3384:1473,
+3385:1474,
+3386:1475,
+3387:1476,
+3388:1477,
+3389:1478,
+3390:1479,
+3391:1480,
+3392:1481,
+3393:1482,
+3394:1483,
+3395:1484,
+3396:1485,
+3397:1486,
+3398:1487,
+3399:1488,
+3400:1489,
+3401:1490,
+3402:1491,
+3403:1492,
+3404:1493,
+3405:1494,
+3406:1495,
+3407:1496,
+3408:1497,
+3409:1498,
+3410:1499,
+3411:1500,
+3412:1501,
+3413:1502,
+3414:1503,
+3415:1504,
+3416:1505,
+3417:1506,
+3418:1507,
+3419:1508,
+3420:1509,
+3421:1510,
+3422:1511,
+3423:1512,
+3424:1513,
+3425:1514,
+3426:1515,
+3427:1516,
+3428:1517,
+3429:1518,
+3430:1519,
+3431:1520,
+3432:1521,
+3433:1522,
+3434:1523,
+3435:1524,
+3436:1525,
+3437:1526,
+3438:1527,
+3439:1528,
+3440:1529,
+3441:1530,
+3442:1531,
+3443:1532,
+3444:1533,
+3445:1534,
+3446:1535,
+3447:1536,
+3448:1537,
+3449:1538,
+3450:1539,
+3451:1540,
+3452:1541,
+3453:1542,
+3454:1543,
+3455:1544,
+3456:1545,
+3457:1546,
+3458:1547,
+3459:1548,
+3460:1549,
+3461:1550,
+3462:1551,
+3463:1552,
+3464:1553,
+3465:1554,
+3466:1555,
+3467:1556,
+3468:1557,
+3469:1558,
+3470:1559,
+3471:1560,
+3472:1561,
+3473:1562,
+3474:1563,
+3475:1564,
+3476:1565,
+3477:1566,
+3478:1567,
+3479:1568,
+3480:1569,
+3481:1570,
+3482:1571,
+3483:1572,
+3484:1573,
+3485:1574,
+3486:1575,
+3487:1576,
+3488:1577,
+3489:1578,
+3490:1579,
+3491:1580,
+3492:1581,
+3493:1582,
+3494:1583,
+3495:1584,
+3496:1585,
+3497:1586,
+3498:1587,
+3499:1588,
+3500:1589,
+3501:1590,
+3502:1591,
+3503:1592,
+3504:1593,
+3505:1594,
+3506:1595,
+3507:1596,
+3508:1597,
+3509:1598,
+3510:1599,
+3511:1600,
+3512:1601,
+3513:1602,
+3514:1603,
+3515:1604,
+3516:1605,
+3517:1606,
+3518:1607,
+3519:1608,
+3520:1609,
+3521:1610,
+3522:1611,
+3523:1612,
+3524:1613,
+3525:1614,
+3526:1615,
+3527:1616,
+3528:1617,
+3529:1618,
+3530:1619,
+3531:1620,
+3532:1621,
+3533:1622,
+3534:1623,
+3535:1682,
+3536:1683,
+3537:1684,
+3538:1685,
+3539:1686,
+3540:1687,
+3541:1688,
+3542:1689,
+3543:1690,
+3544:1691,
+3545:1692,
+3546:1693,
+3547:1694,
+3548:1695,
+3549:1696,
+3550:1697,
+3551:1698,
+3552:1699,
+3553:0,
+3554:0,
+3555:0,
+3556:0,
+3557:0,
+3558:0,
+3559:0,
+3560:0,
+3561:0,
+3562:0,
+3563:0,
+3564:0,
+3565:0,
+3566:0,
+3567:0,
+3568:0,
+3569:1716,
+3570:1717,
+3571:1718,
+3572:1719,
+3573:1720,
+3574:1721,
+3575:0,
+3576:0,
+3577:0,
+3578:0,
+3579:0,
+3580:0,
+3581:0,
+3582:0,
+3583:0,
+3584:0,
+3585:0,
+3586:0,
+3587:1755,
+3588:1756,
+3589:1757,
+3590:1758,
+3591:1759,
+3592:1760,
+3593:1761,
+3594:1762,
+3595:1763,
+3596:1764,
+3597:1765,
+3598:1766,
+3599:1767,
+3600:1768,
+3601:1769,
+3602:4945,
+3603:1847,
+3604:1848,
+3605:1849,
+3606:1850,
+3607:1851,
+3608:1852,
+3609:1853,
+3610:1854,
+3611:1855,
+3612:1856,
+3613:1857,
+3614:1858,
+3615:1859,
+3616:1860,
+3617:1861,
+3618:1862,
+3619:1863,
+3620:1864,
+3621:1865,
+3622:1866,
+3623:1867,
+3624:1868,
+3625:1869,
+3626:1870,
+3627:1871,
+3628:1872,
+3629:1873,
+3630:1874,
+3631:1875,
+3632:1876,
+3633:1877,
+3634:1878,
+3635:1879,
+3636:1880,
+3637:1881,
+3638:1882,
+3639:1883,
+3640:1884,
+3641:1885,
+3642:1886,
+3643:1887,
+3644:1888,
+3645:1889,
+3646:1890,
+3647:1891,
+3648:1892,
+3649:1893,
+3650:1894,
+3651:1895,
+3652:1896,
+3653:1897,
+3654:1898,
+3655:1899,
+3656:1900,
+3657:1901,
+3658:1902,
+3659:1903,
+3660:1904,
+3661:1905,
+3662:1906,
+3663:1907,
+3664:1908,
+3665:1909,
+3666:1910,
+3667:1911,
+3668:1912,
+3669:1913,
+3670:1914,
+3671:1915,
+3672:1916,
+3673:1917,
+3674:1918,
+3675:1919,
+3676:1920,
+3677:1921,
+3678:1968,
+3679:1969,
+3680:1970,
+3681:1971,
+3682:1972,
+3683:1973,
+3684:1974,
+3685:1975,
+3686:1976,
+3687:1977,
+3688:1978,
+3689:2004,
+3690:2005,
+3691:2006,
+3692:2007,
+3693:2008,
+3694:2009,
+3695:2010,
+3696:2011,
+3697:2062,
+3698:2063,
+3699:2064,
+3700:2065,
+3701:2066,
+3702:2067,
+3703:2068,
+3704:2069,
+3705:2070,
+3706:2071,
+3707:2072,
+3708:2073,
+3709:2074,
+3710:2075,
+3711:2076,
+3712:2077,
+3713:2078,
+3714:2079,
+3715:2080,
+3716:2081,
+3717:2082,
+3718:2083,
+3719:2084,
+3720:2085,
+3721:2086,
+3722:2087,
+3723:2088,
+3724:2089,
+3725:2090,
+3726:2091,
+3727:2092,
+3728:2093,
+3729:2094,
+3730:2095,
+3731:2096,
+3732:2097,
+3733:2098,
+3734:2099,
+3735:2100,
+3736:2101,
+3737:2102,
+3738:2103,
+3739:2104,
+3740:2105,
+3741:2106,
+3742:2107,
+3743:2116,
+3744:5024,
+3745:2242,
+3746:2243,
+3747:2244,
+3748:2245,
+3749:2246,
+3750:2247,
+3751:2248,
+3752:2249,
+3753:2250,
+3754:2251,
+3755:2252,
+3756:2253,
+3757:2254,
+3758:2255,
+3759:2256,
+3760:2257,
+3761:2258,
+3762:2259,
+3763:2260,
+3764:2261,
+3765:2262,
+3766:2263,
+3767:2264,
+3768:2265,
+3769:2266,
+3770:2267,
+3771:2268,
+3772:2269,
+3773:2270,
+3774:2271,
+3775:2272,
+3776:2273,
+3777:2274,
+3778:2275,
+3779:2276,
+3780:2277,
+3781:2278,
+3782:2279,
+3783:2280,
+3784:2281,
+3785:2282,
+3786:2283,
+3787:2284,
+3788:2285,
+3789:2286,
+3790:2287,
+3791:2288,
+3792:2289,
+3793:2290,
+3794:2291,
+3795:2292,
+3796:2293,
+3797:2294,
+3798:2295,
+3799:2296,
+3800:2297,
+3801:2298,
+3802:2299,
+3803:2300,
+3804:2301,
+3805:2346,
+3806:2347,
+3807:2348,
+3808:2349,
+3809:2350,
+3810:2351,
+3811:2352,
+3812:2353,
+3813:2418,
+3814:2419,
+3815:2420,
+3816:2421,
+3817:2422,
+3818:2423,
+3819:2424,
+3820:2425,
+3821:2426,
+3822:2455,
+3823:2456,
+3824:2457,
+3825:2458,
+3826:2459,
+3827:2460,
+3828:2461,
+3829:2462,
+3830:2463,
+3831:2464,
+3832:2465,
+3833:2466,
+3834:2467,
+3835:2468,
+3836:2503,
+3837:2504,
+3838:2505,
+3839:2506,
+3840:2507,
+3841:703,
+3842:2509,
+3843:705,
+3844:2511,
+3845:2512,
+3846:2513,
+3847:2514,
+3848:2515,
+3849:2516,
+3850:2517,
+3851:2518,
+3852:2587,
+3853:2588,
+3854:2589,
+3855:2590,
+3856:2591,
+3857:2592,
+3858:2593,
+3859:2594,
+3860:2595,
+3861:2687,
+3862:2688,
+3863:2689,
+3864:2690,
+3865:2691,
+3866:2692,
+3867:2738,
+3868:2739,
+3869:2740,
+3870:2741,
+3871:2742,
+3872:2743,
+3873:2744,
+3874:2745,
+3875:2746,
+3876:2747,
+3877:2748,
+3878:2749,
+3879:2750,
+3880:2751,
+3881:2752,
+3882:2753,
+3883:2754,
+3884:2755,
+3885:2756,
+3886:2757,
+3887:2758,
+3888:2759,
+3889:2760,
+3890:2761,
+3891:2762,
+3892:2763,
+3893:2764,
+3894:2765,
+3895:2766,
+3896:2767,
+3897:2768,
+3898:2769,
+3899:2770,
+3900:2771,
+3901:2775,
+3902:2776,
+3903:2777,
+3904:2778,
+3905:2779,
+3906:2780,
+3907:2781,
+3908:2782,
+3909:2783,
+3910:2784,
+3911:2785,
+3912:2786,
+3913:2787,
+3914:2788,
+3915:2789,
+3916:2790,
+3917:2791,
+3918:2792,
+3919:2793,
+3920:2794,
+3921:2795,
+3922:2796,
+3923:2797,
+3924:2798,
+3925:2799,
+3926:2800,
+3927:2801,
+3928:2802,
+3929:2803,
+3930:2804,
+3931:2805,
+3932:2806,
+3933:2807,
+3934:2808,
+3935:2809,
+3936:2810,
+3937:2811,
+3938:2812,
+3939:2864,
+3940:2872,
+3941:2883,
+3942:2884,
+3943:2940,
+3944:2941,
+3945:2942,
+3946:2943,
+3947:2944,
+3948:2945,
+3949:2946,
+3950:2947,
+3951:2951,
+3952:2965,
+3953:2966,
+3954:3001,
+3955:0000,
+3956:3044,
+3957:3103,
+3958:3145,
+3959:3146,
+3960:3244,
+3961:3343,
+3962:3344,
+3963:3345,
+3964:3346,
+3965:3347,
+3966:3348,
+3967:3403,
+3968:3404,
+3969:3405,
+3970:3406,
+3971:3407,
+3972:3408,
+3973:3443,
+3974:3444,
+3975:3445,
+3976:3492,
+3977:3493,
+3978:3494,
+3979:3495,
+3980:3496,
+3981:3508,
+3982:3556,
+3983:3560,
+3984:3701,
+3985:3702,
+3986:3703,
+3987:3704,
+3988:3705,
+3989:3706,
+3990:3707,
+3991:3708,
+3992:3709,
+3993:3710,
+3994:3711,
+3995:3712,
+3996:3713,
+3997:3714,
+3998:3715,
+3999:3716,
+4000:3717,
+4001:3718,
+4002:3719,
+4003:3720,
+4004:3721,
+4005:3722,
+4006:3742,
+4007:3743,
+4008:3744,
+4009:3745,
+4010:3746,
+4011:3747,
+4012:3748,
+4013:3749,
+4014:3750,
+4015:3751,
+4016:3752,
+4017:3753,
+4018:3754,
+4019:3755,
+4020:3756,
+4021:3757,
+4022:3758,
+4023:3759,
+4024:3760,
+4025:3761,
+4026:3762,
+4027:3763,
+4028:3764,
+4029:3765,
+4030:3766,
+4031:3767,
+4032:3768,
+4033:3769,
+4034:3770,
+4035:3771,
+4036:3772,
+4037:3773,
+4038:3774,
+4039:3775,
+4040:3776,
+4041:3777,
+4042:3778,
+4043:3779,
+4044:3780,
+4045:3781,
+4046:3782,
+4047:3783,
+4048:3784,
+4049:3785,
+4050:3786,
+4051:3787,
+4052:3788,
+4053:3789,
+4054:3790,
+4055:3791,
+4056:3792,
+4057:3793,
+4058:3794,
+4059:3795,
+4060:3796,
+4061:3797,
+4062:3798,
+4063:3799,
+4064:3800,
+4065:3801,
+4066:3802,
+4067:3803,
+4068:3804,
+4069:3805,
+4070:3806,
+4071:3807,
+4072:3808,
+4073:3809,
+4074:3810,
+4075:3811,
+4076:3812,
+4077:3813,
+4078:3814,
+4079:3815,
+4080:3816,
+4081:3817,
+4082:3818,
+4083:3819,
+4084:3820,
+4085:3821,
+4086:3822,
+4087:3823,
+4088:3824,
+4089:3825,
+4090:3826,
+4091:3827,
+4092:3828,
+4093:3829,
+4094:3830,
+4095:3831,
+4096:3832,
+4097:3833,
+4098:3834,
+4099:3835,
+4100:3836,
+4101:3837,
+4102:3838,
+4103:3839,
+4104:3840,
+4105:3841,
+4106:3842,
+4107:3843,
+4108:3844,
+4109:3845,
+4110:3846,
+4111:3847,
+4112:3848,
+4113:3849,
+4114:3850,
+4115:3851,
+4116:3852,
+4117:3853,
+4118:3854,
+4119:3855,
+4120:3856,
+4121:3857,
+4122:3858,
+4123:3859,
+4124:3860,
+4125:3861,
+4126:3862,
+4127:3863,
+4128:3864,
+4129:3865,
+4130:3866,
+4131:3867,
+4132:3868,
+4133:3869,
+4134:3870,
+4135:3871,
+4136:3872,
+4137:3873,
+4138:3874,
+4139:3875,
+4140:3876,
+4141:3877,
+4142:3878,
+4143:3879,
+4144:3880,
+4145:3881,
+4146:3882,
+4147:3883,
+4148:3884,
+4149:3885,
+4150:3886,
+4151:3887,
+4152:3888,
+4153:3889,
+4154:3890,
+4155:3891,
+4156:3892,
+4157:3893,
+4158:3894,
+4159:3895,
+4160:3896,
+4161:3897,
+4162:3898,
+4163:3899,
+4164:3900,
+4165:3901,
+4166:3902,
+4167:3903,
+4168:3904,
+4169:3905,
+4170:3906,
+4171:3907,
+4172:3908,
+4173:3909,
+4174:3910,
+4175:3911,
+4176:3912,
+4177:3913,
+4178:3914,
+4179:3915,
+4180:3916,
+4181:3917,
+4182:3918,
+4183:3919,
+4184:3920,
+4185:3921,
+4186:3922,
+4187:3923,
+4188:3924,
+4189:3925,
+4190:3926,
+4191:3927,
+4192:3928,
+4193:3929,
+4194:3930,
+4195:3931,
+4196:3932,
+4197:3933,
+4198:3934,
+4199:3935,
+4200:3936,
+4201:3937,
+4202:3938,
+4203:3939,
+4204:3940,
+4205:3941,
+4206:3942,
+4207:3943,
+4208:3944,
+4209:3945,
+4210:3946,
+4211:3947,
+4212:3948,
+4213:3949,
+4214:3950,
+4215:3951,
+4216:3952,
+4217:3953,
+4218:3954,
+4219:3955,
+4220:3956,
+4221:3957,
+4222:3958,
+4223:3959,
+4224:3960,
+4225:3961,
+4226:3962,
+4227:3963,
+4228:3964,
+4229:3965,
+4230:3966,
+4231:3967,
+4232:3968,
+4233:3969,
+4234:3970,
+4235:3971,
+4236:3972,
+4237:3973,
+4238:3974,
+4239:3975,
+4240:3976,
+4241:3977,
+4242:3978,
+4243:3979,
+4244:3980,
+4245:3981,
+4246:3982,
+4247:3983,
+4248:3984,
+4249:3985,
+4250:3986,
+4251:4239,
+4252:4310,
+4253:4318,
+4254:4319,
+4255:4320,
+4256:4321,
+4257:4322,
+4258:4323,
+4259:4324,
+4260:4325,
+4261:4326,
+4262:4327,
+4263:4328,
+4264:4329,
+4265:4330,
+4266:4331,
+4267:4332,
+4268:4333,
+4269:4334,
+4270:4335,
+4271:4336,
+4272:4337,
+4273:4338,
+4274:4339,
+4275:4340,
+4276:4341,
+4277:4342,
+4278:4343,
+4279:4344,
+4280:4345,
+4281:4346,
+4282:4347,
+4283:4348,
+4284:4349,
+4285:4350,
+4286:4351,
+4287:4352,
+4288:4353,
+4289:4354,
+4290:4355,
+4291:4356,
+4292:4357,
+4293:4358,
+4294:4359,
+4295:4360,
+4296:4361,
+4297:4362,
+4298:4363,
+4299:4364,
+4300:4365,
+4301:4366,
+4302:4367,
+4303:4368,
+4304:4369,
+4305:4370,
+4306:4371,
+4307:4372,
+4308:4373,
+4309:4374,
+4310:4375,
+4311:4376,
+4312:4377,
+4313:4378,
+4314:4379,
+4315:4380,
+4316:4381,
+4317:4382,
+4318:4383,
+4319:4384,
+4320:4385,
+4321:4386,
+4322:4387,
+4323:4388,
+4324:4389,
+4325:4390,
+4326:4391,
+4327:4392,
+4328:426,
+4329:427,
+4330:451,
+4331:465,
+4332:466,
+4333:467,
+4334:468,
+4335:471,
+4336:472,
+4337:473,
+4338:474,
+4339:614,
+4340:616,
+4341:617,
+4342:857,
+4343:858,
+4344:912,
+4345:913,
+4346:922,
+4347:1099,
+4348:1990,
+4349:1991,
+4350:2139,
+4351:2141,
+4352:2142,
+4353:2143,
+4354:2149,
+4355:2150,
+4356:2151,
+4357:2186,
+4358:2217,
+4359:2470,
+4360:2479,
+4361:2543,
+4362:2544,
+4363:2545,
+4364:2546,
+4365:2547,
+4366:2548,
+4367:2549,
+4368:2550,
+4369:2551,
+4370:2552,
+4371:2553,
+4372:2554,
+4373:2555,
+4374:2556,
+4375:2557,
+4376:2558,
+4377:2559,
+4378:2560,
+4379:2561,
+4380:2562,
+4381:2563,
+4382:2564,
+4383:2565,
+4384:2566,
+4385:2567,
+4386:2568,
+4387:2569,
+4388:2570,
+4389:2571,
+4390:2774,
+4391:3610,
+4392:3611,
+4393:3612,
+4394:4854,
+4395:4855,
+4396:4856,
+4397:4857,
+4398:4858,
+4399:4859,
+4400:4860,
+4401:4861,
+4402:4862,
+4403:1770,
+4404:2117,
+4405:4394,
+4406:4395,
+4407:4396,
+4408:4397,
+4409:4398,
+4410:4399,
+4411:4400,
+4412:4401,
+4413:4402,
+4414:4403,
+4415:4404,
+4416:4405,
+4417:4406,
+4418:4407,
+4419:4408,
+4420:4409,
+4421:4410,
+4422:0,
+4423:0,
+4424:0,
+4425:0,
+4426:0,
+4427:0,
+4428:0,
+4429:0,
+4430:0,
+4431:0,
+4432:0,
+4433:0,
+4434:0,
+4435:0,
+4436:0,
+4437:0,
+4438:0,
+4439:0,
+4440:0,
+4441:0,
+4442:0,
+4443:0,
+4444:0,
+4445:0,
+4446:0,
+4447:0,
+4448:0,
+4449:0,
+4450:0,
+4451:0,
+4452:0,
+4453:0,
+4454:0,
+4455:0,
+4456:4445,
+4457:4446,
+4458:4447,
+4459:4448,
+4460:4449,
+4461:4450,
+4462:4451,
+4463:4452,
+4464:4453,
+4465:4454,
+4466:4455,
+4467:4456,
+4468:4457,
+4469:4458,
+4470:4459,
+4471:4460,
+4472:4461,
+4473:4462,
+4474:4463,
+4475:4464,
+4476:4465,
+4477:4466,
+4478:4467,
+4479:4468,
+4480:4469,
+4481:4470,
+4482:4471,
+4483:4472,
+4484:4473,
+4485:4474,
+4486:4475,
+4487:4476,
+4488:4477,
+4489:4478,
+4490:4479,
+4491:4480,
+4492:4481,
+4493:4482,
+4494:4483,
+4495:4484,
+4496:4485,
+4497:4486,
+4498:4487,
+4499:4488,
+4500:4489,
+4501:4490,
+4502:4491,
+4503:4492,
+4504:4493,
+4505:4494,
+4506:4495,
+4507:4496,
+4508:4497,
+4509:4498,
+4510:4499,
+4511:4500,
+4512:4501,
+4513:4502,
+4514:4503,
+4515:4504,
+4516:4505,
+4517:4506,
+4518:4507,
+4519:4508,
+4520:4509,
+4521:4510,
+4522:4511,
+4523:4512,
+4524:4513,
+4525:4514,
+4526:4515,
+4527:4516,
+4528:4517,
+4529:4518,
+4530:4519,
+4531:4520,
+4532:4521,
+4533:4522,
+4534:4523,
+4535:4524,
+4536:4525,
+4537:4526,
+4538:4527,
+4539:4528,
+4540:4529,
+4541:4530,
+4542:4531,
+4543:4532,
+4544:4533,
+4545:4534,
+4546:4535,
+4547:4536,
+4548:4537,
+4549:4538,
+4550:4539,
+4551:4540,
+4552:4541,
+4553:4542,
+4554:4543,
+4555:4544,
+4556:4545,
+4557:4546,
+4558:4547,
+4559:4548,
+4560:4549,
+4561:4550,
+4562:4551,
+4563:4552,
+4564:4553,
+4565:4554,
+4566:4555,
+4567:0,
+4568:0,
+4569:0,
+4570:4559,
+4571:4560,
+4572:4561,
+4573:4562,
+4574:4563,
+4575:4564,
+4576:4565,
+4577:4566,
+4578:4567,
+4579:4568,
+4580:4569,
+4581:4570,
+4582:4571,
+4583:4572,
+4584:4573,
+4585:4574,
+4586:4575,
+4587:4576,
+4588:4577,
+4589:4578,
+4590:4579,
+4591:4580,
+4592:4581,
+4593:4582,
+4594:4583,
+4595:4584,
+4596:4585,
+4597:4586,
+4598:4587,
+4599:4588,
+4600:4589,
+4601:4590,
+4602:4591,
+4603:4592,
+4604:4593,
+4605:4594,
+4606:4595,
+4607:4596,
+4608:4597,
+4609:4598,
+4610:4599,
+4611:4600,
+4612:4601,
+4613:4602,
+4614:4609,
+4615:4610,
+4616:4611,
+4617:4612,
+4618:4613,
+4619:4614,
+4620:629,
+4621:630,
+4622:631,
+4623:632,
+4624:633,
+4625:634,
+4626:4393,
+4627:4428,
+4628:4615,
+4629:4616,
+4630:4617,
+4631:4618,
+4632:4621,
+4633:4622,
+4634:4623,
+4635:4624,
+4636:4625,
+4637:4626,
+4638:4627,
+4639:4628,
+4640:4629,
+4641:4630,
+4642:4631,
+4643:4632,
+4644:4633,
+4645:4634,
+4646:4635,
+4647:4636,
+4648:4637,
+4649:4638,
+4650:4639,
+4651:4640,
+4652:4641,
+4653:4642,
+4654:4643,
+4655:4644,
+4656:4645,
+4657:4646,
+4658:4647,
+4659:4648,
+4660:4649,
+4661:4650,
+4662:4651,
+4663:4652,
+4664:4653,
+4665:4654,
+4666:4655,
+4667:4656,
+4668:4657,
+4669:4658,
+4670:4659,
+4671:4660,
+4672:4661,
+4673:4662,
+4674:4663,
+4675:4664,
+4676:4665,
+4677:4666,
+4678:4667,
+4679:4668,
+4680:4669,
+4681:4670,
+4682:4671,
+4683:4672,
+4684:4673,
+4685:4674,
+4686:4675,
+4687:4676,
+4688:4677,
+4689:4678,
+4690:4679,
+4691:4680,
+4692:4681,
+4693:4682,
+4694:4683,
+4695:4684,
+4696:4685,
+4697:4686,
+4698:4687,
+4699:4688,
+4700:4689,
+4701:4690,
+4702:4691,
+4703:4692,
+4704:4693,
+4705:4694,
+4706:4695,
+4707:4696,
+4708:4697,
+4709:4698,
+4710:4699,
+4711:4700,
+4712:4701,
+4713:4702,
+4714:4703,
+4715:4704,
+4716:4705,
+4717:4706,
+4718:4707,
+4719:4708,
+4720:4709,
+4721:4710,
+4722:4711,
+4723:4712,
+4724:4713,
+4725:4714,
+4726:4715,
+4727:4716,
+4728:4717,
+4729:4718,
+4730:4719,
+4731:4720,
+4732:4721,
+4733:4722,
+4734:4723,
+4735:4724,
+4736:4725,
+4737:4726,
+4738:4727,
+4739:4728,
+4740:4729,
+4741:4730,
+4742:4731,
+4743:4732,
+4744:4733,
+4745:4734,
+4746:4735,
+4747:4736,
+4748:4737,
+4749:4738,
+4750:4739,
+4751:4740,
+4752:4741,
+4753:4742,
+4754:4743,
+4755:4744,
+4756:0,
+4757:0,
+4758:0,
+4759:0,
+4760:4749,
+4761:4750,
+4762:4751,
+4763:4752,
+4764:4753,
+4765:4754,
+4766:4755,
+4767:4756,
+4768:4757,
+4769:4758,
+4770:4759,
+4771:4760,
+4772:4761,
+4773:4762,
+4774:4763,
+4775:4764,
+4776:4765,
+4777:4766,
+4778:4767,
+4779:4768,
+4780:4769,
+4781:4770,
+4782:4771,
+4783:4772,
+4784:4773,
+4785:4774,
+4786:4775,
+4787:4776,
+4788:4777,
+4789:4778,
+4790:4779,
+4791:4780,
+4792:4781,
+4793:4782,
+4794:4783,
+4795:4784,
+4796:4785,
+4797:4786,
+4798:4787,
+4799:4788,
+4800:4789,
+4801:4790,
+4802:4791,
+4803:4792,
+4804:4793,
+4805:4794,
+4806:4795,
+4807:4796,
+4808:4797,
+4809:4798,
+4810:4799,
+4811:4800,
+4812:4801,
+4813:4802,
+4814:4803,
+4815:4804,
+4816:4805,
+4817:4806,
+4818:4807,
+4819:4808,
+4820:4809,
+4821:4810,
+4822:4811,
+4823:4812,
+4824:4813,
+4825:4814,
+4826:4815,
+4827:4816,
+4828:4817,
+4829:4818,
+4830:4819,
+4831:4820,
+4832:4821,
+4833:4822,
+4834:4823,
+4835:859,
+4836:4825,
+4837:4826,
+4838:4827,
+4839:4828,
+4840:4829,
+4841:4619,
+4842:348,
+4843:4832,
+4844:4620,
+4845:4834,
+4846:4835,
+4847:4836,
+4848:4837,
+4849:4838,
+4850:4839,
+4851:4840,
+4852:4841,
+4853:4842,
+4854:4843,
+4855:401,
+4856:4845,
+4857:173,
+4858:4847,
+4859:2525,
+4860:4849,
+4861:4850,
+4862:4851,
+4863:4852,
+4864:131,
+4865:4863,
+4866:4864,
+4867:4865,
+4868:4866,
+4869:4867,
+4870:4868,
+4871:4869,
+4872:4870,
+4873:4871,
+4874:3456,
+4875:5676,
+4876:4874,
+4877:4875,
+4878:4876,
+4879:4877,
+4880:4878,
+4881:4879,
+4882:4880,
+4883:4881,
+4884:4882,
+4885:4883,
+4886:4884,
+4887:4885,
+4888:4886,
+4889:4887,
+4890:4888,
+4891:4889,
+4892:4890,
+4893:4891,
+4894:4892,
+4895:4893,
+4896:4894,
+4897:4895,
+4898:4896,
+4899:4897,
+4900:4898,
+4901:4899,
+4902:4900,
+4903:4901,
+4904:4902,
+4905:4903,
+4906:4904,
+4907:4905,
+4908:4906,
+4909:4907,
+4910:4908,
+4911:4909,
+4912:4910,
+4913:4913,
+4914:5006,
+4915:4914,
+4916:4912,
+4917:5007,
+4918:4911,
+4919:4917,
+4920:4918,
+4921:4919,
+4922:4920,
+4923:4921,
+4924:4922,
+4925:4923,
+4926:4924,
+4927:4925,
+4928:4926,
+4929:4927,
+4930:4928,
+4931:4929,
+4932:4930,
+4933:4931,
+4934:4932,
+4935:4933,
+4936:4934,
+4937:4935,
+4938:4936,
+4939:4937,
+4940:4938,
+4941:4939,
+4942:4940,
+4943:4941,
+4944:4942,
+4945:4943,
+4946:4944,
+4947:4946,
+4948:4947,
+4949:4948,
+4950:4949,
+4951:4950,
+4952:4951,
+4953:4952,
+4954:4953,
+4955:4954,
+4956:4955,
+4957:4956,
+4958:4957,
+4959:4958,
+4960:4959,
+4961:4960,
+4962:4961,
+4963:4962,
+4964:4963,
+4965:4964,
+4966:4965,
+4967:4966,
+4968:4967,
+4969:4968,
+4970:4969,
+4971:4970,
+4972:4971,
+4973:4972,
+4974:4973,
+4975:4974,
+4976:4975,
+4977:4976,
+4978:4977,
+4979:4978,
+4980:4979,
+4981:4980,
+4982:4981,
+4983:4982,
+4984:4983,
+4985:4984,
+4986:4985,
+4987:4986,
+4988:4987,
+4989:4988,
+4990:4989,
+4991:4990,
+4992:4991,
+4993:4992,
+4994:4993,
+4995:4994,
+4996:4995,
+4997:4996,
+4998:4997,
+4999:4998,
+5000:4999,
+5001:5000,
+5002:5001,
+5003:5002,
+5004:5003,
+5005:5004,
+5006:5005,
+5007:4915,
+5008:4916,
+5009:5008,
+5010:5009,
+5011:5010,
+5012:5011,
+5013:5012,
+5014:5013,
+5015:5014,
+5016:5015,
+5017:5016,
+5018:5017,
+5019:5018,
+5020:5019,
+5021:5020,
+5022:5021,
+5023:5022,
+5024:5023,
+5025:2117,
+5026:2117,
+5027:2117,
+5028:2117,
+5029:2117,
+5030:2117,
+5031:2117,
+5032:2117,
+5033:2117,
+5034:2117,
+5035:2117,
+5036:2117,
+5037:2117,
+5038:2117,
+5039:2117,
+5040:2117,
+5041:2117,
+5042:2117,
+5043:2117,
+5044:2117,
+5045:5045,
+5046:5046,
+5047:5047,
+5048:5048,
+5049:5049,
+5050:5050,
+5051:5051,
+5052:5052,
+5053:5053,
+5054:5054,
+5055:5055,
+5056:5056,
+5057:5057,
+5058:5058,
+5059:5059,
+5060:5060,
+5061:5061,
+5062:5062,
+5063:5063,
+5064:5064,
+5065:232,
+5066:5066,
+5067:5067,
+5068:5068,
+5069:5069,
+5070:5070,
+5071:5071,
+5072:5072,
+5073:5073,
+5074:5074,
+5075:5075,
+5076:5076,
+5077:5077,
+5078:5078,
+5079:5079,
+5080:5080,
+5081:5081,
+5082:5082,
+5083:5083,
+5084:5084,
+5085:5085,
+5086:5086,
+5087:5087,
+5088:5088,
+5089:5089,
+5090:4830,
+5091:5090,
+5092:5091,
+5093:5092,
+5094:5093,
+5095:5094,
+5096:5095,
+5097:5096,
+5098:5097,
+5099:5098,
+5100:5099,
+5101:5100,
+5102:5101,
+5103:5102,
+5104:5103,
+5105:5104,
+5106:5105,
+5107:5106,
+5108:5107,
+5109:5108,
+5110:5109,
+5111:5110,
+5112:5111,
+5113:5112,
+5114:5113,
+5115:5114,
+5116:5115,
+5117:5116,
+5118:5117,
+5119:5118,
+5120:5119,
+5121:5120,
+5122:5121,
+5123:5122,
+5124:5123,
+5125:5124,
+5126:5125,
+5127:5126,
+5128:5127,
+5129:5128,
+5130:5129,
+5131:5130,
+5132:5131,
+5133:5132,
+5134:5133,
+5135:5134,
+5136:5135,
+5137:5136,
+5138:5137,
+5139:5138,
+5140:5139,
+5141:5140,
+5142:5141,
+5143:5142,
+5144:5143,
+5145:5144,
+5146:5145,
+5147:5146,
+5148:5147,
+5149:5148,
+5150:5149,
+5151:5150,
+5152:5151,
+5153:5152,
+5154:5153,
+5155:5154,
+5156:5155,
+5157:5156,
+5158:5157,
+5159:5158,
+5160:5159,
+5161:5160,
+5162:5161,
+5163:5162,
+5164:5163,
+5165:5164,
+5166:5165,
+5167:5166,
+5168:5167,
+5169:5168,
+5170:5169,
+5171:5170,
+5172:5171,
+5173:5172,
+5174:5173,
+5175:5174,
+5176:5175,
+5177:5176,
+5178:5177,
+5179:5178,
+5180:5179,
+5181:5180,
+5182:5181,
+5183:5182,
+5184:5183,
+5185:5184,
+5186:5185,
+5187:5186,
+5188:5187,
+5189:5188,
+5190:5189,
+5191:5190,
+5192:5191,
+5193:5192,
+5194:5193,
+5195:5194,
+5196:5195,
+5197:5196,
+5198:5197,
+5199:5198,
+5200:5199,
+5201:5200,
+5202:5201,
+5203:5202,
+5204:5203,
+5205:5204,
+5206:5205,
+5207:5206,
+5208:5207,
+5209:5208,
+5210:5209,
+5211:5210,
+5212:5211,
+5213:5212,
+5214:5213,
+5215:5214,
+5216:5215,
+5217:5216,
+5218:5217,
+5219:5218,
+5220:5219,
+5221:5220,
+5222:5221,
+5223:5222,
+5224:5223,
+5225:5224,
+5226:5225,
+5227:5226,
+5228:5227,
+5229:5228,
+5230:5229,
+5231:5230,
+5232:5231,
+5233:5232,
+5234:5233,
+5235:5234,
+5236:5235,
+5237:5236,
+5238:5237,
+5239:5238,
+5240:5239,
+5241:5240,
+5242:5241,
+5243:5242,
+5244:5243,
+5245:5244,
+5246:5245,
+5247:5246,
+5248:5247,
+5249:5248,
+5250:5249,
+5251:5250,
+5252:5251,
+5253:5252,
+5254:5253,
+5255:5254,
+5256:5255,
+5257:5256,
+5258:5257,
+5259:5258,
+5260:5259,
+5261:5260,
+5262:5261,
+5263:5262,
+5264:5263,
+5265:5264,
+5266:5265,
+5267:5266,
+5268:5267,
+5269:5268,
+5270:5269,
+5271:5270,
+5272:5271,
+5273:5272,
+5274:5273,
+5275:5274,
+5276:5275,
+5277:5276,
+5278:5277,
+5279:5278,
+5280:5279,
+5281:5280,
+5282:5281,
+5283:5282,
+5284:5283,
+5285:5284,
+5286:5285,
+5287:5286,
+5288:5287,
+5289:5288,
+5290:5289,
+5291:5290,
+5292:5291,
+5293:5292,
+5294:5293,
+5295:5294,
+5296:5295,
+5297:5296,
+5298:5297,
+5299:5298,
+5300:5299,
+5301:5300,
+5302:5301,
+5303:5302,
+5304:5303,
+5305:5304,
+5306:5305,
+5307:5306,
+5308:5307,
+5309:5308,
+5310:5309,
+5311:5310,
+5312:5311,
+5313:5312,
+5314:5313,
+5315:5314,
+5316:5315,
+5317:5316,
+5318:5317,
+5319:5318,
+5320:5319,
+5321:5320,
+5322:5321,
+5323:5322,
+5324:5323,
+5325:5324,
+5326:5325,
+5327:5326,
+5328:5327,
+5329:5328,
+5330:5329,
+5331:5330,
+5332:5331,
+5333:5332,
+5334:5333,
+5335:5334,
+5336:5335,
+5337:5336,
+5338:5337,
+5339:5338,
+5340:5339,
+5341:5340,
+5342:5341,
+5343:5342,
+5344:5343,
+5345:5344,
+5346:5345,
+5347:5346,
+5348:5347,
+5349:5348,
+5350:5349,
+5351:5350,
+5352:5351,
+5353:5352,
+5354:5353,
+5355:5354,
+5356:5355,
+5357:5356,
+5358:5357,
+5359:5358,
+5360:5359,
+5361:5360,
+5362:5361,
+5363:5362,
+5364:5363,
+5365:5364,
+5366:5365,
+5367:5366,
+5368:5367,
+5369:5368,
+5370:5369,
+5371:5370,
+5372:5371,
+5373:5372,
+5374:5373,
+5375:5374,
+5376:5375,
+5377:5376,
+5378:5377,
+5379:5378,
+5380:5379,
+5381:5380,
+5382:5381,
+5383:5382,
+5384:5383,
+5385:5384,
+5386:5385,
+5387:5386,
+5388:5387,
+5389:5388,
+5390:5389,
+5391:5390,
+5392:5391,
+5393:5392,
+5394:5393,
+5395:5394,
+5396:5395,
+5397:5396,
+5398:5397,
+5399:5398,
+5400:5399,
+5401:5400,
+5402:2117,
+5403:2117,
+5404:2117,
+5405:0000,
+5406:5405,
+5407:5406,
+5408:5407,
+5409:5408,
+5410:5409,
+5411:5410,
+5412:5411,
+5413:5412,
+5414:5413,
+5415:5414,
+5416:5415,
+5417:5416,
+5418:5417,
+5419:5418,
+5420:5419,
+5421:5420,
+5422:5421,
+5423:5422,
+5424:5423,
+5425:5424,
+5426:5425,
+5427:5426,
+5428:5427,
+5429:5428,
+5430:5429,
+5431:5430,
+5432:5431,
+5433:5432,
+5434:5433,
+5435:5434,
+5436:5435,
+5437:5436,
+5438:5437,
+5439:5438,
+5440:5439,
+5441:5440,
+5442:5441,
+5443:5442,
+5444:5443,
+5445:5444,
+5446:5445,
+5447:5446,
+5448:5447,
+5449:5448,
+5450:5449,
+5451:5450,
+5452:5451,
+5453:5452,
+5454:5453,
+5455:5454,
+5456:5455,
+5457:5456,
+5458:5457,
+5459:5458,
+5460:5459,
+5461:5460,
+5462:5461,
+5463:5462,
+5464:5463,
+5465:5464,
+5466:5465,
+5467:5466,
+5468:5467,
+5469:5468,
+5470:5469,
+5471:5470,
+5472:5471,
+5473:5472,
+5474:5473,
+5475:5474,
+5476:5475,
+5477:5476,
+5478:5477,
+5479:5478,
+5480:5479,
+5481:5480,
+5482:5481,
+5483:5482,
+5484:5483,
+5485:5484,
+5486:5485,
+5487:5486,
+5488:5487,
+5489:5488,
+5490:5489,
+5491:5490,
+5492:5491,
+5493:5492,
+5494:5493,
+5495:5494,
+5496:5495,
+5497:5496,
+5498:5497,
+5499:767,
+5500:5499,
+5501:5500,
+5502:5501,
+5503:5502,
+5504:5503,
+5505:5504,
+5506:5505,
+5507:5506,
+5508:5507,
+5509:5508,
+5510:5509,
+5511:5510,
+5512:5511,
+5513:5512,
+5514:5513,
+5515:5514,
+5516:5515,
+5517:5516,
+5518:5517,
+5519:5518,
+5520:5519,
+5521:5520,
+5522:5521,
+5523:5522,
+5524:5523,
+5525:5524,
+5526:5525,
+5527:5526,
+5528:5527,
+5529:5528,
+5530:5529,
+5531:5530,
+5532:5531,
+5533:5532,
+5534:5533,
+5535:5534,
+5536:5535,
+5537:5536,
+5538:5537,
+5539:4848,
+5540:5539,
+5541:5540,
+5542:5541,
+5543:5542,
+5544:5543,
+5545:5544,
+5546:5545,
+5547:5546,
+5548:5547,
+5549:5548,
+5550:5549,
+5551:5550,
+5552:5551,
+5553:5552,
+5554:5553,
+5555:5554,
+5556:5555,
+5557:5556,
+5558:5557,
+5559:5558,
+5560:5559,
+5561:5560,
+5562:5561,
+5563:5562,
+5564:5563,
+5565:5564,
+5566:5565,
+5567:5566,
+5568:5567,
+5569:5568,
+5570:5569,
+5571:5570,
+5572:5571,
+5573:5572,
+5574:5573,
+5575:5574,
+5576:5575,
+5577:5576,
+5578:5577,
+5579:5578,
+5580:5579,
+5581:5580,
+5582:5581,
+5583:5582,
+5584:5583,
+5585:5584,
+5586:5585,
+5587:5586,
+5588:5587,
+5589:5588,
+5590:5589,
+5591:5590,
+5592:5591,
+5593:5592,
+5594:5593,
+5595:5594,
+5596:5595,
+5597:5596,
+5598:5597,
+5599:5598,
+5600:5599,
+5601:5600,
+5602:5601,
+5603:5602,
+5604:5603,
+5605:5604,
+5606:5605,
+5607:5606,
+5608:5607,
+5609:5608,
+5610:5609,
+5611:5610,
+5612:5611,
+5613:5612,
+5614:5613,
+5615:5614,
+5616:5615,
+5617:5616,
+5618:5617,
+5619:5618,
+5620:5619,
+5621:5620,
+5622:5621,
+5623:5622,
+5624:5623,
+5625:5624,
+5626:5625,
+5627:5626,
+5628:5627,
+5629:5628,
+5630:5629,
+5631:5630,
+5632:5631,
+5633:5632,
+5634:5633,
+5635:5634,
+5636:5635,
+5637:5636,
+5638:5637,
+5639:5638,
+5640:5639,
+5641:5640,
+5642:5641,
+5643:5642,
+5644:5643,
+5645:5644,
+5646:5645,
+5647:5646,
+5648:5647,
+5649:5648,
+5650:5649,
+5651:5650,
+5652:5651,
+5653:5652,
+5654:5653,
+5655:5654,
+5656:5655,
+5657:5656,
+5658:5657,
+5659:5658,
+5660:5659,
+5661:5660,
+5662:5661,
+5663:5662,
+5664:5663,
+5665:5664,
+5666:5665,
+5667:5666,
+5668:5667,
+5669:5668,
+5670:5669,
+5671:5670,
+5672:5671,
+5673:5672,
+5674:5673,
+5675:5674,
+5676:5675,
+5677:5677,
+5678:5678,
+5679:5679,
+5680:5680,
+5681:5681,
+5682:5682,
+5683:5683,
+5684:5684,
+5685:5685,
+5686:5686,
+5687:5687,
+5688:5688,
+5689:5689,
+5690:5690,
+5691:5691,
+5692:5692,
+5693:5693,
+5694:5694,
+5695:5695,
+5696:5696,
+5697:5697,
+5698:5698,
+5699:5699,
+5700:5700,
+5701:5701,
+5702:5702,
+5703:5703,
+5704:5704,
+5705:5705,
+5706:5706,
+5707:5707,
+5708:5708,
+5709:5709,
+5710:5710,
+5711:5711,
+5712:5712,
+5713:5713,
+5714:5714,
+5715:5715,
+5716:5716,
+5717:5717,
+5718:5718,
+5719:5719,
+5720:5720,
+5721:5721,
+5722:5722,
+5723:5723,
+5724:5724,
+5725:5725,
+5726:5726,
+5727:5727,
+5728:5728,
+5729:5729,
+5730:4833,
+5731:5731,
+5732:5732,
+5733:5733,
+5734:5734,
+5735:5735,
+5736:5736,
+5737:5737,
+5738:5738,
+5739:5739,
+5740:5740,
+5741:5741,
+5742:4873,
+5743:5743,
+5744:5744,
+5745:5745,
+5746:5746,
+5747:5747,
+5748:5749,
+5749:5748,
+5750:5750,
+5751:5751,
+5752:5752,
+5753:5753,
+5754:5754,
+5755:5755,
+5756:5756,
+5757:5757,
+5758:5758,
+5759:5759,
+5760:5760,
+5761:5761,
+5762:5762,
+5763:5763,
+5764:5764,
+5765:5765,
+5766:5766,
+5767:5767,
+5768:5768,
+5769:5769,
+5770:5770,
+5771:5771,
+5772:5772,
+5773:5773,
+5774:5774,
+5775:5730,
+5776:5742,
+5777:5777,
+5778:5778,
+5779:5779,
+5780:5780,
+5781:5781,
+5782:5782,
+5783:5783,
+5784:5784,
+5785:5785,
+5786:5786,
+5787:5787,
+5788:5788,
+5789:5789,
+5790:5790,
+5791:5791,
+5792:5792,
+5793:5793,
+5794:5794,
+5795:5795,
+5796:5796,
+5797:5797,
+5798:0000,
+5799:5799,
+5800:5800,
+5801:5801,
+5802:5802,
+5803:5803,
+5804:5804,
+5805:5805,
+5806:5806,
+5807:5807,
+5808:5808,
+5809:5809,
+5810:5810,
+5811:5811,
+5812:5812,
+5813:5813,
+5814:5814,
+5815:5815,
+5816:5816,
+5817:5817,
+5818:5818,
+5819:5819,
+5820:5820,
+5821:5821,
+5822:5822,
+5823:5823,
+5824:5824,
+5825:5825,
+5826:5826,
+5827:5827,
+5828:5828,
+5829:5829,
+5830:5830,
+5831:5831,
+5832:5832,
+5833:5833,
+5834:5834,
+5835:5835,
+5836:5836,
+5837:5837,
+5838:5838,
+5839:5839,
+5840:5840,
+5841:5841,
+5842:5842,
+5843:5843,
+5844:5844,
+5845:5845,
+5846:5846,
+5847:5847,
+5848:5848,
+5849:5849,
+5850:5850,
+5851:5851,
+5852:5852,
+5853:5853,
+5854:5854,
+5855:5855,
+5856:5856,
+5857:5857,
+5858:5858,
+5859:5859,
+5860:5860,
+5861:5861,
+5862:5862,
+5863:5863,
+5864:5775,
+5865:5865,
+5866:5866,
+5867:5867,
+5868:5868,
+5869:5869,
+5870:5870,
+5871:5871,
+5872:5872,
+5873:5873,
+5874:5874,
+5875:5875,
+5876:5876,
+5877:5877,
+5878:5878,
+5879:5879,
+5880:5880,
+5881:5881,
+5882:5882,
+5883:5883,
+5884:4853,
+5885:5885,
+5886:5886,
+5887:5887,
+5888:5888,
+5889:5889,
+5890:5890,
+5891:5891,
+5892:5892,
+5893:5893,
+5894:5894,
+5895:5895,
+5896:5896,
+5897:5897,
+5898:5898,
+5899:5899,
+5900:5900,
+5901:5901,
+5902:5902,
+5903:5903,
+5904:5904,
+5905:5905,
+5906:5906,
+5907:5907,
+5908:5908,
+5909:5909,
+5910:5910,
+5911:5911,
+5912:5912,
+5913:5913,
+5914:5914,
+5915:5915,
+5916:5916,
+5917:5917,
+5918:5918,
+5919:0000,
+5920:5920,
+5921:5921,
+5922:5922,
+5923:5776,
+5924:3351,
+5925:5925,
+5926:5926,
+5927:5927,
+5928:5928,
+5929:5929,
+5930:5930,
+5931:5931,
+5932:5932,
+5933:5933,
+5934:5934,
+5935:5935,
+5936:5936,
+5937:5937,
+5938:5938,
+5939:7246,
+5940:5940,
+5941:5941,
+5942:5942,
+5943:5943,
+5944:5944,
+5945:5946,
+5946:5946,
+5947:5947,
+5948:5948,
+5949:5949,
+5950:5950,
+5951:5951,
+5952:347,
+5953:5953,
+5954:5954,
+5955:5864,
+5956:4831,
+5957:5952,
+5958:5958,
+5959:5923,
+5960:5960,
+5961:5961,
+5962:5962,
+5963:5963,
+5964:5964,
+5965:5965,
+5966:5966,
+5967:5967,
+5968:5968,
+5969:5969,
+5970:5970,
+5971:5971,
+5972:5972,
+5973:5973,
+5974:5974,
+5975:5975,
+5976:5976,
+5977:5977,
+5978:5978,
+5979:5979,
+5980:5980,
+5981:5981,
+5982:5982,
+5983:5983,
+5984:5984,
+5985:5985,
+5986:5986,
+5987:5987,
+5988:5988,
+5989:5989,
+5990:5990,
+5991:5991,
+5992:5992,
+5993:5993,
+5994:5994,
+5995:5995,
+5996:5996,
+5997:5997,
+5998:5998,
+5999:5999,
+6000:6000,
+6001:6001,
+6002:6002,
+6003:6003,
+6004:6004,
+6005:6005,
+6006:6006,
+6007:6007,
+6008:6008,
+6009:6009,
+6010:6010,
+6011:6011,
+6012:6012,
+6013:6013,
+6014:6014,
+6015:6015,
+6016:6016,
+6017:6017,
+6018:6018,
+6019:6019,
+6020:6020,
+6021:6021,
+6022:6022,
+6023:6023,
+6024:6024,
+6025:6025,
+6026:6026,
+6027:6027,
+6028:6028,
+6029:6029,
+6030:6030,
+6031:6031,
+6032:6032,
+6033:6033,
+6034:6034,
+6035:6035,
+6036:6036,
+6037:6037,
+6038:6038,
+6039:6039,
+6040:6040,
+6041:6041,
+6042:6042,
+6043:6043,
+6044:6044,
+6045:6045,
+6046:6046,
+6047:6047,
+6048:6048,
+6049:6049,
+6050:6050,
+6051:6051,
+6052:6052,
+6053:6053,
+6054:6054,
+6055:6055,
+6056:6056,
+6057:6057,
+6058:6058,
+6059:6059,
+6060:6060,
+6061:6061,
+6062:6062,
+6063:6063,
+6064:6064,
+6065:6065,
+6066:6066,
+6067:6067,
+6068:6068,
+6069:6069,
+6070:6070,
+6071:6071,
+6072:6072,
+6073:6073,
+6074:6074,
+6075:6075,
+6076:6076,
+6077:6077,
+6078:6078,
+6079:6079,
+6080:111,
+6081:6081,
+6082:6082,
+6083:6083,
+6084:6084,
+6085:6085,
+6086:6086,
+6087:6087,
+6088:6088,
+6089:6089,
+6090:6090,
+6091:2906,
+6092:6091,
+6093:6093,
+6094:6094,
+6095:6095,
+6096:6096,
+6097:6097,
+6098:6098,
+6099:6096,
+6100:6095,
+6101:3273,
+6102:6102,
+6103:6103,
+6104:6104,
+6105:2393,
+6106:6106,
+6107:6107,
+6108:4844,
+6109:6109,
+6110:6110,
+6111:6111,
+6112:6112,
+6113:3220,
+6114:6114,
+6115:6115,
+6116:6116,
+6117:6117,
+6118:6118,
+6119:5957,
+6120:3059,
+6121:6121,
+6122:6122,
+6123:6123,
+6124:6124,
+6125:6125,
+6126:6126,
+6127:6127,
+6128:6128,
+6129:6129,
+6130:6130,
+6131:6131,
+6132:6529,
+6133:6132,
+6134:6133,
+6135:6134,
+6136:6135,
+6137:6136,
+6138:6137,
+6139:6138,
+6140:6139,
+6141:6140,
+6142:6141,
+6143:6142,
+6144:6143,
+6145:6144,
+6146:6145,
+6147:6146,
+6148:6147,
+6149:6148,
+6150:6149,
+6151:6150,
+6152:6151,
+6153:6152,
+6154:6153,
+6155:6154,
+6156:6155,
+6157:6156,
+6158:6157,
+6159:6158,
+6160:6159,
+6161:6160,
+6162:6161,
+6163:6162,
+6164:6163,
+6165:6164,
+6166:6165,
+6167:6166,
+6168:6167,
+6169:6168,
+6170:6169,
+6171:6170,
+6172:6171,
+6173:6172,
+6174:6173,
+6175:6174,
+6176:6175,
+6177:6176,
+6178:6177,
+6179:6178,
+6180:6179,
+6181:6180,
+6182:6181,
+6183:6182,
+6184:6183,
+6185:6184,
+6186:6185,
+6187:6186,
+6188:6187,
+6189:6188,
+6190:6189,
+6191:6190,
+6192:6191,
+6193:6192,
+6194:6193,
+6195:6194,
+6196:6195,
+6197:6196,
+6198:6197,
+6199:6198,
+6200:6199,
+6201:6200,
+6202:6201,
+6203:6202,
+6204:6203,
+6205:6204,
+6206:6205,
+6207:6206,
+6208:6207,
+6209:6208,
+6210:6209,
+6211:6210,
+6212:6211,
+6213:6212,
+6214:6213,
+6215:6214,
+6216:6215,
+6217:6216,
+6218:6217,
+6219:6218,
+6220:6219,
+6221:6220,
+6222:6221,
+6223:6222,
+6224:6223,
+6225:6224,
+6226:6225,
+6227:6226,
+6228:6227,
+6229:6228,
+6230:6229,
+6231:6230,
+6232:6231,
+6233:6232,
+6234:6233,
+6235:6234,
+6236:6235,
+6237:6236,
+6238:6237,
+6239:6238,
+6240:6239,
+6241:6240,
+6242:6241,
+6243:6242,
+6244:6243,
+6245:6244,
+6246:6245,
+6247:6246,
+6248:6247,
+6249:6248,
+6250:6249,
+6251:6250,
+6252:6251,
+6253:6252,
+6254:6253,
+6255:6254,
+6256:6255,
+6257:6256,
+6258:6257,
+6259:6258,
+6260:6259,
+6261:6260,
+6262:6261,
+6263:6262,
+6264:6263,
+6265:6264,
+6266:6265,
+6267:6266,
+6268:6267,
+6269:6268,
+6270:6269,
+6271:6270,
+6272:6271,
+6273:6272,
+6274:6273,
+6275:6274,
+6276:6275,
+6277:6276,
+6278:6277,
+6279:6278,
+6280:6279,
+6281:6280,
+6282:6281,
+6283:6282,
+6284:6283,
+6285:6284,
+6286:6285,
+6287:5955,
+6288:5959,
+6289:6288,
+6290:6289,
+6291:6290,
+6292:6291,
+6293:6292,
+6294:6293,
+6295:6294,
+6296:6295,
+6297:6296,
+6298:6297,
+6299:6298,
+6300:6299,
+6301:6300,
+6302:6301,
+6303:6302,
+6304:6303,
+6305:6304,
+6306:6305,
+6307:6306,
+6308:6307,
+6309:6308,
+6310:6309,
+6311:6310,
+6312:6311,
+6313:6312,
+6314:6313,
+6315:6314,
+6316:6315,
+6317:6316,
+6318:6317,
+6319:6318,
+6320:6319,
+6321:6320,
+6322:6321,
+6323:6322,
+6324:6323,
+6325:6324,
+6326:6325,
+6327:6326,
+6328:6327,
+6329:6328,
+6330:6329,
+6331:6330,
+6332:6331,
+6333:6332,
+6334:6333,
+6335:6334,
+6336:6335,
+6337:6336,
+6338:6337,
+6339:6338,
+6340:6339,
+6341:6340,
+6342:6341,
+6343:6342,
+6344:6343,
+6345:6344,
+6346:6345,
+6347:6346,
+6348:6347,
+6349:6348,
+6350:6349,
+6351:6350,
+6352:6286,
+6353:6352,
+6354:6353,
+6355:6354,
+6356:6355,
+6357:6356,
+6358:6357,
+6359:6358,
+6360:6359,
+6361:6360,
+6362:6361,
+6363:6362,
+6364:6363,
+6365:6364,
+6366:6365,
+6367:6366,
+6368:6367,
+6369:6368,
+6370:6369,
+6371:6370,
+6372:6371,
+6373:6372,
+6374:6373,
+6375:6374,
+6376:6375,
+6377:6376,
+6378:6377,
+6379:6378,
+6380:6379,
+6381:6380,
+6382:6381,
+6383:6382,
+6384:6383,
+6385:6384,
+6386:6385,
+6387:6386,
+6388:6387,
+6389:6388,
+6390:6389,
+6391:6390,
+6392:6391,
+6393:6392,
+6394:904,
+6395:6394,
+6396:6395,
+6397:6396,
+6398:6397,
+6399:6398,
+6400:6399,
+6401:6400,
+6402:6401,
+6403:6402,
+6404:6403,
+6405:6404,
+6406:6405,
+6407:6406,
+6408:6407,
+6409:6408,
+6410:6409,
+6411:6410,
+6412:6411,
+6413:6412,
+6414:6413,
+6415:6414,
+6416:6415,
+6417:6416,
+6418:6417,
+6419:6418,
+6420:6419,
+6421:6420,
+6422:6421,
+6423:6422,
+6424:6423,
+6425:6424,
+6426:6425,
+6427:6426,
+6428:6427,
+6429:6428,
+6430:6429,
+6431:6430,
+6432:6431,
+6433:6432,
+6434:6433,
+6435:6434,
+6436:6435,
+6437:6436,
+6438:6437,
+6439:6438,
+6440:6439,
+6441:6440,
+6442:6441,
+6443:6442,
+6444:6443,
+6445:6444,
+6446:6445,
+6447:6446,
+6448:6447,
+6449:6448,
+6450:6449,
+6451:6450,
+6452:6451,
+6453:6452,
+6454:6453,
+6455:6454,
+6456:6455,
+6457:6456,
+6458:6457,
+6459:6458,
+6460:6459,
+6461:6460,
+6462:6461,
+6463:6462,
+6464:6463,
+6465:6464,
+6466:6465,
+6467:6466,
+6468:6467,
+6469:6468,
+6470:6469,
+6471:6470,
+6472:6471,
+6473:6472,
+6474:6473,
+6475:6474,
+6476:6475,
+6477:6476,
+6478:6477,
+6479:6478,
+6480:6479,
+6481:6480,
+6482:6481,
+6483:6482,
+6484:6483,
+6485:6484,
+6486:6485,
+6487:6486,
+6488:6487,
+6489:6488,
+6490:6489,
+6491:6490,
+6492:6491,
+6493:6492,
+6494:6493,
+6495:6494,
+6496:6495,
+6497:6496,
+6498:6497,
+6499:6498,
+6500:6499,
+6501:6500,
+6502:6501,
+6503:6502,
+6504:6503,
+6505:6504,
+6506:6505,
+6507:6506,
+6508:6507,
+6509:6508,
+6510:6509,
+6511:6510,
+6512:6511,
+6513:6512,
+6514:6513,
+6515:6514,
+6516:6515,
+6517:6516,
+6518:6517,
+6519:6518,
+6520:6519,
+6521:6520,
+6522:6521,
+6523:174,
+6524:6523,
+6525:6524,
+6526:6525,
+6527:6526,
+6528:6527,
+6529:6528,
+6530:3123,
+6531:6531,
+6532:6532,
+6533:6533,
+6534:6534,
+6535:6535,
+6536:6536,
+6537:6537,
+6538:6538,
+6539:6539,
+6540:6540,
+6541:6541,
+6542:6542,
+6543:6543,
+6544:6544,
+6545:6545,
+6546:6546,
+6547:6547,
+6548:6548,
+6549:6549,
+6550:6550,
+6551:6551,
+6552:6552,
+6553:6553,
+6554:6287,
+6555:6351,
+6556:6556,
+6557:6557,
+6558:6558,
+6559:6554,
+6560:6560,
+6561:6561,
+6562:6562,
+6563:6563,
+6564:6564,
+6565:6565,
+6566:6566,
+6567:6567,
+6568:6568,
+6569:6569,
+6570:6570,
+6571:6571,
+6572:6572,
+6573:6573,
+6574:6574,
+6575:6575,
+6576:6576,
+6577:6577,
+6578:6578,
+6579:6579,
+6580:6580,
+6581:6581,
+6582:6582,
+6583:6583,
+6584:6584,
+6585:6585,
+6586:6586,
+6587:6587,
+6588:6588,
+6589:6589,
+6590:6590,
+6591:6591,
+6592:6592,
+6593:6593,
+6594:6594,
+6595:6595,
+6596:6596,
+6597:6597,
+6598:6598,
+6599:6599,
+6600:6600,
+6601:6601,
+6602:6602,
+6603:6603,
+6604:6604,
+6605:6605,
+6606:6606,
+6607:6607,
+6608:6608,
+6609:6609,
+6610:6610,
+6611:6611,
+6612:6612,
+6613:6613,
+6614:6614,
+6615:6615,
+6616:6616,
+6617:6617,
+6618:6618,
+6619:6619,
+6620:6620,
+6621:6621,
+6622:6622,
+6623:6623,
+6624:6624,
+6625:6625,
+6626:6626,
+6627:6627,
+6628:6628,
+6629:6629,
+6630:6630,
+6631:6631,
+6632:6632,
+6633:6633,
+6634:6634,
+6635:6635,
+6636:6636,
+6637:6637,
+6638:6638,
+6639:6639,
+6640:6640,
+6641:6641,
+6642:6642,
+6643:6643,
+6644:6644,
+6645:6645,
+6646:6646,
+6647:6647,
+6648:6648,
+6649:6649,
+6650:6650,
+6651:6651,
+6652:6652,
+6653:6653,
+6654:6654,
+6655:6655,
+6656:6656,
+6657:6657,
+6658:6658,
+6659:6659,
+6660:6660,
+6661:6661,
+6662:6662,
+6663:6663,
+6664:6664,
+6665:6665,
+6666:6666,
+6667:6667,
+6668:6668,
+6669:6669,
+6670:6670,
+6671:6671,
+6672:6672,
+6673:6673,
+6674:6674,
+6675:6675,
+6676:6676,
+6677:6677,
+6678:6678,
+6679:6679,
+6680:6680,
+6681:6681,
+6682:6682,
+6683:6683,
+6684:6684,
+6685:6685,
+6686:6686,
+6687:6687,
+6688:6688,
+6689:6689,
+6690:6690,
+6691:6691,
+6692:6692,
+6693:6693,
+6694:6694,
+6695:6695,
+6696:6696,
+6697:6697,
+6698:6698,
+6699:6699,
+6700:6700,
+6701:6701,
+6702:6702,
+6703:6703,
+6704:6704,
+6705:6705,
+6706:6706,
+6707:6707,
+6708:6708,
+6709:6709,
+6710:6710,
+6711:6711,
+6712:6712,
+6713:6713,
+6714:6714,
+6715:6715,
+6716:6716,
+6717:6717,
+6718:6718,
+6719:6719,
+6720:6720,
+6721:6721,
+6722:6722,
+6723:6723,
+6724:6724,
+6725:6725,
+6726:6726,
+6727:6727,
+6728:6728,
+6729:6729,
+6730:6730,
+6731:6731,
+6732:6732,
+6733:6733,
+6734:6734,
+6735:6735,
+6736:6736,
+6737:6737,
+6738:6738,
+6739:6739,
+6740:6740,
+6741:6741,
+6742:6742,
+6743:6743,
+6744:6744,
+6745:6745,
+6746:6746,
+6747:6747,
+6748:6748,
+6749:6749,
+6750:6750,
+6751:6751,
+6752:6752,
+6753:6753,
+6754:6754,
+6755:6755,
+6756:6756,
+6757:6757,
+6758:6758,
+6759:6759,
+6760:6760,
+6761:6761,
+6762:6762,
+6763:6763,
+6764:6764,
+6765:6765,
+6766:6766,
+6767:6767,
+6768:6768,
+6769:6769,
+6770:6770,
+6771:6771,
+6772:6772,
+6773:6773,
+6774:6774,
+6775:6775,
+6776:6776,
+6777:6777,
+6778:6778,
+6779:6779,
+6780:6780,
+6781:6781,
+6782:6782,
+6783:6783,
+6784:6784,
+6785:6785,
+6786:6786,
+6787:6787,
+6788:6788,
+6789:6789,
+6790:6790,
+6791:6791,
+6792:6792,
+6793:6793,
+6794:6794,
+6795:6795,
+6796:6796,
+6797:6797,
+6798:6798,
+6799:6799,
+6800:6800,
+6801:6801,
+6802:6802,
+6803:6803,
+6804:6804,
+6805:6805,
+6806:6806,
+6807:6807,
+6808:6808,
+6809:6809,
+6810:6810,
+6811:6811,
+6812:6812,
+6813:6813,
+6814:6814,
+6815:6815,
+6816:6816,
+6817:6817,
+6818:6818,
+6819:6819,
+6820:6820,
+6821:6821,
+6822:6822,
+6823:6823,
+6824:6824,
+6825:6825,
+6826:6826,
+6827:6827,
+6828:6828,
+6829:6829,
+6830:6830,
+6831:6831,
+6832:6832,
+6833:6833,
+6834:6834,
+6835:6835,
+6836:6836,
+6837:6837,
+6838:6838,
+6839:6839,
+6840:6840,
+6841:6841,
+6842:6842,
+6843:6843,
+6844:6844,
+6845:6845,
+6846:6846,
+6847:6847,
+6848:6848,
+6849:6849,
+6850:6850,
+6851:6851,
+6852:6852,
+6853:6853,
+6854:6854,
+6855:6855,
+6856:6856,
+6857:6857,
+6858:6858,
+6859:6859,
+6860:6860,
+6861:6861,
+6862:6862,
+6863:6863,
+6864:6864,
+6865:6865,
+6866:6866,
+6867:6867,
+6868:6555,
+6869:6869,
+6870:6870,
+6871:6871,
+6872:6872,
+6873:6873,
+6874:6874,
+6875:6875,
+6876:6876,
+6877:6877,
+6878:6878,
+6879:6879,
+6880:6880,
+6881:6881,
+6882:6882,
+6883:6883,
+6884:6884,
+6885:6885,
+6886:6886,
+6887:6887,
+6888:6888,
+6889:6889,
+6890:6890,
+6891:6891,
+6892:6892,
+6893:6893,
+6894:6894,
+6895:6895,
+6896:6896,
+6897:6897,
+6898:6898,
+6899:6899,
+6900:6900,
+6901:6901,
+6902:6902,
+6903:6903,
+6904:6904,
+6905:6905,
+6906:6906,
+6907:6907,
+6908:6908,
+6909:6909,
+6910:6910,
+6911:6911,
+6912:6912,
+6913:6913,
+6914:6914,
+6915:6915,
+6916:6916,
+6917:6917,
+6918:6918,
+6919:6919,
+6920:6920,
+6921:6921,
+6922:6922,
+6923:6923,
+6924:6924,
+6925:6925,
+6926:6926,
+6927:6927,
+6928:6928,
+6929:6929,
+6930:6930,
+6931:6931,
+6932:6932,
+6933:6933,
+6934:6934,
+6935:6935,
+6936:6936,
+6937:6937,
+6938:6559,
+6939:6939,
+6940:6940,
+6941:6941,
+6942:6942,
+6943:6943,
+6944:6944,
+6945:6945,
+6946:6946,
+6947:6947,
+6948:6948,
+6949:6949,
+6950:6950,
+6951:6951,
+6952:6952,
+6953:6953,
+6954:6954,
+6955:6955,
+6956:6956,
+6957:6957,
+6958:6958,
+6959:6959,
+6960:6960,
+6961:6961,
+6962:6962,
+6963:6963,
+6964:6964,
+6965:6965,
+6966:6966,
+6967:6967,
+6968:6968,
+6969:6969,
+6970:6970,
+6971:6971,
+6972:6972,
+6973:6973,
+6974:6974,
+6975:6975,
+6976:6976,
+6977:6977,
+6978:6978,
+6979:6979,
+6980:6980,
+6981:6981,
+6982:6982,
+6983:6983,
+6984:6984,
+6985:6985,
+6986:6986,
+6987:6987,
+6988:6988,
+6989:6989,
+6990:6990,
+6991:6991,
+6992:6992,
+6993:6993,
+6994:6994,
+6995:6995,
+6996:6996,
+6997:6997,
+6998:6998,
+6999:6999,
+7000:7000,
+7001:7001,
+7002:7002,
+7003:7003,
+7004:7004,
+7005:7005,
+7006:7006,
+7007:7007,
+7008:7008,
+7009:7009,
+7010:7010,
+7011:7011,
+7012:7012,
+7013:7013,
+7014:7014,
+7015:7015,
+7016:7016,
+7017:7017,
+7018:7018,
+7019:7019,
+7020:7020,
+7021:7021,
+7022:7022,
+7023:7023,
+7024:7024,
+7025:7027,
+7026:7028,
+7027:7025,
+7028:7026,
+7029:7051,
+7030:7052,
+7031:7029,
+7032:7030,
+7033:7033,
+7034:7034,
+7035:7035,
+7036:7036,
+7037:7037,
+7038:7038,
+7039:7039,
+7040:7040,
+7041:7041,
+7042:7042,
+7043:7043,
+7044:7044,
+7045:7045,
+7046:7046,
+7047:7047,
+7048:7048,
+7049:7049,
+7050:7050,
+7051:7031,
+7052:7032,
+7053:7053,
+7054:7054,
+7055:7055,
+7056:7056,
+7057:7057,
+7058:7058,
+7059:7059,
+7060:7060,
+7061:7061,
+7062:7062,
+7063:7063,
+7064:7064,
+7065:7065,
+7066:7066,
+7067:7067,
+7068:7068,
+7069:7069,
+7070:7070,
+7071:7071,
+7072:7072,
+7073:7073,
+7074:7074,
+7075:7075,
+7076:7076,
+7077:7077,
+7078:7078,
+7079:7079,
+7080:7080,
+7081:7081,
+7082:7082,
+7083:7083,
+7084:7084,
+7085:7085,
+7086:7086,
+7087:7087,
+7088:7088,
+7089:7089,
+7090:7090,
+7091:7091,
+7092:7092,
+7093:7093,
+7094:7094,
+7095:7095,
+7096:7096,
+7097:7097,
+7098:7098,
+7099:7099,
+7100:7100,
+7101:7101,
+7102:7102,
+7103:7103,
+7104:7104,
+7105:7105,
+7106:7106,
+7107:7107,
+7108:7108,
+7109:7109,
+7110:7110,
+7111:7111,
+7112:7112,
+7113:7113,
+7114:7114,
+7115:7115,
+7116:7116,
+7117:7117,
+7118:7118,
+7119:7119,
+7120:7120,
+7121:7121,
+7122:7122,
+7123:7123,
+7124:7124,
+7125:7125,
+7126:7126,
+7127:7127,
+7128:7128,
+7129:7129,
+7130:7130,
+7131:7131,
+7132:7132,
+7133:7133,
+7134:7134,
+7135:7135,
+7136:7136,
+7137:7137,
+7138:7138,
+7139:7139,
+7140:7140,
+7141:7141,
+7142:7142,
+7143:7143,
+7144:7144,
+7145:7145,
+7146:7146,
+7147:7147,
+7148:7148,
+7149:7149,
+7150:7150,
+7151:7151,
+7152:7152,
+7153:7153,
+7154:7154,
+7155:7155,
+7156:7156,
+7157:7157,
+7158:7158,
+7159:7159,
+7160:7160,
+7161:7161,
+7162:7162,
+7163:7163,
+7164:7164,
+7165:7165,
+7166:7166,
+7167:7167,
+7168:7168,
+7169:7169,
+7170:7170,
+7171:7171,
+7172:7172,
+7173:7173,
+7174:7174,
+7175:7175,
+7176:7176,
+7177:7177,
+7178:7178,
+7179:6868,
+7180:6938,
+7181:7179,
+7182:7287,
+7183:7183,
+7184:7184,
+7185:7185,
+7186:7186,
+7187:7187,
+7188:7188,
+7189:7189,
+7190:7190,
+7191:7191,
+7192:7192,
+7193:7193,
+7194:7194,
+7195:7195,
+7196:7196,
+7197:7197,
+7198:7198,
+7199:7199,
+7200:7200,
+7201:7201,
+7202:7202,
+7203:7203,
+7204:7204,
+7205:7205,
+7206:7206,
+7207:7207,
+7208:7208,
+7209:7209,
+7210:7210,
+7211:7211,
+7212:7212,
+7213:7213,
+7214:7214,
+7215:7215,
+7216:7216,
+7217:7217,
+7218:7218,
+7219:7219,
+7220:7220,
+7221:7221,
+7222:7222,
+7223:7223,
+7224:7224,
+7225:7225,
+7226:7226,
+7227:7227,
+7228:7228,
+7229:7229,
+7230:7230,
+7231:7231,
+7232:7232,
+7233:7233,
+7234:7234,
+7235:7235,
+7236:7236,
+7237:7237,
+7238:7238,
+7239:7239,
+7240:7240,
+7241:7241,
+7242:7242,
+7243:7243,
+7244:7244,
+7245:7245,
+7246:7246,
+7247:124,
+7248:7248,
+7249:7249,
+7250:7250,
+7251:7251,
+7252:7252,
+7253:135,
+7254:7254,
+7255:7255,
+7256:7256,
+7257:7257,
+7258:7258,
+7259:7259,
+7260:7260,
+7261:7261,
+7262:7262,
+7263:7263,
+7264:7264,
+7265:7265,
+7266:7266,
+7267:7267,
+7268:7268,
+7269:7269,
+7270:7270,
+7271:7271,
+7272:7272,
+7273:7273,
+7274:7274,
+7275:7275,
+7276:7276,
+7277:7277,
+7278:7278,
+7279:7279,
+7280:7280,
+7281:7281,
+7282:7282,
+7283:7283,
+7284:7284,
+7285:7285,
+7286:7286,
+7287:7313,
+7288:7288,
+7289:7289,
+7290:7290,
+7291:7291,
+7292:7292,
+7293:7293,
+7294:7294,
+7295:7295,
+7296:7296,
+7297:7297,
+7298:7298,
+7299:7299,
+7300:7300,
+7301:7301,
+7302:7302,
+7303:7303,
+7304:7304,
+7305:7305,
+7306:7306,
+7307:7307,
+7308:7308,
+7309:7309,
+7310:7310,
+7311:7311,
+7312:7312,
+7313:0000,
+7314:7314,
+7315:7315,
+7316:7316,
+7317:7317,
+7318:7318,
+7319:7319,
+7320:7320,
+7321:7321,
+7322:7322,
+7323:7323,
+7324:7324,
+7325:7325,
+7326:7326,
+7327:7327,
+7328:7328,
+7329:7329,
+7330:7330,
+7331:7331,
+7332:7332,
+7333:7333,
+7334:7334,
+7335:7335,
+7336:7336,
+7337:7337,
+7338:7338,
+7339:7339,
+7340:7340,
+7341:7341,
+7342:7342,
+7343:7343,
+7344:7344,
+7345:7345,
+7346:7346,
+7347:7347,
+7348:7348,
+7349:7349,
+7350:7350,
+7351:7351,
+7352:7352,
+7353:7353,
+7354:7354,
+7355:7355,
+7356:7356,
+7357:7357,
+7358:7358,
+7359:7359,
+7360:7360,
+7361:7361,
+7362:7362,
+7363:7363,
+7364:7364,
+7365:7365,
+7366:7366,
+7367:7367,
+7368:7368,
+7369:7369,
+7370:7370,
+7371:7371,
+7372:229,
+7373:7373,
+7374:7374,
+7375:7375,
+7376:7376,
+7377:7377,
+7378:7378,
+7379:7379,
+7380:7380,
+7381:7381,
+7382:7382,
+7383:7383,
+7384:7384,
+7385:7385,
+7386:7386,
+7387:7387,
+7388:7388,
+7389:7389,
+7390:7390,
+7391:7391,
+7392:7392,
+7393:7393,
+7394:7394,
+7395:7395,
+7396:7396,
+7397:7397,
+7398:7398,
+7399:7399,
+7400:7400,
+7401:7401,
+7402:7402,
+7403:7403,
+7404:7404,
+7405:7405,
+7406:7406,
+7407:7407,
+7408:7408,
+7409:7409,
+7410:7410,
+7411:7411,
+7412:7412,
+7413:7413,
+7414:7414,
+7415:7415,
+7416:7416,
+7417:7417,
+7418:7418,
+7419:7419,
+7420:7420,
+7421:7421,
+7422:7422,
+7423:7423,
+7424:7424,
+7425:7425,
+7426:7426,
+7427:7427,
+7428:7428,
+7429:7429,
+7430:7430,
+7431:7431,
+7432:7432,
+7433:7433,
+7434:7434,
+7435:7435,
+7436:7436,
+7437:7437,
+7438:7438,
+7439:7439,
+7440:7440,
+7441:7441,
+7442:7442,
+7443:7443,
+7444:7444,
+7445:7445,
+7446:7446,
+7447:7447,
+7448:7448,
+7449:7449,
+7450:7450,
+7451:7451,
+7452:7452,
+7453:7453,
+7454:7454,
+7455:7455,
+7456:7456,
+7457:7457,
+7458:7458,
+7459:7459,
+7460:7460,
+7461:7461,
+7462:7462,
+7463:7463,
+7464:7464,
+7465:7465,
+7466:7466,
+7467:7467,
+7468:7468,
+7469:7469,
+7470:7470,
+7471:7471,
+7472:7472,
+7473:7473,
+7474:7474,
+7475:7475,
+7476:102,
+7477:107,
+7478:110,
+7479:113,
+7480:114,
+7481:116,
+7482:117,
+7483:118,
+7484:119,
+7485:121,
+7486:122,
+7487:123,
+7488:125,
+7489:126,
+7490:3509,
+7491:128,
+7492:129,
+7493:132,
+7494:133,
+7495:136,
+7496:138,
+7497:139,
+7498:140,
+7499:141,
+7500:142,
+7501:143,
+7502:144,
+7503:145,
+7504:146,
+7505:147,
+7506:148,
+7507:149,
+7508:150,
+7509:151,
+7510:152,
+7511:153,
+7512:154,
+7513:155,
+7514:156,
+7515:157,
+7516:158,
+7517:159,
+7518:160,
+7519:161,
+7520:162,
+7521:163,
+7522:164,
+7523:165,
+7524:166,
+7525:167,
+7526:170,
+7527:171,
+7528:172,
+7529:175,
+7530:176,
+7531:177,
+7532:178,
+7533:179,
+7534:4846,
+7535:6522,
+7536:180,
+7537:181,
+7538:182,
+7539:183,
+7540:184,
+7541:185,
+7542:186,
+7543:187,
+7544:188,
+7545:189,
+7546:190,
+7547:191,
+7548:192,
+7549:193,
+7550:195,
+7551:196,
+7552:197,
+7553:198,
+7554:199,
+7555:200,
+7556:201,
+7557:202,
+7558:203,
+7559:204,
+7560:205,
+7561:206,
+7562:207,
+7563:208,
+7564:209,
+7565:210,
+7566:211,
+7567:212,
+7568:213,
+7569:214,
+7570:215,
+7571:216,
+7572:217,
+7573:218,
+7574:219,
+7575:220,
+7576:221,
+7577:222,
+7578:223,
+7579:224,
+7580:225,
+7581:226,
+7582:227,
+7583:228,
+7584:230,
+7585:233,
+7586:234,
+7587:0000,
+7588:236,
+7589:237,
+7590:238,
+7591:239,
+7592:240,
+7593:241,
+7594:242,
+7595:243,
+7596:244,
+7597:245,
+7598:246,
+7599:247,
+7600:248,
+7601:249,
+7602:250,
+7603:251,
+7604:252,
+7605:253,
+7606:254,
+7607:255,
+7608:256,
+7609:257,
+7610:258,
+7611:259,
+7612:260,
+7613:261,
+7614:262,
+7615:263,
+7616:264,
+7617:265,
+7618:266,
+7619:267,
+7620:268,
+7621:269,
+7622:270,
+7623:271,
+7624:272,
+7625:273,
+7626:274,
+7627:275,
+7628:276,
+7629:277,
+7630:278,
+7631:279,
+7632:281,
+7633:282,
+7634:283,
+7635:284,
+7636:285,
+7637:286,
+7638:287,
+7639:288,
+7640:289,
+7641:290,
+7642:291,
+7643:292,
+7644:295,
+7645:296,
+7646:297,
+7647:298,
+7648:299,
+7649:300,
+7650:301,
+7651:302,
+7652:303,
+7653:304,
+7654:305,
+7655:306,
+7656:307,
+7657:308,
+7658:309,
+7659:310,
+7660:311,
+7661:312,
+7662:313,
+7663:314,
+7664:315,
+7665:316,
+7666:317,
+7667:318,
+7668:319,
+7669:320,
+7670:321,
+7671:322,
+7672:323,
+7673:324,
+7674:325,
+7675:326,
+7676:327,
+7677:328,
+7678:329,
+7679:330,
+7680:331,
+7681:332,
+7682:333,
+7683:334,
+7684:335,
+7685:336,
+7686:337,
+7687:338,
+7688:339,
+7689:340,
+7690:341,
+7691:342,
+7692:343,
+7693:344,
+7694:345,
+7695:346,
+7696:5956,
+7697:349,
+7698:350,
+7699:396,
+7700:397,
+7701:398,
+7702:399,
+7703:400,
+7704:6108,
+7705:403,
+7706:404,
+7707:405,
+7708:406,
+7709:407,
+7710:618,
+7711:619,
+7712:620,
+7713:621,
+7714:623,
+7715:624,
+7716:625,
+7717:626,
+7718:627,
+7719:628,
+7720:635,
+7721:636,
+7722:637,
+7723:638,
+7724:639,
+7725:640,
+7726:641,
+7727:642,
+7728:643,
+7729:644,
+7730:645,
+7731:646,
+7732:647,
+7733:649,
+7734:650,
+7735:651,
+7736:652,
+7737:653,
+7738:0000,
+7739:655,
+7740:656,
+7741:657,
+7742:658,
+7743:659,
+7744:660,
+7745:661,
+7746:662,
+7747:663,
+7748:664,
+7749:665,
+7750:666,
+7751:667,
+7752:668,
+7753:669,
+7754:670,
+7755:671,
+7756:672,
+7757:673,
+7758:674,
+7759:675,
+7760:676,
+7761:677,
+7762:678,
+7763:679,
+7764:680,
+7765:681,
+7766:682,
+7767:683,
+7768:684,
+7769:685,
+7770:686,
+7771:687,
+7772:688,
+7773:689,
+7774:690,
+7775:691,
+7776:692,
+7777:693,
+7778:694,
+7779:696,
+7780:697,
+7781:698,
+7782:699,
+7783:700,
+7784:701,
+7785:702,
+7786:704,
+7787:706,
+7788:707,
+7789:708,
+7790:709,
+7791:710,
+7792:711,
+7793:712,
+7794:713,
+7795:714,
+7796:715,
+7797:716,
+7798:717,
+7799:718,
+7800:719,
+7801:720,
+7802:721,
+7803:722,
+7804:723,
+7805:724,
+7806:725,
+7807:726,
+7808:731,
+7809:732,
+7810:733,
+7811:734,
+7812:735,
+7813:736,
+7814:737,
+7815:738,
+7816:739,
+7817:740,
+7818:741,
+7819:742,
+7820:743,
+7821:744,
+7822:745,
+7823:746,
+7824:747,
+7825:748,
+7826:749,
+7827:750,
+7828:751,
+7829:752,
+7830:753,
+7831:754,
+7832:755,
+7833:756,
+7834:757,
+7835:758,
+7836:759,
+7837:760,
+7838:761,
+7839:762,
+7840:763,
+7841:764,
+7842:765,
+7843:766,
+7844:768,
+7845:769,
+7846:770,
+7847:771,
+7848:772,
+7849:773,
+7850:774,
+7851:776,
+7852:777,
+7853:778,
+7854:779,
+7855:780,
+7856:781,
+7857:782,
+7858:783,
+7859:784,
+7860:785,
+7861:786,
+7862:787,
+7863:788,
+7864:789,
+7865:790,
+7866:791,
+7867:792,
+7868:793,
+7869:794,
+7870:795,
+7871:796,
+7872:797,
+7873:798,
+7874:801,
+7875:802,
+7876:803,
+7877:804,
+7878:805,
+7879:806,
+7880:807,
+7881:808,
+7882:809,
+7883:810,
+7884:811,
+7885:812,
+7886:813,
+7887:814,
+7888:815,
+7889:816,
+7890:817,
+7891:818,
+7892:819,
+7893:820,
+7894:821,
+7895:822,
+7896:823,
+7897:824,
+7898:825,
+7899:826,
+7900:827,
+7901:828,
+7902:829,
+7903:830,
+7904:831,
+7905:832,
+7906:833,
+7907:834,
+7908:835,
+7909:836,
+7910:841,
+7911:842,
+7912:843,
+7913:844,
+7914:845,
+7915:846,
+7916:847,
+7917:848,
+7918:849,
+7919:850,
+7920:851,
+7921:852,
+7922:853,
+7923:854,
+7924:855,
+7925:856,
+7926:861,
+7927:862,
+7928:863,
+7929:4254,
+7930:865,
+7931:866,
+7932:867,
+7933:868,
+7934:869,
+7935:871,
+7936:872,
+7937:873,
+7938:874,
+7939:875,
+7940:877,
+7941:878,
+7942:879,
+7943:880,
+7944:881,
+7945:882,
+7946:883,
+7947:884,
+7948:885,
+7949:886,
+7950:887,
+7951:888,
+7952:889,
+7953:890,
+7954:891,
+7955:892,
+7956:893,
+7957:894,
+7958:895,
+7959:896,
+7960:897,
+7961:898,
+7962:899,
+7963:901,
+7964:902,
+7965:903,
+7966:6393,
+7967:907,
+7968:908,
+7969:909,
+7970:910,
+7971:911,
+7972:0000,
+7973:0000,
+7974:0000,
+7975:1010,
+7976:1011,
+7977:1012,
+7978:1013,
+7979:1014,
+7980:1015,
+7981:1016,
+7982:1017,
+7983:1018,
+7984:1022,
+7985:1023,
+7986:1024,
+7987:1025,
+7988:1026,
+7989:1027,
+7990:1028,
+7991:1029,
+7992:1030,
+7993:1031,
+7994:1032,
+7995:1033,
+7996:1034,
+7997:1035,
+7998:1036,
+7999:1037,
+8000:1038,
+8001:1039,
+8002:1040,
+8003:1041,
+8004:1042,
+8005:1043,
+8006:1044,
+8007:1045,
+8008:1046,
+8009:1047,
+8010:1048,
+8011:1049,
+8012:1050,
+8013:1051,
+8014:1052,
+8015:1053,
+8016:1069,
+8017:1087,
+8018:1088,
+8019:1089,
+8020:1090,
+8021:1091,
+8022:1092,
+8023:1093,
+8024:1094,
+8025:1095,
+8026:1096,
+8027:1097,
+8028:1098,
+8029:0000,
+8030:1700,
+8031:1701,
+8032:1702,
+8033:1703,
+8034:1704,
+8035:1705,
+8036:1706,
+8037:1707,
+8038:1708,
+8039:1709,
+8040:1710,
+8041:1711,
+8042:1712,
+8043:1713,
+8044:1714,
+8045:1715,
+8046:1722,
+8047:1723,
+8048:1724,
+8049:1725,
+8050:1726,
+8051:1727,
+8052:1728,
+8053:1729,
+8054:1730,
+8055:1731,
+8056:1732,
+8057:1733,
+8058:1959,
+8059:1979,
+8060:0000,
+8061:168,
+8062:2127,
+8063:2139,
+8064:2141,
+8065:2142,
+8066:2143,
+8067:2149,
+8068:2150,
+8069:2151,
+8070:2186,
+8071:2217,
+8072:2397,
+8073:2470,
+8074:2479,
+8075:2496,
+8076:2498,
+8077:2508,
+8078:2510,
+8079:2543,
+8080:2544,
+8081:2545,
+8082:2546,
+8083:2547,
+8084:2548,
+8085:2549,
+8086:2550,
+8087:2551,
+8088:2552,
+8089:2553,
+8090:2554,
+8091:2555,
+8092:2556,
+8093:2557,
+8094:2558,
+8095:2559,
+8096:2560,
+8097:2561,
+8098:2562,
+8099:2563,
+8100:2564,
+8101:2565,
+8102:2566,
+8103:2567,
+8104:2568,
+8105:2569,
+8106:2570,
+8107:2571,
+8108:2774,
+8109:3129,
+8110:3218,
+8111:3598,
+8112:3607,
+8113:3610,
+8114:3611,
+8115:3612,
+8116:4393,
+8117:4411,
+8118:4412,
+8119:4413,
+8120:4414,
+8121:4415,
+8122:4416,
+8123:4417,
+8124:4418,
+8125:4419,
+8126:4420,
+8127:4421,
+8128:4422,
+8129:4423,
+8130:4424,
+8131:4425,
+8132:4426,
+8133:4427,
+8134:4428,
+8135:4429,
+8136:4430,
+8137:4431,
+8138:4432,
+8139:4433,
+8140:4434,
+8141:4435,
+8142:4436,
+8143:4437,
+8144:4438,
+8145:4439,
+8146:4440,
+8147:4441,
+8148:4442,
+8149:4443,
+8150:4444,
+8151:4556,
+8152:4557,
+8153:4558,
+8154:4603,
+8155:4604,
+8156:4605,
+8157:4606,
+8158:4607,
+8159:4608,
+8160:4615,
+8161:4616,
+8162:4617,
+8163:4618,
+8164:4619,
+8165:4620,
+8166:4745,
+8167:4746,
+8168:4747,
+8169:4748,
+8170:4824,
+8171:4830,
+8172:4833,
+8173:4873,
+8174:5065,
+8175:5498,
+8176:5538,
+8177:5730,
+8178:5742,
+8179:5775,
+8180:5776,
+8181:5864,
+8182:5884,
+8183:5923,
+8184:5955,
+8185:5959,
+8186:6080,
+8187:6092,
+8188:3506,
+8189:6119,
+8190:6120,
+8191:6286,
+8192:6287,
+8193:6351,
+8194:6393,
+8195:6554,
+8196:6555,
+8197:6559,
+8198:6868,
+8199:6938,
+8200:7179,
+8201:7180,
+8202:7181,
+8203:7182,
+8204:7247,
+8205:7253,
+8206:7287,
+8207:7313,
+8208:7372,
+8209:7385,
+8210:0000,
+8211:0000,
+8212:0000,
+8213:0000,
+8214:0000,
+8215:0000,
+8216:0000,
+8217:0000,
+8218:0000,
+8219:0000,
+8220:0000,
+8221:0000,
+8222:0000,
+8223:0000,
+8224:0000,
+8225:0000,
+8226:0000,
+8227:0000,
+8228:0000,
+8229:0000,
+8230:0000,
+8231:0000,
+8232:0000,
+8233:0000,
+8234:0000,
+8235:0000,
+8236:0000,
+8237:0000,
+8238:0000,
+8239:0000,
+8240:0000,
+8241:0000,
+8242:0000,
+8243:0000,
+8244:0000,
+8245:0000,
+8246:0000,
+8247:0000,
+8248:0000,
+8249:0000,
+8250:0000,
+8251:0000,
+8252:0000,
+8253:0000,
+8254:0000,
+8255:0000,
+8256:0000,
+8257:0000,
+8258:0000,
+8259:0000,
+8260:0000,
+8261:0000,
+8262:0000,
+8263:0000,
+8264:0000,
+8265:0000,
+8266:0000,
+8267:0000,
+8268:0000,
+8269:0000,
+8270:0000,
+8271:0000,
+8272:0000,
+8273:0000,
+8274:0000,
+8275:100,
+8276:369,
+8277:370,
+8278:386,
+8279:411,
+8280:0000,
+8281:413,
+8282:414,
+8283:428,
+8284:432,
+8285:433,
+8286:0000,
+8287:905,
+8288:914,
+8289:915,
+8290:916,
+8291:917,
+8292:918,
+8293:919,
+8294:920,
+8295:921,
+8296:938,
+8297:939,
+8298:940,
+8299:941,
+8300:942,
+8301:943,
+8302:944,
+8303:945,
+8304:946,
+8305:947,
+8306:948,
+8307:949,
+8308:951,
+8309:953,
+8310:954,
+8311:955,
+8312:956,
+8313:957,
+8314:958,
+8315:959,
+8316:960,
+8317:961,
+8318:962,
+8319:963,
+8320:964,
+8321:965,
+8322:966,
+8323:967,
+8324:968,
+8325:969,
+8326:970,
+8327:971,
+8328:972,
+8329:973,
+8330:974,
+8331:975,
+8332:976,
+8333:977,
+8334:978,
+8335:979,
+8336:980,
+8337:981,
+8338:983,
+8339:984,
+8340:985,
+8341:986,
+8342:987,
+8343:988,
+8344:989,
+8345:990,
+8346:991,
+8347:992,
+8348:993,
+8349:994,
+8350:995,
+8351:996,
+8352:997,
+8353:998,
+8354:999,
+8355:1000,
+8356:1001,
+8357:1002,
+8358:1003,
+8359:1004,
+8360:1005,
+8361:1006,
+8362:1007,
+8363:1008,
+8364:1009,
+8365:1339,
+8366:2727,
+8367:2731,
+8368:3607,
+8369:3993,
+8370:6113,
+8371:0000,
+8372:0000,
+8373:0000,
+8374:0000,
+8375:0000,
+8376:0000,
+8377:0000,
+8378:0000,
+8379:0000,
+8380:0000,
+8381:0000,
+8382:0000,
+8383:0000,
+8384:0000,
+8385:0000,
+8386:0000,
+8387:0000,
+8388:0000,
+8389:0000,
+8390:0000,
+8391:0000,
+8392:0000,
+8393:0000,
+8394:0000,
+8395:0000,
+8396:0000,
+8397:0000,
+8398:0000,
+8399:0000,
+8400:0000,
+8401:0000,
+8402:0000,
+8403:0000,
+8404:0000,
+8405:0000,
+8406:0000,
+8407:0000,
+8408:0000,
+8409:0000,
+8410:0000,
+8411:0000,
+8412:0000,
+8413:0000,
+8414:0000,
+8415:0000,
+8416:0000,
+8417:0000,
+8418:0000,
+8419:0000,
+8420:0000,
+8421:0000,
+8422:0000,
+8423:0000,
+8424:0000,
+8425:0000,
+8426:0000,
+8427:0000,
+8428:0000,
+8429:0000,
+8430:0000,
+8431:0000,
+8432:0000,
+8433:0000,
+8434:0000,
+8435:0000,
+8436:0000,
+8437:0000,
+8438:0000,
+8439:0000,
+8440:0000,
+8441:0000,
+8442:0000,
+8443:0000,
+8444:0000,
+8445:0000,
+8446:0000,
+8447:0000,
+8448:0000,
+8449:0000,
+8450:0000,
+8451:0000,
+8452:0000,
+8453:0000,
+8454:0000,
+8455:0000,
+8456:0000,
+8457:0000,
+8458:0000,
+8459:0000,
+8460:0000,
+8461:0000,
+8462:0000,
+8463:0000,
+8464:0000,
+8465:0000,
+8466:0000,
+8467:0000,
+8468:0000,
+8469:0000,
+8470:0000,
+8471:0000,
+8472:0000,
+8473:0000,
+8474:0000,
+8475:0000,
+8476:0000,
+8477:0000,
+8478:0000,
+8479:0000,
+8480:0000,
+8481:0000,
+8482:0000,
+8483:0000,
+8484:0000,
+8485:0000,
+8486:0000,
+8487:0000,
+8488:0000,
+8489:0000,
+8490:0000,
+8491:0000,
+8492:0000,
+8493:0000,
+8494:0000,
+8495:0000,
+8496:0000,
+8497:0000,
+8498:0000,
+8499:0000,
+8500:0000,
+8501:0000,
+8502:0000,
+8503:0000,
+8504:0000,
+8505:0000,
+8506:0000,
+8507:0000,
+8508:0000,
+8509:0000,
+8510:0000,
+8511:0000,
+8512:0000,
+8513:0000,
+8514:0000,
+8515:0000,
+8516:0000,
+8517:0000,
+8518:0000,
+8519:0000,
+8520:0000,
+8521:0000,
+8522:0000,
+8523:0000,
+8524:0000,
+8525:0000,
+8526:0000,
+8527:0000,
+8528:0000,
+8529:0000,
+8530:0000,
+8531:0000,
+8532:0000,
+8533:0000,
+8534:0000,
+8535:0000,
+8536:0000,
+8537:0000,
+8538:0000,
+8539:0000,
+8540:0000,
+8541:0000,
+8542:0000,
+8543:0000,
+8544:0000,
+8545:0000,
+8546:0000,
+8547:0000,
+8548:0000,
+8549:0000,
+8550:0000,
+8551:0000,
+8552:0000,
+8553:0000,
+8554:0000,
+8555:0000,
+8556:0000,
+8557:0000,
+8558:0000,
+8559:0000,
+8560:0000,
+8561:0000,
+8562:0000,
+8563:0000,
+8564:0000,
+8565:0000,
+8566:0000,
+8567:0000,
+8568:0000,
+8569:0000,
+8570:0000,
+8571:0000,
+8572:0000,
+8573:0000,
+8574:0000,
+8575:0000,
+8576:0000,
+8577:0000,
+8578:0000,
+8579:0000,
+8580:0000,
+8581:0000,
+8582:0000,
+8583:0000,
+8584:0000,
+8585:0000,
+8586:0000,
+8587:0000,
+8588:0000,
+8589:0000,
+8590:0000,
+8591:0000,
+8592:0000,
+8593:0000,
+8594:0000,
+8595:0000,
+8596:0000,
+8597:0000,
+8598:0000,
+8599:0000,
+8600:0000,
+8601:0000,
+8602:0000,
+8603:0000,
+8604:0000,
+8605:0000,
+8606:0000,
+8607:0000,
+8608:0000,
+8609:0000,
+8610:0000,
+8611:0000,
+8612:0000,
+8613:0000,
+8614:0000,
+8615:0000,
+8616:0000,
+8617:0000,
+8618:0000,
+8619:0000,
+8620:0000,
+8621:0000,
+8622:0000,
+8623:0000,
+8624:0000,
+8625:0000,
+8626:0000,
+8627:0000,
+8628:0000,
+8629:0000,
+8630:0000,
+8631:0000,
+8632:0000,
+8633:0000,
+8634:0000,
+8635:0000,
+8636:0000,
+8637:0000,
+8638:0000,
+8639:0000,
+8640:0000,
+8641:0000,
+8642:0000,
+8643:0000,
+8644:0000,
+8645:0000,
+8646:0000,
+8647:0000,
+8648:0000,
+8649:0000,
+8650:0000,
+8651:0000,
+8652:0000,
+8653:0000,
+8654:0000,
+8655:0000,
+8656:0000,
+8657:0000,
+8658:0000,
+8659:0000,
+8660:0000,
+8661:0000,
+8662:0000,
+8663:0000,
+8664:0000,
+8665:0000,
+8666:0000,
+8667:0000,
+8668:0000,
+8669:0000,
+8670:0000,
+8671:0000,
+8672:0000,
+8673:0000,
+8674:0000,
+8675:0000,
+8676:0000,
+8677:0000,
+8678:0000,
+8679:0000,
+8680:0000,
+8681:0000,
+8682:0000,
+8683:0000,
+8684:0000,
+8685:0000,
+8686:0000,
+8687:0000,
+8688:0000,
+8689:0000,
+8690:0000,
+8691:0000,
+8692:0000,
+8693:0000,
+8694:0000,
+8695:0000,
+8696:0000,
+8697:0000,
+8698:0000,
+8699:0000,
+8700:0000,
+8701:0000,
+8702:0000,
+8703:0000,
+8704:0000,
+8705:0000,
+8706:0000,
+8707:0000,
+8708:0000,
+8709:0000,
+8710:0000,
+8711:0000,
+8712:0000,
+8713:0000,
+8714:0000,
+8715:0000,
+8716:0000,
+8717:0000,
+8718:0000,
+8719:0000,
+8720:0000,
+8721:0000,
+8722:0000,
+8723:0000,
+8724:0000,
+8725:0000,
+8726:0000,
+8727:0000,
+8728:0000,
+8729:0000,
+8730:0000,
+8731:0000,
+8732:0000,
+8733:0000,
+8734:0000,
+8735:0000,
+8736:0000,
+8737:0000,
+8738:0000,
+8739:0000,
+8740:0000,
+8741:0000,
+8742:0000,
+8743:0000,
+8744:0000,
+8745:0000,
+8746:0000,
+8747:0000,
+8748:0000,
+8749:0000,
+8750:0000,
+8751:0000,
+8752:0000,
+8753:0000,
+8754:0000,
+8755:0000,
+8756:0000,
+8757:0000,
+8758:0000,
+8759:0000,
+8760:0000,
+8761:0000,
+8762:0000,
+8763:0000,
+8764:0000,
+8765:0000,
+8766:0000,
+8767:0000,
+8768:0000,
+8769:0000,
+8770:0000,
+8771:0000,
+8772:0000,
+8773:0000,
+8774:0000,
+8775:0000,
+8776:0000,
+8777:0000,
+8778:0000,
+8779:0000,
+8780:0000,
+8781:0000,
+8782:0000,
+8783:0000,
+8784:0000,
+8785:0000,
+8786:0000,
+8787:0000,
+8788:0000,
+8789:0000,
+8790:0000,
+8791:0000,
+8792:0000,
+8793:0000,
+8794:0000,
+8795:0000,
+8796:0000,
+8797:0000,
+8798:0000,
+8799:0000,
+8800:0000,
+8801:0000,
+8802:0000,
+8803:0000,
+8804:0000,
+8805:0000,
+8806:0000,
+8807:0000,
+8808:0000,
+8809:0000,
+8810:0000,
+8811:0000,
+8812:0000,
+8813:0000,
+8814:0000,
+8815:0000,
+8816:0000,
+8817:0000,
+8818:0000,
+8819:0000,
+8820:0000,
+8821:0000,
+8822:0000,
+8823:0000,
+8824:0000,
+8825:0000,
+8826:0000,
+8827:0000,
+8828:0000,
+8829:0000,
+8830:0000,
+8831:0000,
+8832:0000,
+8833:0000,
+8834:0000,
+8835:0000,
+8836:0000,
+8837:0000,
+8838:0000,
+8839:0000,
+8840:0000,
+8841:0000,
+8842:0000,
+8843:0000,
+8844:0000,
+8845:0000,
+8846:0000,
+8847:0000,
+8848:0000,
+8849:0000,
+8850:0000,
+8851:0000,
+8852:0000,
+8853:0000,
+8854:0000,
+8855:0000,
+8856:0000,
+8857:0000,
+8858:0000,
+8859:0000,
+8860:0000,
+8861:0000,
+8862:0000,
+8863:0000,
+8864:0000,
+8865:0000,
+8866:0000,
+8867:0000,
+8868:0000,
+8869:0000,
+8870:0000,
+8871:0000,
+8872:0000,
+8873:0000,
+8874:0000,
+8875:0000,
+8876:0000,
+8877:0000,
+8878:0000,
+8879:0000,
+8880:0000,
+8881:0000,
+8882:0000,
+8883:0000,
+8884:0000,
+8885:0000,
+8886:0000,
+8887:0000,
+8888:0000,
+8889:0000,
+8890:0000,
+8891:0000,
+8892:0000,
+8893:0000,
+8894:0000,
+8895:0000,
+8896:0000,
+8897:0000,
+8898:0000,
+8899:0000,
+8900:0000,
+8901:0000,
+8902:0000,
+8903:0000,
+8904:0000,
+8905:0000,
+8906:0000,
+8907:0000,
+8908:0000,
+8909:0000,
+8910:0000,
+8911:0000,
+8912:0000,
+8913:0000,
+8914:0000,
+8915:0000,
+8916:0000,
+8917:0000,
+8918:0000,
+8919:0000,
+8920:0000,
+8921:0000,
+8922:0000,
+8923:0000,
+8924:0000,
+8925:0000,
+8926:0000,
+8927:0000,
+8928:0000,
+8929:0000,
+8930:0000,
+8931:0000,
+8932:0000,
+8933:0000,
+8934:0000,
+8935:0000,
+8936:0000,
+8937:0000,
+8938:0000,
+8939:0000,
+8940:0000,
+8941:0000,
+8942:0000,
+8943:0000,
+8944:0000,
+8945:0000,
+8946:0000,
+8947:0000,
+8948:0000,
+8949:0000,
+8950:0000,
+8951:0000,
+8952:0000,
+8953:0000,
+8954:0000,
+8955:0000,
+8956:0000,
+8957:0000,
+8958:0000,
+8959:0000,
+8960:0000,
+8961:0000,
+8962:0000,
+8963:0000,
+8964:0000,
+8965:0000,
+8966:0000,
+8967:0000,
+8968:0000,
+8969:0000,
+8970:0000,
+8971:0000,
+8972:0000,
+8973:0000,
+8974:0000,
+8975:0000,
+8976:0000,
+8977:0000,
+8978:0000,
+8979:0000,
+8980:0000,
+8981:0000,
+8982:0000,
+8983:0000,
+8984:0000,
+8985:0000,
+8986:0000,
+8987:0000,
+8988:0000,
+8989:0000,
+8990:0000,
+8991:0000,
+8992:0000,
+8993:0000,
+8994:0000,
+8995:0000,
+8996:0000,
+8997:0000,
+8998:0000,
+8999:0000,
+9000:0000,
+9001:0000,
+9002:0000,
+9003:0000,
+9004:0000,
+9005:0000,
+9006:0000,
+9007:0000,
+9008:0000,
+9009:0000,
+9010:0000,
+9011:0000,
+9012:0000,
+9013:0000,
+9014:0000,
+9015:0000,
+9016:0000,
+9017:0000,
+9018:0000,
+9019:0000,
+9020:0000,
+9021:351,
+9022:352,
+9023:353,
+9024:354,
+9025:355,
+9026:101,
+9027:5711,
+9028:5712,
+9029:5713,
+9030:5714,
+9031:5715,
+9032:5716,
+9033:5717,
+9034:5718,
+9035:5719,
+9036:5720,
+9037:5721,
+9038:5722,
+9039:5723,
+9040:5724,
+9041:5725,
+9042:5726,
+9043:4515,
+9044:4516,
+9045:4517,
+9046:4518,
+9047:4519,
+9048:4520,
+9049:4521,
+9050:4522,
+9051:4523,
+9052:4524,
+9053:4525,
+9054:4526,
+9055:4527,
+9056:4528,
+9057:4529,
+9058:4530,
+9059:231,
+9060:248,
+9061:1283,
+9062:6841,
+9063:6841,
+9064:6841,
+9065:6841,
+9066:6841,
+9067:408,
+9068:7062,
+9069:7063,
+9070:7064,
+9071:7065,
+9072:7066,
+9073:7066,
+9074:906,
+9075:653,
+9076:654,
+9077:655,
+9078:110,
+9079:138,
+9080:3573,
+9081:3503,
+9082:3991,
+9083:4130,
+9084:5025,
+9085:5026,
+9086:5027,
+9087:5028,
+9088:5029,
+9089:5030,
+9090:5031,
+9091:5032,
+9092:5033,
+9093:5034,
+9094:5035,
+9095:5036,
+9096:5037,
+9097:5038,
+9098:5039,
+9099:5040,
+9100:5041,
+9101:5042,
+9102:5043,
+9103:5044,
+9104:5401,
+9105:5402,
+9106:5403,
+9107:5798,
+9108:235,
+9109:5939,
+9110:0000,
+9111:0000,
+9112:0000,
+9113:0000,
+9114:0000,
+9115:0000,
+9116:0000,
+9117:0000,
+9118:0000,
+9119:0000,
+9120:0000,
+9121:0000,
+9122:0000,
+9123:0000,
+9124:0000,
+9125:0000,
+9126:0000,
+9127:0000,
+9128:0000,
+9129:0000,
+9130:0000,
+9131:0000,
+9132:0000,
+9133:0000,
+9134:0000,
+9135:0000,
+9136:0000,
+9137:0000,
+9138:0000,
+9139:0000,
+9140:0000,
+9141:0000,
+9142:0000,
+9143:0000,
+9144:0000,
+9145:0000,
+9146:0000,
+9147:0000,
+9148:0000,
+9149:0000,
+9150:0000,
+9151:0000,
+9152:0000,
+9153:0000,
+9154:0000,
+9155:0000,
+9156:0000,
+9157:0000,
+9158:0000,
+9159:0000,
+9160:0000,
+9161:0000,
+9162:0000,
+9163:0000,
+9164:0000,
+9165:0000,
+9166:0000,
+9167:0000,
+9168:0000,
+9169:0000,
+9170:0000,
+9171:0000,
+9172:0000,
+9173:0000,
+9174:0000,
+9175:0000,
+9176:0000,
+9177:0000,
+9178:0000,
+9179:0000,
+9180:0000,
+9181:0000,
+9182:0000,
+9183:0000,
+9184:0000,
+9185:0000,
+9186:0000,
+9187:0000,
+9188:0000,
+9189:0000,
+9190:0000,
+9191:0000,
+9192:0000,
+9193:0000,
+9194:0000,
+9195:0000,
+9196:0000,
+9197:0000,
+9198:0000,
+9199:0000,
+9200:0000,
+9201:0000,
+9202:0000,
+9203:0000,
+9204:0000,
+9205:0000,
+9206:0000,
+9207:0000,
+9208:0000,
+9209:0000,
+9210:0000,
+9211:0000,
+9212:0000,
+9213:0000,
+9214:0000,
+9215:0000,
+9216:0000,
+9217:0000,
+9218:0000,
+9219:0000,
+9220:0000,
+9221:0000,
+9222:0000,
+9223:0000,
+9224:0000,
+9225:0000,
+9226:0000,
+9227:0000,
+9228:0000,
+9229:0000,
+9230:0000,
+9231:0000,
+9232:0000,
+9233:0000,
+9234:0000,
+9235:0000,
+9236:0000,
+9237:0000,
+9238:0000,
+9239:0000,
+9240:0000,
+9241:0000,
+9242:0000,
+9243:0000,
+9244:0000,
+9245:0000,
+9246:0000,
+9247:0000,
+9248:0000,
+9249:0000,
+9250:0000,
+9251:0000,
+9252:0000,
+9253:0000,
+9254:0000,
+9255:0000,
+9256:0000,
+9257:0000,
+9258:0000,
+9259:0000,
+9260:0000,
+9261:0000,
+9262:0000,
+9263:0000,
+9264:0000,
+9265:0000,
+9266:0000,
+9267:0000,
+9268:0000,
+9269:0000,
+9270:0000,
+9271:0000,
+9272:0000,
+9273:0000,
+9274:0000,
+9275:0000,
+9276:0000,
+9277:0000,
+9278:0000,
+9279:0000,
+9280:0000,
+9281:0000,
+9282:0000,
+9283:0000,
+9284:0000,
+9285:0000,
+9286:0000,
+9287:0000,
+9288:0000,
+9289:0000,
+9290:0000,
+9291:0000,
+9292:0000,
+9293:0000,
+9294:0000,
+9295:0000,
+9296:0000,
+9297:0000,
+9298:0000,
+9299:0000,
+9300:0000,
+9301:0000,
+9302:0000,
+9303:0000,
+9304:0000,
+9305:0000,
+9306:0000,
+9307:0000,
+9308:0000,
+9309:0000,
+9310:0000,
+9311:0000,
+9312:0000,
+9313:0000,
+9314:0000,
+9315:0000,
+9316:0000,
+9317:0000,
+9318:0000,
+9319:0000,
+9320:0000,
+9321:0000,
+9322:0000,
+9323:0000,
+9324:0000,
+9325:0000,
+9326:0000,
+9327:0000,
+9328:0000,
+9329:0000,
+9330:0000,
+9331:0000,
+9332:0000,
+9333:0000,
+9334:0000,
+9335:0000,
+9336:0000,
+9337:0000,
+9338:0000,
+9339:0000,
+9340:0000,
+9341:0000,
+9342:0000,
+9343:0000,
+9344:0000,
+9345:0000,
+9346:0000,
+9347:0000,
+9348:0000,
+9349:0000,
+9350:0000,
+9351:0000,
+9352:0000,
+9353:0000,
+9354:0000,
+9355:0000,
+9356:0000,
+9357:0000,
+9358:0000,
+9359:0000,
+9360:0000,
+9361:0000,
+9362:0000,
+9363:0000,
+9364:0000,
+9365:0000,
+9366:0000,
+9367:0000,
+9368:0000,
+9369:0000,
+9370:0000,
+9371:0000,
+9372:0000,
+9373:0000,
+9374:0000,
+9375:0000,
+9376:0000,
+9377:0000,
+9378:0000,
+9379:0000,
+9380:0000,
+9381:0000,
+9382:0000,
+9383:0000,
+9384:0000,
+9385:0000,
+9386:0000,
+9387:0000,
+9388:0000,
+9389:0000,
+9390:0000,
+9391:0000,
+9392:0000,
+9393:0000,
+9394:0000,
+9395:0000,
+9396:0000,
+9397:0000,
+9398:0000,
+9399:0000,
+9400:0000,
+9401:0000,
+9402:0000,
+9403:0000,
+9404:0000,
+9405:0000,
+9406:0000,
+9407:0000,
+9408:0000,
+9409:0000,
+9410:0000,
+9411:0000,
+9412:0000,
+9413:0000,
+9414:0000,
+9415:0000,
+9416:0000,
+9417:0000,
+9418:0000,
+9419:0000,
+9420:0000,
+9421:0000,
+9422:0000,
+9423:0000,
+9424:0000,
+9425:0000,
+9426:0000,
+9427:0000,
+9428:0000,
+9429:0000,
+9430:0000,
+9431:0000,
+9432:0000,
+9433:0000,
+9434:0000,
+9435:0000,
+9436:0000,
+9437:0000,
+9438:0000,
+9439:0000,
+9440:0000,
+9441:0000,
+9442:0000,
+9443:0000,
+9444:0000,
+9445:0000,
+9446:0000,
+9447:0000,
+9448:0000,
+9449:0000,
+9450:0000,
+9451:0000,
+9452:0000,
+9453:0000,
+9454:0000,
+9455:0000,
+9456:0000,
+9457:0000,
+9458:0000,
+9459:0000,
+9460:0000,
+9461:0000,
+9462:0000,
+9463:0000,
+9464:0000,
+9465:0000,
+9466:0000,
+9467:0000,
+9468:0000,
+9469:0000,
+9470:0000,
+9471:0000,
+9472:0000,
+9473:0000,
+9474:0000,
+9475:0000,
+9476:0000,
+9477:0000,
+9478:0000,
+9479:0000,
+9480:0000,
+9481:0000,
+9482:0000,
+9483:0000,
+9484:0000,
+9485:0000,
+9486:0000,
+9487:0000,
+9488:0000,
+9489:0000,
+9490:0000,
+9491:0000,
+9492:0000,
+9493:0000,
+9494:0000,
+9495:0000,
+9496:0000,
+9497:0000,
+9498:0000,
+9499:0000,
+9500:0000,
+9501:0000,
+9502:0000,
+9503:0000,
+9504:0000,
+9505:0000,
+9506:0000,
+9507:0000,
+9508:0000,
+9509:0000,
+9510:0000,
+9511:0000,
+9512:0000,
+9513:0000,
+9514:0000,
+9515:0000,
+9516:0000,
+9517:0000,
+9518:0000,
+9519:0000,
+9520:0000,
+9521:0000,
+9522:0000,
+9523:0000,
+9524:0000,
+9525:0000,
+9526:0000,
+9527:0000,
+9528:0000,
+9529:0000,
+9530:0000,
+9531:0000,
+9532:0000,
+9533:0000,
+9534:0000,
+9535:0000,
+9536:0000,
+9537:0000,
+9538:0000,
+9539:0000,
+9540:0000,
+9541:0000,
+9542:0000,
+9543:0000,
+9544:0000,
+9545:0000,
+9546:0000,
+9547:0000,
+9548:0000,
+9549:0000,
+9550:0000,
+9551:0000,
+9552:0000,
+9553:0000,
+9554:0000,
+9555:0000,
+9556:0000,
+9557:0000,
+9558:0000,
+9559:0000,
+9560:0000,
+9561:0000,
+9562:0000,
+9563:0000,
+9564:0000,
+9565:0000,
+9566:0000,
+9567:0000,
+9568:0000,
+9569:0000,
+9570:0000,
+9571:0000,
+9572:0000,
+9573:0000,
+9574:0000,
+9575:0000,
+9576:0000,
+9577:0000,
+9578:0000,
+9579:0000,
+9580:0000,
+9581:0000,
+9582:0000,
+9583:0000,
+9584:0000,
+9585:0000,
+9586:0000,
+9587:0000,
+9588:0000,
+9589:0000,
+9590:0000,
+9591:0000,
+9592:0000,
+9593:0000,
+9594:0000,
+9595:0000,
+9596:0000,
+9597:0000,
+9598:0000,
+9599:0000,
+9600:0000,
+9601:0000,
+9602:0000,
+9603:0000,
+9604:0000,
+9605:0000,
+9606:0000,
+9607:0000,
+9608:0000,
+9609:0000,
+9610:0000,
+9611:0000,
+9612:0000,
+9613:0000,
+9614:0000,
+9615:0000,
+9616:0000,
+9617:0000,
+9618:0000,
+9619:0000,
+9620:0000,
+9621:0000,
+9622:0000,
+9623:0000,
+9624:0000,
+9625:0000,
+9626:0000,
+9627:0000,
+9628:0000,
+9629:0000,
+9630:0000,
+9631:0000,
+9632:0000,
+9633:0000,
+9634:0000,
+9635:0000,
+9636:0000,
+9637:0000,
+9638:0000,
+9639:0000,
+9640:0000,
+9641:0000,
+9642:0000,
+9643:0000,
+9644:0000,
+9645:0000,
+9646:0000,
+9647:0000,
+9648:0000,
+9649:0000,
+9650:0000,
+9651:0000,
+9652:0000,
+9653:0000,
+9654:0000,
+9655:0000,
+9656:0000,
+9657:0000,
+9658:0000,
+9659:0000,
+9660:0000,
+9661:0000,
+9662:0000,
+9663:0000,
+9664:0000,
+9665:0000,
+9666:0000,
+9667:0000,
+9668:0000,
+9669:0000,
+9670:0000,
+9671:0000,
+9672:0000,
+9673:0000,
+9674:0000,
+9675:0000,
+9676:0000,
+9677:0000,
+9678:0000,
+9679:2983,
+9680:0000,
+9681:0000,
+9682:0000,
+9683:0000,
+9684:0000,
+9685:0000,
+9686:0000,
+9687:0000,
+9688:0000,
+9689:0000,
+9690:0000,
+9691:0000,
+9692:0000,
+9693:0000,
+9694:0000,
+9695:0000,
+9696:0000,
+9697:0000,
+9698:0000,
+9699:0000,
+9700:0000,
+9701:0000,
+9702:0000,
+9703:0000,
+9704:0000,
+9705:0000,
+9706:0000,
+9707:0000,
+9708:0000,
+9709:0000,
+9710:0000,
+9711:0000,
+9712:0000,
+9713:0000,
+9714:0000,
+9715:0000,
+9716:0000,
+9717:0000,
+9718:0000,
+9719:0000,
+9720:0000,
+9721:0000,
+9722:0000,
+9723:0000,
+9724:0000,
+9725:0000,
+9726:0000,
+9727:0000,
+9728:0000,
+9729:0000,
+9730:0000,
+9731:0000,
+9732:0000,
+9733:0000,
+9734:0000,
+9735:0000,
+9736:0000,
+9737:0000,
+9738:0000,
+9739:0000,
+9740:0000,
+9741:0000,
+9742:0000,
+9743:0000,
+9744:0000,
+9745:0000,
+9746:0000,
+9747:0000,
+9748:0000,
+9749:0000,
+9750:0000,
+9751:0000,
+9752:0000,
+9753:0000,
+9754:0000,
+9755:0000,
+9756:0000,
+9757:0000,
+9758:0000,
+9759:0000,
+9760:0000,
+9761:0000,
+9762:0000,
+9763:0000,
+9764:0000,
+9765:0000,
+9766:0000,
+9767:0000,
+9768:0000,
+9769:0000,
+9770:0000,
+9771:0000,
+9772:0000,
+9773:0000,
+9774:0000,
+9775:0000,
+9776:0000,
+9777:0000,
+9778:0000,
+9779:0000,
+9780:0000,
+9781:0000,
+9782:0000,
+9783:0000,
+9784:0000,
+9785:0000,
+9786:0000,
+9787:0000,
+9788:0000,
+9789:0000,
+9790:0000,
+9791:0000,
+9792:0000,
+9793:0000,
+9794:0000,
+9795:0000,
+9796:0000,
+9797:0000,
+9798:0000,
+9799:0000,
+9800:0000,
+9801:0000,
+9802:0000,
+9803:0000,
+9804:0000,
+9805:0000,
+9806:0000,
+9807:0000,
+9808:0000,
+9809:0000,
+9810:0000,
+9811:0000,
+9812:0000,
+9813:0000,
+9814:0000,
+9815:0000,
+9816:0000,
+9817:0000,
+9818:0000,
+9819:0000,
+9820:0000,
+9821:0000,
+9822:0000,
+9823:0000,
+9824:0000,
+9825:0000,
+9826:0000,
+9827:0000,
+9828:0000,
+9829:0000,
+9830:0000,
+9831:0000,
+9832:0000,
+9833:0000,
+9834:0000,
+9835:0000,
+9836:0000,
+9837:0000,
+9838:0000,
+9839:0000,
+9840:0000,
+9841:0000,
+9842:0000,
+9843:0000,
+9844:0000,
+9845:0000,
+9846:0000,
+9847:0000,
+9848:0000,
+9849:0000,
+9850:0000,
+9851:0000,
+9852:0000,
+9853:0000,
+9854:0000,
+9855:0000,
+9856:0000,
+9857:0000,
+9858:0000,
+9859:0000,
+9860:0000,
+9861:0000,
+9862:0000,
+9863:0000,
+9864:0000,
+9865:0000,
+9866:0000,
+9867:0000,
+9868:0000,
+9869:0000,
+9870:0000,
+9871:0000,
+9872:0000,
+9873:0000,
+9874:0000,
+9875:0000,
+9876:0000,
+9877:0000,
+9878:0000,
+9879:0000,
+9880:0000,
+9881:0000,
+9882:0000,
+9883:0000,
+9884:0000,
+9885:0000,
+9886:0000,
+9887:0000,
+9888:0000,
+9889:0000,
+9890:0000,
+9891:0000,
+9892:0000,
+9893:0000,
+9894:0000,
+9895:0000,
+9896:0000,
+9897:0000,
+9898:0000,
+9899:0000,
+9900:0000,
+9901:0000,
+9902:0000,
+9903:0000,
+9904:0000,
+9905:0000,
+9906:0000,
+9907:0000,
+9908:0000,
+9909:0000,
+9910:0000,
+9911:0000,
+9912:0000,
+9913:0000,
+9914:0000,
+9915:0000,
+9916:0000,
+9917:0000,
+9918:0000,
+9919:0000,
+9920:0000,
+9921:0000,
+9922:0000,
+9923:0000,
+9924:0000,
+9925:0000,
+9926:0000,
+9927:0000,
+9928:0000,
+9929:0000,
+9930:0000,
+9931:0000,
+9932:0000,
+9933:0000,
+9934:3123,
+9935:0000,
+9936:0000,
+9937:0000,
+9938:0000,
+9939:0000,
+9940:0000,
+9941:0000,
+9942:0000,
+9943:0000,
+9944:0000,
+9945:0000,
+9946:0000,
+9947:0000,
+9948:0000,
+9949:0000,
+9950:0000,
+9951:0000,
+9952:0000,
+9953:0000,
+9954:0000,
+9955:0000,
+9956:0000,
+9957:0000,
+9958:0000,
+9959:0000,
+9960:0000,
+9961:0000,
+9962:0000,
+9963:0000,
+9964:0000,
+9965:0000,
+9966:0000,
+9967:0000,
+9968:0000,
+9969:0000,
+9970:0000,
+9971:0000,
+9972:0000,
+9973:0000,
+9974:0000,
+9975:0000,
+9976:0000,
+9977:0000,
+9978:0000,
+9979:0000,
+9980:0000,
+9981:0000,
+9982:0000,
+9983:0000,
+9984:0000,
+9985:0000,
+9986:0000,
+9987:0000,
+9988:0000,
+9989:0000,
+9990:0000,
+9991:0000,
+9992:0000,
+9993:0000,
+9994:0000,
+9995:0000,
+9996:0000,
+9997:0000,
+9998:0000,
+9999:0000,
+10000:0000,
+10001:0000,
+10002:0000,
+10003:0000,
+10004:0000,
+10005:0000,
+10006:0000,
+10007:0000,
+10008:0000,
+10009:0000,
+10010:0000,
+10011:0000,
+10012:0000,
+10013:0000,
+10014:0000,
+10015:0000,
+10016:0000,
+10017:0000,
+10018:3002,
+10019:5404,
+10020:5919,
+10021:6530,
+10022:0000,
+10023:0000,
+10024:0000,
+10025:0000,
+10026:0000,
+10027:0000,
+10028:0000,
+10029:0000,
+10030:0000,
+10031:0000,
+10032:0000,
+10033:0000,
+10034:0000,
+10035:0000,
+10036:0000,
+10037:0000,
+10038:0000,
+10039:0000,
+10040:0000,
+10041:0000,
+10042:0000,
+10043:0000,
+10044:0000,
+10045:0000,
+10046:0000,
+10047:0000,
+10048:0000,
+10049:0000,
+10050:0000,
+10051:0000,
+10052:0000,
+10053:0000,
+10054:0000,
+10055:0000,
+10056:0000,
+10057:0000,
+10058:0000,
+10059:0000,
+10060:0000,
+10061:0000,
+10062:0000,
+10063:0000,
+10064:0000,
+10065:0000,
+10066:0000,
+10067:0000,
+10068:0000,
+10069:0000,
+10070:0000,
+10071:0000,
+10072:0000,
+10073:0000,
+10074:0000,
+10075:0000,
+10076:0000,
+10077:0000,
+10078:0000,
+10079:0000,
+10080:0000,
+10081:0000,
+10082:0000,
+10083:0000,
+10084:0000,
+10085:0000,
+10086:0000,
+10087:0000,
+10088:0000,
+10089:0000,
+10090:0000,
+10091:0000,
+10092:0000,
+10093:0000,
+10094:0000,
+10095:0000,
+10096:0000,
+10097:0000,
+10098:0000,
+10099:0000,
+10100:0000,
+10101:0000,
+10102:0000,
+10103:0000,
+10104:0000,
+10105:0000,
+10106:0000,
+10107:0000,
+10108:0000,
+10109:0000,
+10110:0000,
+10111:0000,
+10112:0000,
+10113:0000,
+10114:0000,
+10115:0000,
+10116:0000,
+10117:0000,
+10118:0000,
+10119:0000,
+10120:0000,
+10121:0000,
+10122:0000,
+10123:0000,
+10124:0000,
+10125:0000,
+10126:0000,
+10127:0000,
+10128:0000,
+10129:0000,
+10130:0000,
+10131:0000,
+10132:0000,
+10133:0000,
+10134:0000,
+10135:0000,
+10136:0000,
+10137:0000,
+10138:0000,
+10139:0000,
+10140:0000,
+10141:0000,
+10142:0000,
+10143:0000,
+10144:0000,
+10145:0000,
+10146:0000,
+10147:0000,
+10148:0000,
+10149:0000,
+10150:0000,
+10151:0000,
+10152:0000,
+10153:0000,
+10154:0000,
+10155:0000,
+10156:0000,
+10157:0000,
+10158:0000,
+10159:0000,
+10160:0000,
+10161:0000,
+10162:0000,
+10163:0000,
+10164:0000,
+10165:0000,
+10166:0000,
+10167:0000,
+10168:0000,
+10169:0000,
+10170:0000,
+10171:0000,
+10172:0000,
+10173:0000,
+10174:0000,
+10175:0000,
+10176:0000,
+10177:0000,
+10178:0000,
+10179:0000,
+10180:0000,
+10181:0000,
+10182:0000,
+10183:0000,
+10184:0000,
+10185:0000,
+10186:0000,
+10187:0000,
+10188:0000,
+10189:0000,
+10190:0000,
+10191:0000,
+10192:0000,
+10193:0000,
+10194:0000,
+10195:0000,
+10196:0000,
+10197:0000,
+10198:0000,
+10199:0000,
+10200:0000,
+10201:0000,
+10202:0000,
+10203:0000,
+10204:0000,
+10205:0000,
+10206:0000,
+10207:0000,
+10208:0000,
+10209:0000,
+10210:0000,
+10211:0000,
+10212:0000,
+10213:0000,
+10214:0000,
+10215:0000,
+10216:0000,
+10217:0000,
+10218:0000,
+10219:0000,
+10220:0000,
+10221:0000,
+10222:0000,
+10223:0000,
+10224:0000,
+10225:0000,
+10226:0000,
+10227:0000,
+10228:0000,
+10229:0000,
+10230:0000,
+10231:0000,
+10232:0000,
+10233:0000,
+10234:0000,
+10235:0000,
+10236:0000,
+10237:0000,
+10238:0000,
+10239:0000,
+10240:0000,
+10241:0000,
+10242:0000,
+10243:0000,
+10244:0000,
+10245:0000,
+10246:0000,
+10247:0000,
+10248:0000,
+10249:0000,
+10250:0000,
+10251:0000,
+10252:0000,
+10253:0000,
+10254:0000,
+10255:0000,
+10256:0000,
+10257:0000,
+10258:0000,
+10259:0000,
+10260:0000,
+10261:0000,
+10262:0000,
+10263:0000,
+10264:0000,
+10265:0000,
+10266:0000,
+10267:0000,
+10268:0000,
+10269:0000,
+10270:0000,
+10271:0000,
+10272:0000,
+10273:0000,
+10274:0000,
+10275:0000,
+10276:0000,
+10277:0000,
+10278:0000,
+10279:0000,
+10280:0000,
+10281:0000,
+10282:0000,
+10283:0000,
+10284:0000,
+10285:0000,
+10286:0000,
+10287:0000,
+10288:0000,
+10289:0000,
+10290:0000,
+10291:0000,
+10292:0000,
+10293:0000,
+10294:0000,
+10295:0000,
+10296:0000,
+10297:0000,
+10298:0000,
+10299:0000,
+10300:0000,
+10301:0000,
+10302:0000,
+10303:0000,
+10304:0000,
+10305:0000,
+10306:0000,
+10307:0000,
+10308:0000,
+10309:0000,
+10310:0000,
+10311:0000,
+10312:0000,
+10313:0000,
+10314:0000,
+10315:0000,
+10316:0000,
+10317:0000,
+10318:0000,
+10319:0000,
+10320:0000,
+10321:0000,
+10322:0000,
+10323:0000,
+10324:0000,
+10325:0000,
+10326:0000,
+10327:0000,
+10328:0000,
+10329:0000,
+10330:0000,
+10331:0000,
+10332:0000,
+10333:0000,
+10334:0000,
+10335:0000,
+10336:0000,
+10337:0000,
+10338:0000,
+10339:0000,
+10340:0000,
+10341:0000,
+10342:0000,
+10343:0000,
+10344:0000,
+10345:0000,
+10346:0000,
+10347:0000,
+10348:0000,
+10349:0000,
+10350:0000,
+10351:0000,
+10352:0000,
+10353:0000,
+10354:0000,
+10355:0000,
+10356:0000,
+10357:0000,
+10358:0000,
+10359:0000,
+10360:0000,
+10361:0000,
+10362:0000,
+10363:0000,
+10364:0000,
+10365:0000,
+10366:0000,
+10367:0000,
+10368:0000,
+10369:0000,
+10370:0000,
+10371:0000,
+10372:0000,
+10373:0000,
+10374:0000,
+10375:0000,
+10376:0000,
+10377:0000,
+10378:0000,
+10379:0000,
+10380:0000,
+10381:0000,
+10382:0000,
+10383:0000,
+10384:0000,
+10385:0000,
+10386:0000,
+10387:0000,
+10388:0000,
+10389:0000,
+10390:0000,
+10391:0000,
+10392:0000,
+10393:0000,
+10394:0000,
+10395:0000,
+10396:0000,
+10397:0000,
+10398:0000,
+10399:0000,
+10400:0000,
+10401:0000,
+10402:0000,
+10403:0000,
+10404:0000,
+10405:0000,
+10406:0000,
+10407:0000,
+10408:0000,
+10409:0000,
+10410:0000,
+10411:0000,
+10412:0000,
+10413:0000,
+10414:0000,
+10415:0000,
+10416:0000,
+10417:0000,
+10418:0000,
+10419:0000,
+10420:0000,
+10421:0000,
+10422:0000,
+10423:0000,
+10424:0000,
+10425:0000,
+10426:0000,
+10427:0000,
+10428:0000,
+10429:0000,
+10430:0000,
+10431:0000,
+10432:0000,
+10433:0000,
+10434:0000,
+10435:0000,
+10436:0000,
+10437:0000,
+10438:0000,
+10439:0000,
+10440:0000,
+10441:0000,
+10442:0000,
+10443:0000,
+10444:0000,
+10445:0000,
+10446:0000,
+10447:0000,
+10448:0000,
+10449:0000,
+10450:0000,
+10451:0000,
+10452:0000,
+10453:0000,
+10454:0000,
+10455:0000,
+10456:0000,
+10457:0000,
+10458:0000,
+10459:0000,
+10460:0000,
+10461:0000,
+10462:0000,
+10463:0000,
+10464:0000,
+10465:0000,
+10466:0000,
+10467:0000,
+10468:0000,
+10469:0000,
+10470:0000,
+10471:0000,
+10472:0000,
+10473:0000,
+10474:0000,
+10475:0000,
+10476:0000,
+10477:0000,
+10478:0000,
+10479:0000,
+10480:0000,
+10481:0000,
+10482:0000,
+10483:0000,
+10484:0000,
+10485:0000,
+10486:0000,
+10487:0000,
+10488:0000,
+10489:0000,
+10490:0000,
+10491:0000,
+10492:0000,
+10493:0000,
+10494:0000,
+10495:0000,
+10496:0000,
+10497:0000,
+10498:0000,
+10499:0000,
+10500:0000,
+10501:0000,
+10502:0000,
+10503:0000,
+10504:0000,
+10505:0000,
+10506:0000,
+10507:0000,
+10508:0000,
+10509:0000,
+10510:0000,
+10511:0000,
+10512:0000,
+10513:0000,
+10514:0000,
+10515:0000,
+10516:0000,
+10517:0000,
+10518:0000,
+10519:0000,
+10520:0000,
+10521:0000,
+10522:0000,
+10523:0000,
+10524:0000,
+10525:0000,
+10526:0000,
+10527:0000,
+10528:0000,
+10529:0000,
+10530:0000,
+10531:0000,
+10532:0000,
+10533:0000,
+10534:0000,
+10535:0000,
+10536:0000,
+10537:0000,
+10538:0000,
+10539:0000,
+10540:0000,
+10541:0000,
+10542:0000,
+10543:0000,
+10544:0000,
+10545:0000,
+10546:2128,
+10547:0000,
+10548:0000,
+10549:0000,
+10550:0000,
+10551:0000,
+10552:0000,
+10553:0000,
+10554:0000,
+10555:0000,
+10556:0000,
+10557:0000,
+10558:0000,
+10559:0000,
+10560:0000,
+10561:0000,
+10562:0000,
+10563:0000,
+10564:0000,
+10565:0000,
+10566:0000,
+10567:0000,
+10568:0000,
+10569:0000,
+10570:0000,
+10571:0000,
+10572:0000,
+10573:0000,
+10574:0000,
+10575:0000,
+10576:0000,
+10577:0000,
+10578:0000,
+10579:0000,
+10580:0000,
+10581:0000,
+10582:0000,
+10583:0000,
+10584:0000,
+10585:0000,
+10586:0000,
+10587:0000,
+10588:0000,
+10589:0000,
+10590:0000,
+10591:0000,
+10592:0000,
+10593:0000,
+10594:0000,
+10595:0000,
+10596:0000,
+10597:0000,
+10598:0000,
+10599:0000,
+10600:0000,
+10601:0000,
+10602:0000,
+10603:0000,
+10604:0000,
+10605:0000,
+10606:0000,
+10607:0000,
+10608:0000,
+10609:0000,
+10610:0000,
+10611:0000,
+10612:0000,
+10613:0000,
+10614:0000,
+10615:0000,
+10616:0000,
+10617:0000,
+10618:0000,
+10619:0000,
+10620:0000,
+10621:0000,
+10622:0000,
+10623:0000,
+10624:0000,
+10625:0000,
+10626:0000,
+10627:0000,
+10628:0000,
+10629:0000,
+10630:0000,
+10631:0000,
+10632:0000,
+10633:0000,
+10634:0000,
+10635:0000,
+10636:0000,
+10637:0000,
+10638:0000,
+10639:0000,
+10640:0000,
+10641:0000,
+10642:0000,
+10643:0000,
+10644:0000,
+10645:0000,
+10646:0000,
+10647:0000,
+10648:0000,
+10649:0000,
+10650:0000,
+10651:0000,
+10652:0000,
+10653:0000,
+10654:0000,
+10655:0000,
+10656:0000,
+10657:0000,
+10658:0000,
+10659:0000,
+10660:0000,
+10661:0000,
+10662:0000,
+10663:0000,
+10664:0000,
+10665:0000,
+10666:0000,
+10667:0000,
+10668:0000,
+10669:0000,
+10670:0000,
+10671:0000,
+10672:0000,
+10673:0000,
+10674:0000,
+10675:0000,
+10676:0000,
+10677:0000,
+10678:0000,
+10679:0000,
+10680:0000,
+10681:0000,
+10682:0000,
+10683:0000,
+10684:0000,
+10685:0000,
+10686:0000,
+10687:0000,
+10688:0000,
+10689:0000,
+10690:0000,
+10691:0000,
+10692:0000,
+10693:0000,
+10694:0000,
+10695:0000,
+10696:0000,
+10697:0000,
+10698:0000,
+10699:0000,
+10700:0000,
+10701:0000,
+10702:0000,
+10703:0000,
+10704:0000,
+10705:0000,
+10706:0000,
+10707:0000,
+10708:0000,
+10709:0000,
+10710:0000,
+10711:0000,
+10712:0000,
+10713:0000,
+10714:0000,
+10715:0000,
+10716:0000,
+10717:0000,
+10718:0000,
+10719:0000,
+10720:0000,
+10721:0000,
+10722:0000,
+10723:0000,
+10724:0000,
+10725:0000,
+10726:0000,
+10727:0000,
+10728:0000,
+10729:0000,
+10730:0000,
+10731:0000,
+10732:0000,
+10733:0000,
+10734:0000,
+10735:0000,
+10736:0000,
+10737:0000,
+10738:0000,
+10739:0000,
+10740:0000,
+10741:0000,
+10742:0000,
+10743:0000,
+10744:0000,
+10745:0000,
+10746:0000,
+10747:0000,
+10748:0000,
+10749:0000,
+10750:0000,
+10751:0000,
+10752:0000,
+10753:0000,
+10754:0000,
+10755:0000,
+10756:0000,
+10757:0000,
+10758:0000,
+10759:0000,
+10760:0000,
+10761:0000,
+10762:0000,
+10763:0000,
+10764:0000,
+10765:0000,
+10766:0000,
+10767:0000,
+10768:0000,
+10769:0000,
+10770:0000,
+10771:0000,
+10772:0000,
+10773:0000,
+10774:0000,
+10775:0000,
+10776:0000,
+10777:0000,
+10778:0000,
+10779:0000,
+10780:0000,
+10781:0000,
+10782:0000,
+10783:0000,
+10784:0000,
+10785:0000,
+10786:0000,
+10787:0000,
+10788:0000,
+10789:0000,
+10790:0000,
+10791:0000,
+10792:0000,
+10793:0000,
+10794:0000,
+10795:0000,
+10796:0000,
+10797:0000,
+10798:0000,
+10799:0000,
+10800:0000,
+10801:0000,
+10802:0000,
+10803:0000,
+10804:0000,
+10805:0000,
+10806:0000,
+10807:0000,
+10808:0000,
+10809:0000,
+10810:0000,
+10811:0000,
+10812:0000,
+10813:0000,
+10814:0000,
+10815:0000,
+10816:0000,
+10817:0000,
+10818:0000,
+10819:0000,
+10820:0000,
+10821:0000,
+10822:0000,
+10823:0000,
+10824:0000,
+10825:0000,
+10826:0000,
+10827:0000,
+10828:0000,
+10829:0000,
+10830:0000,
+10831:0000,
+10832:0000,
+10833:0000,
+10834:0000,
+10835:0000,
+10836:0000,
+10837:0000,
+10838:0000,
+10839:0000,
+10840:0000,
+10841:0000,
+10842:0000,
+10843:0000,
+10844:0000,
+10845:0000,
+10846:0000,
+10847:0000,
+10848:0000,
+10849:0000,
+10850:0000,
+10851:0000,
+10852:0000,
+10853:0000,
+10854:0000,
+10855:0000,
+10856:0000,
+10857:0000,
+10858:0000,
+10859:0000,
+10860:0000,
+10861:0000,
+10862:0000,
+10863:0000,
+10864:0000,
+10865:0000,
+10866:0000,
+10867:0000,
+10868:0000,
+10869:0000,
+10870:0000,
+10871:0000,
+10872:0000,
+10873:0000,
+10874:0000,
+10875:0000,
+10876:0000,
+10877:0000,
+10878:0000,
+10879:0000,
+10880:0000,
+10881:0000,
+10882:0000,
+10883:0000,
+10884:0000,
+10885:0000,
+10886:0000,
+10887:0000,
+10888:0000,
+10889:0000,
+10890:0000,
+10891:0000,
+10892:0000,
+10893:0000,
+10894:0000,
+10895:0000,
+10896:0000,
+10897:0000,
+10898:0000,
+10899:0000,
+10900:0000,
+10901:0000,
+10902:0000,
+10903:0000,
+10904:0000,
+10905:0000,
+10906:0000,
+10907:0000,
+10908:0000,
+10909:0000,
+10910:0000,
+10911:0000,
+10912:0000,
+10913:0000,
+10914:0000,
+10915:0000,
+10916:0000,
+10917:0000,
+10918:0000,
+10919:0000,
+10920:0000,
+10921:0000,
+10922:0000,
+10923:0000,
+10924:0000,
+10925:0000,
+10926:0000,
+10927:0000,
+10928:0000,
+10929:0000,
+10930:0000,
+10931:0000,
+10932:0000,
+10933:0000,
+10934:0000,
+10935:0000,
+10936:0000,
+10937:0000,
+10938:0000,
+10939:0000,
+10940:0000,
+10941:0000,
+10942:0000,
+10943:0000,
+10944:0000,
+10945:0000,
+10946:0000,
+10947:0000,
+10948:0000,
+10949:0000,
+10950:0000,
+10951:0000,
+10952:0000,
+10953:0000,
+10954:0000,
+10955:0000,
+10956:0000,
+10957:0000,
+10958:0000,
+10959:0000,
+10960:0000,
+10961:0000,
+10962:0000,
+10963:0000,
+10964:0000,
+10965:0000,
+10966:0000,
+10967:0000,
+10968:0000,
+10969:0000,
+10970:0000,
+10971:0000,
+10972:0000,
+10973:0000,
+10974:0000,
+10975:0000,
+10976:0000,
+10977:0000,
+10978:0000,
+10979:0000,
+10980:0000,
+10981:0000,
+10982:0000,
+10983:0000,
+10984:0000,
+10985:0000,
+10986:0000,
+10987:0000,
+10988:0000,
+10989:0000,
+10990:0000,
+10991:0000,
+10992:0000,
+10993:0000,
+10994:0000,
+10995:0000,
+10996:0000,
+10997:0000,
+10998:0000,
+10999:0000,
+11000:0000,
+11001:0000,
+11002:0000,
+11003:0000,
+11004:0000,
+11005:0000,
+11006:0000,
+11007:0000,
+11008:0000,
+11009:0000,
+11010:0000,
+11011:0000,
+11012:0000,
+11013:0000,
+11014:0000,
+11015:0000,
+11016:0000,
+11017:0000,
+11018:0000,
+11019:0000,
+11020:0000,
+11021:0000,
+11022:0000,
+11023:0000,
+11024:0000,
+11025:0000,
+11026:0000,
+11027:0000,
+11028:0000,
+11029:0000,
+11030:0000,
+11031:0000,
+11032:0000,
+11033:0000,
+11034:0000,
+11035:0000,
+11036:0000,
+11037:0000,
+11038:0000,
+11039:0000,
+11040:0000,
+11041:0000,
+11042:0000,
+11043:0000,
+11044:0000,
+11045:0000,
+11046:0000,
+11047:0000,
+11048:0000,
+11049:0000,
+11050:0000,
+11051:0000,
+11052:0000,
+11053:0000,
+11054:0000,
+11055:0000,
+11056:0000,
+11057:0000,
+11058:0000,
+11059:0000,
+11060:0000,
+11061:0000,
+11062:0000,
+11063:0000,
+11064:0000,
+11065:0000,
+11066:0000,
+11067:0000,
+11068:0000,
+11069:0000,
+11070:0000,
+11071:0000,
+11072:0000,
+11073:0000,
+11074:0000,
+11075:0000,
+11076:0000,
+11077:0000,
+11078:0000,
+11079:0000,
+11080:0000,
+11081:0000,
+11082:0000,
+11083:0000,
+11084:0000,
+11085:0000,
+11086:0000,
+11087:0000,
+11088:0000,
+11089:0000,
+11090:0000,
+11091:0000,
+11092:0000,
+11093:0000,
+11094:0000,
+11095:0000,
+11096:0000,
+11097:0000,
+11098:0000,
+11099:0000,
+11100:0000,
+11101:0000,
+11102:0000,
+11103:0000,
+11104:0000,
+11105:0000,
+11106:0000,
+11107:0000,
+11108:0000,
+11109:0000,
+11110:0000,
+11111:0000,
+11112:0000,
+11113:0000,
+11114:0000,
+11115:0000,
+11116:0000,
+11117:0000,
+11118:0000,
+11119:0000,
+11120:0000,
+11121:0000,
+11122:0000,
+11123:0000,
+11124:0000,
+11125:0000,
+11126:0000,
+11127:0000,
+11128:0000,
+11129:0000,
+11130:0000,
+11131:0000,
+11132:0000,
+11133:0000,
+11134:0000,
+11135:0000,
+11136:3579,
+11137:0000,
+11138:6046,
+11139:4342,
+11140:4343,
+11141:4344,
+11142:0000,
+11143:0000,
+11144:0000,
+11145:0000,
+11146:0000,
+11147:0000,
+11148:0000,
+11149:0000,
+11150:0000,
+11151:0000,
+11152:0000,
+11153:0000,
+11154:0000,
+11155:0000,
+11156:0000,
+11157:0000,
+11158:0000,
+11159:0000,
+11160:0000,
+11161:0000,
+11162:0000,
+11163:0000,
+11164:0000,
+11165:0000,
+11166:0000,
+11167:0000,
+11168:0000,
+11169:0000,
+11170:0000,
+11171:0000,
+11172:0000,
+11173:0000,
+11174:0000,
+11175:0000,
+11176:0000,
+11177:0000,
+11178:0000,
+11179:0000,
+11180:0000,
+11181:0000,
+11182:0000,
+11183:0000,
+11184:0000,
+11185:0000,
+11186:0000,
+11187:0000,
+11188:0000,
+11189:0000,
+11190:0000,
+11191:0000,
+11192:0000,
+11193:0000,
+11194:0000,
+11195:0000,
+11196:0000,
+11197:0000,
+11198:0000,
+11199:0000,
+11200:0000,
+11201:0000,
+11202:0000,
+11203:0000,
+11204:0000,
+11205:0000,
+11206:0000,
+11207:0000,
+11208:0000,
+11209:0000,
+11210:0000,
+11211:0000,
+11212:0000,
+11213:0000,
+11214:0000,
+11215:0000,
+11216:0000,
+11217:0000,
+11218:0000,
+11219:0000,
+11220:0000,
+11221:0000,
+11222:0000,
+11223:0000,
+11224:0000,
+11225:0000,
+11226:0000,
+11227:0000,
+11228:0000,
+11229:0000,
+11230:0000,
+11231:0000,
+11232:0000,
+11233:0000,
+11234:0000,
+11235:0000,
+11236:0000,
+11237:0000,
+11238:0000,
+11239:0000,
+11240:0000,
+11241:0000,
+11242:0000,
+11243:0000,
+11244:0000,
+11245:0000,
+11246:0000,
+11247:0000,
+11248:0000,
+11249:0000,
+11250:0000,
+11251:0000,
+11252:0000,
+11253:0000,
+11254:0000,
+11255:0000,
+11256:0000,
+11257:0000,
+11258:0000,
+11259:0000,
+11260:0000,
+11261:0000,
+11262:0000,
+11263:0000,
+11264:0000,
+11265:0000,
+11266:0000,
+11267:0000,
+11268:0000,
+11269:0000,
+11270:0000,
+11271:0000,
+11272:0000,
+11273:0000,
+11274:0000,
+11275:0000,
+11276:0000,
+11277:0000,
+11278:0000,
+11279:0000,
+11280:0000,
+11281:0000,
+11282:0000,
+11283:0000,
+11284:0000,
+11285:0000,
+11286:0000,
+11287:0000,
+11288:0000,
+11289:0000,
+11290:0000,
+11291:0000,
+11292:0000,
+11293:0000,
+11294:0000,
+11295:0000,
+11296:0000,
+11297:0000,
+11298:0000,
+11299:0000,
+11300:0000,
+11301:0000,
+11302:0000,
+11303:0000,
+11304:0000,
+11305:0000,
+11306:0000,
+11307:0000,
+11308:0000,
+11309:0000,
+11310:0000,
+11311:0000,
+11312:0000,
+11313:0000,
+11314:0000,
+11315:0000,
+11316:0000,
+11317:0000,
+11318:0000,
+11319:0000,
+11320:0000,
+11321:0000,
+11322:0000,
+11323:0000,
+11324:0000,
+11325:0000,
+11326:0000,
+11327:0000,
+11328:0000,
+11329:0000,
+11330:0000,
+11331:0000,
+11332:0000,
+11333:0000,
+11334:0000,
+11335:0000,
+11336:0000,
+11337:0000,
+11338:0000,
+11339:0000,
+11340:0000,
+11341:0000,
+11342:0000,
+11343:0000,
+11344:0000,
+11345:0000,
+11346:0000,
+11347:0000,
+11348:0000,
+11349:0000,
+11350:0000,
+11351:0000,
+11352:0000,
+11353:0000,
+11354:0000,
+11355:0000,
+11356:0000,
+11357:0000,
+11358:0000,
+11359:0000,
+11360:0000,
+11361:0000,
+11362:0000,
+11363:0000,
+11364:0000,
+11365:0000,
+11366:0000,
+11367:0000,
+11368:0000,
+11369:0000,
+11370:0000,
+11371:0000,
+11372:0000,
+11373:0000,
+11374:0000,
+11375:0000,
+11376:0000,
+11377:0000,
+11378:0000,
+11379:0000,
+11380:0000,
+11381:0000,
+11382:0000,
+11383:0000,
+11384:0000,
+11385:0000,
+11386:0000,
+11387:0000,
+11388:0000,
+11389:0000,
+11390:0000,
+11391:0000,
+11392:0000,
+11393:0000,
+11394:0000,
+11395:860,
+11396:2874,
+11397:2875,
+11398:2882,
+11399:2901,
+11400:3215,
+11401:3997,
+11402:4010,
+11403:4014,
+11404:4015,
+11405:4019,
+11406:4023,
+11407:4028,
+11408:168,
+11409:4035,
+11410:4037,
+11411:4049,
+11412:4054,
+11413:4065,
+11414:4073,
+11415:4077,
+11416:4100,
+11417:4115,
+11418:4124,
+11419:4140,
+11420:4144,
+11421:4872,
+11422:5924,
+11423:5945,
+11424:6099,
+11425:6100,
+11426:6101,
+11427:6105,
+11428:0000,
+11429:0000,
+11430:0000,
+11431:0000,
+11432:0000,
+11433:0000,
+11434:0000,
+11435:0000,
+11436:0000,
+11437:0000,
+11438:0000,
+11439:0000,
+11440:0000,
+11441:0000,
+11442:0000,
+11443:0000,
+11444:0000,
+11445:0000,
+11446:0000,
+11447:0000,
+11448:0000,
+11449:0000,
+11450:0000,
+11451:0000,
+11452:0000,
+11453:0000,
+11454:0000,
+11455:0000,
+11456:0000,
+11457:0000,
+11458:0000,
+11459:0000,
+11460:0000,
+11461:0000,
+11462:0000,
+11463:0000,
+11464:0000,
+11465:0000,
+11466:0000,
+11467:0000,
+11468:0000,
+11469:0000,
+11470:0000,
+11471:0000,
+11472:0000,
+11473:0000,
+11474:0000,
+11475:0000,
+11476:0000,
+11477:0000,
+11478:0000,
+11479:0000,
+11480:0000,
+11481:0000,
+11482:0000,
+11483:0000,
+11484:0000,
+11485:0000,
+11486:0000,
+11487:0000,
+11488:0000,
+11489:0000,
+11490:0000,
+11491:0000,
+11492:0000,
+11493:0000,
+11494:0000,
+11495:0000,
+11496:0000,
+11497:0000,
+11498:0000,
+11499:0000,
+11500:0000,
+11501:0000,
+11502:0000,
+11503:0000,
+11504:0000,
+11505:0000,
+11506:0000,
+11507:0000,
+11508:0000,
+11509:0000,
+11510:0000,
+11511:0000,
+11512:0000,
+11513:0000,
+11514:0000,
+11515:0000,
+11516:0000,
+11517:0000,
+11518:0000,
+11519:0000,
+11520:0000,
+11521:0000,
+11522:0000,
+11523:0000,
+11524:0000,
+11525:0000,
+11526:0000,
+11527:0000,
+11528:0000,
+11529:0000,
+11530:0000,
+11531:0000,
+11532:0000,
+11533:0000,
+11534:0000,
+11535:0000,
+11536:0000,
+11537:0000,
+11538:0000,
+11539:0000,
+11540:0000,
+11541:0000,
+11542:0000,
+11543:0000,
+11544:0000,
+11545:0000,
+11546:0000,
+11547:0000,
+11548:0000,
+11549:0000,
+11550:0000,
+11551:0000,
+11552:0000,
+11553:0000,
+11554:0000,
+11555:0000,
+11556:0000,
+11557:0000,
+11558:0000,
+11559:0000,
+11560:0000,
+11561:0000,
+11562:0000,
+11563:0000,
+11564:0000,
+11565:0000,
+11566:0000,
+11567:0000,
+11568:0000,
+11569:0000,
+11570:0000,
+11571:0000,
+11572:0000,
+11573:0000,
+11574:0000,
+11575:0000,
+11576:0000,
+11577:0000,
+11578:0000,
+11579:0000,
+11580:0000,
+11581:0000,
+11582:0000,
+11583:0000,
+11584:0000,
+11585:0000,
+11586:0000,
+11587:0000,
+11588:0000,
+11589:0000,
+11590:0000,
+11591:0000,
+11592:0000,
+11593:0000,
+11594:0000,
+11595:0000,
+11596:0000,
+11597:0000,
+11598:0000,
+11599:0000,
+11600:0000,
+11601:0000,
+11602:0000,
+11603:0000,
+11604:0000,
+11605:0000,
+11606:0000,
+11607:0000,
+11608:0000,
+11609:0000,
+11610:0000,
+11611:0000,
+11612:0000,
+11613:0000,
+11614:0000,
+11615:0000,
+11616:0000,
+11617:0000,
+11618:0000,
+11619:0000,
+11620:0000,
+11621:0000,
+11622:0000,
+11623:0000,
+11624:0000,
+11625:0000,
+11626:0000,
+11627:0000,
+11628:0000,
+11629:0000,
+11630:0000,
+11631:0000,
+11632:0000,
+11633:0000,
+11634:0000,
+11635:0000,
+11636:0000,
+11637:0000,
+11638:0000,
+11639:0000,
+11640:0000,
+11641:0000,
+11642:0000,
+11643:0000,
+11644:0000,
+11645:0000,
+11646:0000,
+11647:0000,
+11648:0000,
+11649:0000,
+11650:0000,
+11651:0000,
+11652:0000,
+11653:0000,
+11654:0000,
+11655:0000,
+11656:0000,
+11657:0000,
+11658:0000,
+11659:0000,
+11660:0000,
+11661:0000,
+11662:0000,
+11663:0000,
+11664:0000,
+11665:0000,
+11666:0000,
+11667:0000,
+11668:0000,
+11669:0000,
+11670:0000,
+11671:0000,
+11672:0000,
+11673:0000,
+11674:0000,
+11675:0000,
+11676:0000,
+11677:0000,
+11678:0000,
+11679:0000,
+11680:0000,
+11681:0000,
+11682:0000,
+11683:0000,
+11684:0000,
+11685:0000,
+11686:0000,
+11687:0000,
+11688:0000,
+11689:0000,
+11690:0000,
+11691:0000,
+11692:0000,
+11693:0000,
+11694:0000,
+11695:0000,
+11696:0000,
+11697:0000,
+11698:0000,
+11699:0000,
+11700:0000,
+11701:0000,
+11702:0000,
+11703:0000,
+11704:0000,
+11705:0000,
+11706:0000,
+11707:0000,
+11708:0000,
+11709:0000,
+11710:0000,
+11711:0000,
+11712:0000,
+11713:0000,
+11714:0000,
+11715:0000,
+11716:0000,
+11717:0000,
+11718:0000,
+11719:0000,
+11720:0000,
+11721:0000,
+11722:0000,
+11723:0000,
+11724:0000,
+11725:0000,
+11726:0000,
+11727:0000,
+11728:0000,
+11729:0000,
+11730:0000,
+11731:0000,
+11732:0000,
+11733:0000,
+11734:0000,
+11735:0000,
+11736:0000,
+11737:0000,
+11738:0000,
+11739:0000,
+11740:0000,
+11741:0000,
+11742:0000,
+11743:0000,
+11744:0000,
+11745:0000,
+11746:0000,
+11747:0000,
+11748:0000,
+11749:0000,
+11750:0000,
+11751:0000,
+11752:0000,
+11753:0000,
+11754:0000,
+11755:0000,
+11756:0000,
+11757:0000,
+11758:0000,
+11759:0000,
+11760:0000,
+11761:0000,
+11762:0000,
+11763:0000,
+11764:0000,
+11765:0000,
+11766:0000,
+11767:0000,
+11768:0000,
+11769:0000,
+11770:0000,
+11771:0000,
+11772:0000,
+11773:0000,
+11774:0000,
+11775:0000,
+11776:0000,
+11777:0000,
+11778:0000,
+11779:0000,
+11780:0000,
+11781:0000,
+11782:0000,
+11783:0000,
+11784:0000,
+11785:0000,
+11786:0000,
+11787:0000,
+11788:0000,
+11789:0000,
+11790:0000,
+11791:0000,
+11792:0000,
+11793:0000,
+11794:0000,
+11795:0000,
+11796:0000,
+11797:0000,
+11798:0000,
+11799:0000,
+11800:0000,
+11801:0000,
+11802:0000,
+11803:0000,
+11804:0000,
+11805:0000,
+11806:0000,
+11807:0000,
+11808:0000,
+11809:0000,
+11810:0000,
+11811:0000,
+11812:0000,
+11813:0000,
+11814:0000,
+11815:0000,
+11816:0000,
+11817:0000,
+11818:0000,
+11819:0000,
+11820:0000,
+11821:0000,
+11822:0000,
+11823:0000,
+11824:0000,
+11825:0000,
+11826:0000,
+11827:0000,
+11828:0000,
+11829:0000,
+11830:0000,
+11831:0000,
+11832:0000,
+11833:0000,
+11834:0000,
+11835:0000,
+11836:0000,
+11837:0000,
+11838:0000,
+11839:0000,
+11840:0000,
+11841:0000,
+11842:0000,
+11843:0000,
+11844:0000,
+11845:0000,
+11846:0000,
+11847:0000,
+11848:0000,
+11849:0000,
+11850:0000,
+11851:0000,
+11852:0000,
+11853:0000,
+11854:0000,
+11855:0000,
+11856:0000,
+11857:0000,
+11858:0000,
+11859:0000,
+11860:0000,
+11861:0000,
+11862:0000,
+11863:0000,
+11864:0000,
+11865:0000,
+11866:0000,
+11867:0000,
+11868:0000,
+11869:0000,
+11870:0000,
+11871:0000,
+11872:0000,
+11873:0000,
+11874:0000,
+11875:0000,
+11876:0000,
+11877:0000,
+11878:0000,
+11879:0000,
+11880:0000,
+11881:0000,
+11882:0000,
+11883:0000,
+11884:0000,
+11885:0000,
+11886:0000,
+11887:0000,
+11888:0000,
+11889:0000,
+11890:0000,
+11891:0000,
+11892:0000,
+11893:0000,
+11894:0000,
+11895:0000,
+11896:0000,
+11897:0000,
+11898:0000,
+11899:0000,
+11900:0000,
+11901:0000,
+11902:0000,
+11903:0000,
+11904:0000,
+11905:0000,
+11906:0000,
+11907:0000,
+11908:0000,
+11909:0000,
+11910:0000,
+11911:0000,
+11912:0000,
+11913:0000,
+11914:0000,
+11915:0000,
+11916:0000,
+11917:0000,
+11918:0000,
+11919:0000,
+11920:0000,
+11921:0000,
+11922:0000,
+11923:0000,
+11924:0000,
+11925:0000,
+11926:0000,
+11927:0000,
+11928:0000,
+11929:0000,
+11930:0000,
+11931:0000,
+11932:0000,
+11933:0000,
+11934:0000,
+11935:0000,
+11936:0000,
+11937:0000,
+11938:0000,
+11939:0000,
+11940:0000,
+11941:0000,
+11942:0000,
+11943:0000,
+11944:0000,
+11945:0000,
+11946:0000,
+11947:0000,
+11948:0000,
+11949:0000,
+11950:0000,
+11951:0000,
+11952:0000,
+11953:0000,
+11954:0000,
+11955:0000,
+11956:0000,
+11957:0000,
+11958:0000,
+11959:0000,
+11960:0000,
+11961:0000,
+11962:0000,
+11963:0000,
+11964:0000,
+11965:0000,
+11966:0000,
+11967:0000,
+11968:0000,
+11969:0000,
+11970:0000,
+11971:0000,
+11972:0000,
+11973:0000,
+11974:0000,
+11975:0000,
+11976:0000,
+11977:0000,
+11978:0000,
+11979:0000,
+11980:0000,
+11981:0000,
+11982:0000,
+11983:0000,
+11984:0000,
+11985:0000,
+11986:0000,
+11987:0000,
+11988:0000,
+11989:0000,
+11990:0000,
+11991:0000,
+11992:0000,
+11993:0000,
+11994:0000,
+11995:0000,
+11996:0000,
+11997:0000,
+11998:0000,
+11999:0000,
+12000:0000,
+12001:0000,
+12002:0000,
+12003:0000,
+12004:0000,
+12005:0000,
+12006:0000,
+12007:0000,
+12008:0000,
+12009:0000,
+12010:0000,
+12011:0000,
+12012:0000,
+12013:0000,
+12014:0000,
+12015:0000,
+12016:0000,
+12017:0000,
+12018:0000,
+12019:0000,
+12020:0000,
+12021:0000,
+12022:0000,
+12023:0000,
+12024:0000,
+12025:0000,
+12026:0000,
+12027:0000,
+12028:0000,
+12029:0000,
+12030:0000,
+12031:0000,
+12032:0000,
+12033:0000,
+12034:0000,
+12035:0000,
+12036:0000,
+12037:0000,
+12038:0000,
+12039:0000,
+12040:0000,
+12041:0000,
+12042:0000,
+12043:0000,
+12044:0000,
+12045:0000,
+12046:0000,
+12047:0000,
+12048:0000,
+12049:0000,
+12050:0000,
+12051:0000,
+12052:0000,
+12053:0000,
+12054:0000,
+12055:0000,
+12056:0000,
+12057:0000,
+12058:0000,
+12059:0000,
+12060:0000,
+12061:0000,
+12062:0000,
+12063:0000,
+12064:0000,
+12065:0000,
+12066:0000,
+12067:0000,
+12068:0000,
+12069:0000,
+12070:0000,
+12071:0000,
+12072:0000,
+12073:0000,
+12074:0000,
+12075:0000,
+12076:0000,
+12077:0000,
+12078:0000,
+12079:0000,
+12080:0000,
+12081:0000,
+12082:0000,
+12083:0000,
+12084:0000,
+12085:0000,
+12086:0000,
+12087:0000,
+12088:0000,
+12089:0000,
+12090:0000,
+12091:0000,
+12092:0000,
+12093:0000,
+12094:0000,
+12095:0000,
+12096:0000,
+12097:0000,
+12098:0000,
+12099:0000,
+12100:0000,
+12101:0000,
+12102:0000,
+12103:0000,
+12104:0000,
+12105:0000,
+12106:0000,
+12107:0000,
+12108:0000,
+12109:0000,
+12110:0000,
+12111:0000,
+12112:0000,
+12113:0000,
+12114:0000,
+12115:0000,
+12116:0000,
+12117:0000,
+12118:0000,
+12119:0000,
+12120:0000,
+12121:0000,
+12122:0000,
+12123:0000,
+12124:0000,
+12125:0000,
+12126:0000,
+12127:0000,
+12128:0000,
+12129:0000,
+12130:0000,
+12131:0000,
+12132:0000,
+12133:0000,
+12134:0000,
+12135:0000,
+12136:0000,
+12137:0000,
+12138:0000,
+12139:0000,
+12140:0000,
+12141:0000,
+12142:0000,
+12143:0000,
+12144:0000,
+12145:0000,
+12146:0000,
+12147:0000,
+12148:0000,
+12149:0000,
+12150:0000,
+12151:0000,
+12152:0000,
+12153:0000,
+12154:0000,
+12155:0000,
+12156:0000,
+12157:0000,
+12158:0000,
+12159:0000,
+12160:0000,
+12161:0000,
+12162:0000,
+12163:0000,
+12164:0000,
+12165:0000,
+12166:0000,
+12167:0000,
+12168:0000,
+12169:0000,
+12170:0000,
+12171:0000,
+12172:0000,
+12173:0000,
+12174:0000,
+12175:0000,
+12176:0000,
+12177:0000,
+12178:0000,
+12179:0000,
+12180:0000,
+12181:0000,
+12182:0000,
+12183:0000,
+12184:0000,
+12185:0000,
+12186:0000,
+12187:0000,
+12188:0000,
+12189:0000,
+12190:0000,
+12191:0000,
+12192:0000,
+12193:0000,
+12194:0000,
+12195:0000,
+12196:0000,
+12197:0000,
+12198:0000,
+12199:0000,
+12200:0000,
+12201:0000,
+12202:0000,
+12203:0000,
+12204:0000,
+12205:0000,
+12206:0000,
+12207:0000,
+12208:0000,
+12209:0000,
+12210:0000,
+12211:0000,
+12212:0000,
+12213:0000,
+12214:0000,
+12215:0000,
+12216:0000,
+12217:0000,
+12218:0000,
+12219:0000,
+12220:0000,
+12221:0000,
+12222:0000,
+12223:0000,
+12224:0000,
+12225:0000,
+12226:0000,
+12227:0000,
+12228:0000,
+12229:0000,
+12230:0000,
+12231:0000,
+12232:0000,
+12233:0000,
+12234:0000,
+12235:0000,
+12236:0000,
+12237:0000,
+12238:0000,
+12239:0000,
+12240:0000,
+12241:0000,
+12242:0000,
+12243:0000,
+12244:0000,
+12245:0000,
+12246:0000,
+12247:0000,
+12248:0000,
+12249:0000,
+12250:0000,
+12251:0000,
+12252:0000,
+12253:0000,
+12254:0000,
+12255:0000,
+12256:0000,
+12257:0000,
+12258:0000,
+12259:0000,
+12260:0000,
+12261:0000,
+12262:0000,
+12263:0000,
+12264:0000,
+12265:0000,
+12266:0000,
+12267:0000,
+12268:0000,
+12269:0000,
+12270:0000,
+12271:0000,
+12272:0000,
+12273:0000,
+12274:0000,
+12275:0000,
+12276:0000,
+12277:0000,
+12278:0000,
+12279:0000,
+12280:0000,
+12281:0000,
+12282:0000,
+12283:0000,
+12284:0000,
+12285:0000,
+12286:0000,
+12287:0000,
+12288:0000,
+12289:0000,
+12290:0000,
+12291:0000,
+12292:0000,
+12293:0000,
+12294:0000,
+12295:0000,
+12296:0000,
+12297:0000,
+12298:0000,
+12299:0000,
+12300:0000,
+12301:0000,
+12302:0000,
+12303:0000,
+12304:0000,
+12305:0000,
+12306:0000,
+12307:0000,
+12308:0000,
+12309:0000,
+12310:0000,
+12311:0000,
+12312:0000,
+12313:0000,
+12314:0000,
+12315:0000,
+12316:0000,
+12317:0000,
+12318:0000,
+12319:0000,
+12320:0000,
+12321:0000,
+12322:0000,
+12323:0000,
+12324:0000,
+12325:0000,
+12326:0000,
+12327:0000,
+12328:0000,
+12329:0000,
+12330:0000,
+12331:0000,
+12332:0000,
+12333:0000,
+12334:0000,
+12335:0000,
+12336:0000,
+12337:0000,
+12338:0000,
+12339:0000,
+12340:0000,
+12341:0000,
+12342:0000,
+12343:0000,
+12344:0000,
+12345:0000,
+12346:0000,
+12347:0000,
+12348:0000,
+12349:0000,
+12350:0000,
+12351:0000,
+12352:0000,
+12353:0000,
+12354:0000,
+12355:0000,
+12356:0000,
+12357:0000,
+12358:0000,
+12359:0000,
+12360:0000,
+12361:0000,
+12362:0000,
+12363:0000,
+12364:0000,
+12365:0000,
+12366:0000,
+12367:0000,
+12368:0000,
+12369:0000,
+12370:0000,
+12371:0000,
+12372:0000,
+12373:0000,
+12374:0000,
+12375:0000,
+12376:0000,
+12377:0000,
+12378:0000,
+12379:0000,
+12380:0000,
+12381:0000,
+12382:0000,
+12383:0000,
+12384:0000,
+12385:0000,
+12386:0000,
+12387:0000,
+12388:0000,
+12389:0000,
+12390:0000,
+12391:0000,
+12392:0000,
+12393:0000,
+12394:0000,
+12395:0000,
+12396:0000,
+12397:0000,
+12398:0000,
+12399:0000,
+12400:0000,
+12401:0000,
+12402:0000,
+12403:0000,
+12404:0000,
+12405:0000,
+12406:0000,
+12407:0000,
+12408:0000,
+12409:0000,
+12410:0000,
+12411:0000,
+12412:0000,
+12413:0000,
+12414:0000,
+12415:0000,
+12416:0000,
+12417:0000,
+12418:0000,
+12419:0000,
+12420:0000,
+12421:0000,
+12422:0000,
+12423:0000,
+12424:0000,
+12425:0000,
+12426:0000,
+12427:0000,
+12428:0000,
+12429:0000,
+12430:0000,
+12431:0000,
+12432:0000,
+12433:0000,
+12434:0000,
+12435:0000,
+12436:0000,
+12437:0000,
+12438:0000,
+12439:0000,
+12440:0000,
+12441:0000,
+12442:0000,
+12443:0000,
+12444:0000,
+12445:0000,
+12446:0000,
+12447:0000,
+12448:0000,
+12449:0000,
+12450:0000,
+12451:0000,
+12452:0000,
+12453:0000,
+12454:0000,
+12455:0000,
+12456:0000,
+12457:0000,
+12458:0000,
+12459:0000,
+12460:0000,
+12461:0000,
+12462:0000,
+12463:0000,
+12464:0000,
+12465:0000,
+12466:0000,
+12467:0000,
+12468:0000,
+12469:0000,
+12470:0000,
+12471:0000,
+12472:0000,
+12473:0000,
+12474:0000,
+12475:0000,
+12476:0000,
+12477:0000,
+12478:0000,
+12479:0000,
+12480:0000,
+12481:0000,
+12482:0000,
+12483:0000,
+12484:0000,
+12485:0000,
+12486:0000,
+12487:0000,
+12488:0000,
+12489:0000,
+12490:0000,
+12491:0000,
+12492:0000,
+12493:0000,
+12494:0000,
+12495:0000,
+12496:0000,
+12497:0000,
+12498:0000,
+12499:0000,
+12500:0000,
+12501:0000,
+12502:0000,
+12503:0000,
+12504:0000,
+12505:0000,
+12506:0000,
+12507:0000,
+12508:0000,
+12509:0000,
+12510:0000,
+12511:0000,
+12512:0000,
+12513:0000,
+12514:0000,
+12515:0000,
+12516:0000,
+12517:0000,
+12518:0000,
+12519:0000,
+12520:0000,
+12521:0000,
+12522:0000,
+12523:0000,
+12524:0000,
+12525:0000,
+12526:0000,
+12527:0000,
+12528:0000,
+12529:0000,
+12530:0000,
+12531:0000,
+12532:0000,
+12533:0000,
+12534:0000,
+12535:0000,
+12536:0000,
+12537:0000,
+12538:0000,
+12539:0000,
+12540:0000,
+12541:0000,
+12542:0000,
+12543:0000,
+12544:0000,
+12545:0000,
+12546:0000,
+12547:0000,
+12548:0000,
+12549:0000,
+12550:0000,
+12551:0000,
+12552:0000,
+12553:0000,
+12554:0000,
+12555:0000,
+12556:0000,
+12557:0000,
+12558:0000,
+12559:0000,
+12560:0000,
+12561:0000,
+12562:0000,
+12563:0000,
+12564:0000,
+12565:0000,
+12566:0000,
+12567:0000,
+12568:0000,
+12569:0000,
+12570:0000,
+12571:0000,
+12572:0000,
+12573:0000,
+12574:0000,
+12575:0000,
+12576:0000,
+12577:0000,
+12578:0000,
+12579:0000,
+12580:0000,
+12581:0000,
+12582:0000,
+12583:0000,
+12584:0000,
+12585:0000,
+12586:0000,
+12587:0000,
+12588:0000,
+12589:0000,
+12590:0000,
+12591:0000,
+12592:0000,
+12593:0000,
+12594:0000,
+12595:0000,
+12596:0000,
+12597:0000,
+12598:0000,
+12599:0000,
+12600:0000,
+12601:0000,
+12602:0000,
+12603:0000,
+12604:0000,
+12605:0000,
+12606:0000,
+12607:0000,
+12608:0000,
+12609:0000,
+12610:0000,
+12611:0000,
+12612:0000,
+12613:0000,
+12614:0000,
+12615:0000,
+12616:0000,
+12617:0000,
+12618:0000,
+12619:0000,
+12620:0000,
+12621:0000,
+12622:0000,
+12623:0000,
+12624:0000,
+12625:0000,
+12626:0000,
+12627:0000,
+12628:0000,
+12629:0000,
+12630:0000,
+12631:0000,
+12632:0000,
+12633:0000,
+12634:0000,
+12635:0000,
+12636:0000,
+12637:0000,
+12638:0000,
+12639:0000,
+12640:0000,
+12641:0000,
+12642:0000,
+12643:0000,
+12644:0000,
+12645:0000,
+12646:4033,
+12647:0000,
+12648:0000,
+12649:0000,
+12650:0000,
+12651:0000,
+12652:0000,
+12653:0000,
+12654:0000,
+12655:0000,
+12656:0000,
+12657:0000,
+12658:0000,
+12659:0000,
+12660:0000,
+12661:3091,
+12662:0000,
+12663:948,
+12664:944,
+12665:942,
+12666:0000,
+12667:0000,
+12668:0000,
+12669:0000,
+12670:0000,
+12671:0000,
+12672:0000,
+12673:0000,
+12674:0000,
+12675:0000,
+12676:0000,
+12677:0000,
+12678:0000,
+12679:0000,
+12680:0000,
+12681:0000,
+12682:0000,
+12683:0000,
+12684:0000,
+12685:0000,
+12686:0000,
+12687:0000,
+12688:0000,
+12689:0000,
+12690:0000,
+12691:0000,
+12692:0000,
+12693:0000,
+12694:0000,
+12695:0000,
+12696:0000,
+12697:0000,
+12698:0000,
+12699:0000,
+12700:0000,
+12701:0000,
+12702:0000,
+12703:0000,
+12704:0000,
+12705:0000,
+12706:0000,
+12707:0000,
+12708:0000,
+12709:0000,
+12710:0000,
+12711:0000,
+12712:0000,
+12713:0000,
+12714:0000,
+12715:0000,
+12716:0000,
+12717:0000,
+12718:0000,
+12719:0000,
+12720:0000,
+12721:0000,
+12722:0000,
+12723:0000,
+12724:0000,
+12725:0000,
+12726:0000,
+12727:0000,
+12728:0000,
+12729:0000,
+12730:0000,
+12731:0000,
+12732:0000,
+12733:0000,
+12734:0000,
+12735:0000,
+12736:0000,
+12737:0000,
+12738:0000,
+12739:0000,
+12740:0000,
+12741:0000,
+12742:0000,
+12743:0000,
+12744:0000,
+12745:0000,
+12746:0000,
+12747:0000,
+12748:0000,
+12749:0000,
+12750:0000,
+12751:0000,
+12752:0000,
+12753:0000,
+12754:0000,
+12755:0000,
+12756:0000,
+12757:0000,
+12758:0000,
+12759:0000,
+12760:0000,
+12761:0000,
+12762:0000,
+12763:0000,
+12764:0000,
+12765:0000,
+12766:0000,
+12767:0000,
+12768:0000,
+12769:0000,
+12770:0000,
+12771:0000,
+12772:0000,
+12773:0000,
+12774:0000,
+12775:0000,
+12776:0000,
+12777:0000,
+12778:0000,
+12779:0000,
+12780:0000,
+12781:0000,
+12782:0000,
+12783:0000,
+12784:0000,
+12785:0000,
+12786:0000,
+12787:0000,
+12788:0000,
+12789:0000,
+12790:0000,
+12791:0000,
+12792:0000,
+12793:0000,
+12794:0000,
+12795:0000,
+12796:0000,
+12797:0000,
+12798:0000,
+12799:0000,
+12800:0000,
+12801:0000,
+12802:0000,
+12803:0000,
+12804:0000,
+12805:0000,
+12806:0000,
+12807:0000,
+12808:0000,
+12809:0000,
+12810:0000,
+12811:0000,
+12812:0000,
+12813:0000,
+12814:0000,
+12815:0000,
+12816:0000,
+12817:0000,
+12818:0000,
+12819:0000,
+12820:0000,
+12821:0000,
+12822:0000,
+12823:0000,
+12824:0000,
+12825:0000,
+12826:0000,
+12827:0000,
+12828:0000,
+12829:0000,
+12830:0000,
+12831:0000,
+12832:0000,
+12833:0000,
+12834:0000,
+12835:0000,
+12836:0000,
+12837:0000,
+12838:0000,
+12839:0000,
+12840:0000,
+12841:0000,
+12842:0000,
+12843:0000,
+12844:0000,
+12845:0000,
+12846:0000,
+12847:0000,
+12848:0000,
+12849:0000,
+12850:0000,
+12851:0000,
+12852:0000,
+12853:0000,
+12854:0000,
+12855:0000,
+12856:0000,
+12857:0000,
+12858:0000,
+12859:0000,
+12860:0000,
+12861:0000,
+12862:0000,
+12863:0000,
+12864:0000,
+12865:0000,
+12866:0000,
+12867:0000,
+12868:0000,
+12869:0000,
+12870:0000,
+12871:0000,
+12872:0000,
+12873:0000,
+12874:0000,
+12875:0000,
+12876:0000,
+12877:0000,
+12878:0000,
+12879:0000,
+12880:0000,
+12881:0000,
+12882:0000,
+12883:0000,
+12884:0000,
+12885:0000,
+12886:0000,
+12887:0000,
+12888:0000,
+12889:0000,
+12890:0000,
+12891:0000,
+12892:0000,
+12893:0000,
+12894:0000,
+12895:0000,
+12896:0000,
+12897:0000,
+12898:0000,
+12899:0000,
+12900:0000,
+12901:0000,
+12902:0000,
+12903:0000,
+12904:0000,
+12905:0000,
+12906:0000,
+12907:0000,
+12908:0000,
+12909:0000,
+12910:0000,
+12911:0000,
+12912:0000,
+12913:0000,
+12914:0000,
+12915:0000,
+12916:0000,
+12917:0000,
+12918:0000,
+12919:0000,
+12920:0000,
+12921:0000,
+12922:0000,
+12923:0000,
+12924:0000,
+12925:0000,
+12926:0000,
+12927:0000,
+12928:0000,
+12929:0000,
+12930:0000,
+12931:0000,
+12932:0000,
+12933:0000,
+12934:0000,
+12935:0000,
+12936:0000,
+12937:0000,
+12938:0000,
+12939:0000,
+12940:0000,
+12941:0000,
+12942:0000,
+12943:0000,
+12944:0000,
+12945:0000,
+12946:0000,
+12947:0000,
+12948:0000,
+12949:0000,
+12950:0000,
+12951:0000,
+12952:0000,
+12953:0000,
+12954:0000,
+12955:0000,
+12956:0000,
+12957:0000,
+12958:0000,
+12959:0000,
+12960:0000,
+12961:0000,
+12962:0000,
+12963:0000,
+12964:0000,
+12965:0000,
+12966:0000,
+12967:0000,
+12968:0000,
+12969:0000,
+12970:0000,
+12971:0000,
+12972:0000,
+12973:0000,
+12974:0000,
+12975:0000,
+12976:0000,
+12977:0000,
+12978:0000,
+12979:0000,
+12980:0000,
+12981:0000,
+12982:0000,
+12983:0000,
+12984:0000,
+12985:0000,
+12986:0000,
+12987:0000,
+12988:0000,
+12989:0000,
+12990:0000,
+12991:0000,
+12992:0000,
+12993:0000,
+12994:0000,
+12995:0000,
+12996:0000,
+12997:0000,
+12998:0000,
+12999:0000,
+13000:0000,
+13001:0000,
+13002:0000,
+13003:0000,
+13004:0000,
+13005:0000,
+13006:0000,
+13007:0000,
+13008:0000,
+13009:0000,
+13010:0000,
+13011:0000,
+13012:0000,
+13013:0000,
+13014:0000,
+13015:0000,
+13016:0000,
+13017:0000,
+13018:0000,
+13019:0000,
+13020:0000,
+13021:0000,
+13022:0000,
+13023:0000,
+13024:0000,
+13025:0000,
+13026:0000,
+13027:0000,
+13028:0000,
+13029:0000,
+13030:0000,
+13031:0000,
+13032:0000,
+13033:0000,
+13034:0000,
+13035:0000,
+13036:0000,
+13037:0000,
+13038:0000,
+13039:0000,
+13040:0000,
+13041:0000,
+13042:0000,
+13043:0000,
+13044:0000,
+13045:0000,
+13046:0000,
+13047:0000,
+13048:0000,
+13049:0000,
+13050:0000,
+13051:0000,
+13052:0000,
+13053:0000,
+13054:0000,
+13055:0000,
+13056:0000,
+13057:0000,
+13058:0000,
+13059:0000,
+13060:0000,
+13061:0000,
+13062:0000,
+13063:0000,
+13064:0000,
+13065:0000,
+13066:0000,
+13067:0000,
+13068:0000,
+13069:0000,
+13070:0000,
+13071:0000,
+13072:0000,
+13073:0000,
+13074:0000,
+13075:0000,
+13076:0000,
+13077:0000,
+13078:0000,
+13079:0000,
+13080:0000,
+13081:0000,
+13082:0000,
+13083:0000,
+13084:0000,
+13085:0000,
+13086:0000,
+13087:0000,
+13088:0000,
+13089:0000,
+13090:0000,
+13091:0000,
+13092:0000,
+13093:0000,
+13094:0000,
+13095:0000,
+13096:0000,
+13097:0000,
+13098:0000,
+13099:0000,
+13100:0000,
+13101:0000,
+13102:0000,
+13103:0000,
+13104:0000,
+13105:0000,
+13106:0000,
+13107:0000,
+13108:0000,
+13109:0000,
+13110:0000,
+13111:0000,
+13112:0000,
+13113:0000,
+13114:0000,
+13115:0000,
+13116:0000,
+13117:0000,
+13118:0000,
+13119:0000,
+13120:0000,
+13121:0000,
+13122:0000,
+13123:0000,
+13124:0000,
+13125:0000,
+13126:0000,
+13127:0000,
+13128:0000,
+13129:0000,
+13130:0000,
+13131:0000,
+13132:0000,
+13133:0000,
+13134:0000,
+13135:0000,
+13136:0000,
+13137:0000,
+13138:0000,
+13139:0000,
+13140:0000,
+13141:0000,
+13142:0000,
+13143:0000,
+13144:0000,
+13145:0000,
+13146:0000,
+13147:0000,
+13148:0000,
+13149:0000,
+13150:0000,
+13151:0000,
+13152:0000,
+13153:0000,
+13154:0000,
+13155:0000,
+13156:0000,
+13157:0000,
+13158:0000,
+13159:0000,
+13160:0000,
+13161:0000,
+13162:0000,
+13163:0000,
+13164:0000,
+13165:0000,
+13166:0000,
+13167:0000,
+13168:0000,
+13169:0000,
+13170:0000,
+13171:0000,
+13172:0000,
+13173:0000,
+13174:0000,
+13175:0000,
+13176:0000,
+13177:0000,
+13178:0000,
+13179:0000,
+13180:0000,
+13181:0000,
+13182:0000,
+13183:0000,
+13184:0000,
+13185:0000,
+13186:0000,
+13187:0000,
+13188:0000,
+13189:0000,
+13190:0000,
+13191:0000,
+13192:0000,
+13193:0000,
+13194:0000,
+13195:0000,
+13196:0000,
+13197:0000,
+13198:0000,
+13199:0000,
+13200:0000,
+13201:0000,
+13202:0000,
+13203:0000,
+13204:0000,
+13205:0000,
+13206:0000,
+13207:0000,
+13208:0000,
+13209:0000,
+13210:0000,
+13211:0000,
+13212:0000,
+13213:0000,
+13214:0000,
+13215:0000,
+13216:0000,
+13217:0000,
+13218:0000,
+13219:0000,
+13220:0000,
+13221:0000,
+13222:0000,
+13223:0000,
+13224:0000,
+13225:0000,
+13226:0000,
+13227:0000,
+13228:0000,
+13229:0000,
+13230:0000,
+13231:0000,
+13232:0000,
+13233:0000,
+13234:0000,
+13235:0000,
+13236:0000,
+13237:0000,
+13238:0000,
+13239:0000,
+13240:0000,
+13241:0000,
+13242:0000,
+13243:0000,
+13244:0000,
+13245:0000,
+13246:0000,
+13247:0000,
+13248:0000,
+13249:0000,
+13250:0000,
+13251:0000,
+13252:0000,
+13253:0000,
+13254:0000,
+13255:0000,
+13256:0000,
+13257:0000,
+13258:6532,
+13259:0000,
+13260:0000,
+13261:0000,
+13262:0000,
+13263:0000,
+13264:0000,
+13265:0000,
+13266:0000,
+13267:0000,
+13268:0000,
+13269:0000,
+13270:0000,
+13271:0000,
+13272:0000,
+13273:0000,
+13274:0000,
+13275:0000,
+13276:0000,
+13277:0000,
+13278:0000,
+13279:0000,
+13280:0000,
+13281:0000,
+13282:0000,
+13283:0000,
+13284:0000,
+}
+const otbm2json = require("../../otbm2json");
+const fs = require('fs');
+const mapData = otbm2json.read("svargrond.otbm");
+var gg = {};
+function replaceItem(itemid) {
+	if (replaceArray[itemid]) {
+		return replaceArray[itemid];
+	}
+	else {
+		if (!gg[itemid])
+		gg[itemid] = itemid;
+		return 0;
+	}
+}
+
+function replaceItemRecursive(item) {
+    if (item.type === otbm2json.HEADERS.OTBM_ITEM) {
+        item.id = replaceItem(item.id);
+        if (item.content && item.content.length) {
+            item.content.forEach(function (containerItem) {
+                replaceItemRecursive(containerItem)
+            });
+        }
+    }
+}
+
+// Go over all nodes
+mapData.data.nodes.forEach(function (n) {
+    n.features.forEach(function (e) {
+        if (e.type !== otbm2json.HEADERS.OTBM_TILE_AREA) return;
+        e.tiles.forEach(function (tile) {
+            tile.tileid = replaceItem(tile.tileid);
+            if (tile.items && tile.items.length) {
+                tile.items.forEach(function (item) {
+                    replaceItemRecursive(item)
+                });
+            }
+        });
+		
+    });
+
+	
+	 fs.writeFile("test.txt", JSON.stringify(gg), function(err) {
+	
+	 if(err) {
+		 return console.log(err);
+	 }
+	
+	 console.log("The file was saved!");
+	 }); 
+		
+});
+
+// Write the output to OTBM using the library
+otbm2json.write("svargrond_after.otbm", mapData);
+    
+
+ + - How you edit dat/spr? With old object builder exe which can be found somewhere in otland tools thread. + - How to open map? With the nostalrius map editor which you can find it here. https://github.com/Ezzz-dev/Map-Editor But you will need to rebuild dat/spr to 7.72 signatures with object builder. + +# Sabrehaven Tools + - https://github.com/ErikasKontenis/SabrehavenRme + - https://github.com/Inconcessus/OTBM2JSON + - https://github.com/ErikasKontenis/SabrehavenStaticLibrary + +# Credits + - Erikas Kontenis for the Sabrehaven Platform + - CustomTibia OTLand user for the mapping 8.0 custom areas + - Nostalrius and TheForgottenServer Team + +# Some pictures from in-game +![bank](https://user-images.githubusercontent.com/33052216/213016984-bfdbe92b-3241-4db8-9dcc-1fcb53d4de77.png) +![color_loot](https://user-images.githubusercontent.com/33052216/213017027-8163742a-4f1e-4b17-b120-8ee3e0a359d3.png) +![ingameshop](https://user-images.githubusercontent.com/33052216/213017035-f915f72b-e811-42ff-8f4b-83c43cda1639.png) +![inq](https://user-images.githubusercontent.com/33052216/213017075-33c16144-f29c-4d47-b0e4-637b03e5e067.png) +![market](https://user-images.githubusercontent.com/33052216/213017286-060c040c-aa98-449d-a106-f32b76d88a1a.png) +![mounts_shaders](https://user-images.githubusercontent.com/33052216/213017319-22e4a758-7eb5-43a6-9bf6-ff095d236302.png) + + + + + + diff --git a/app/SabrehavenServer/SabrehavenOTClient/LICENSE b/app/SabrehavenServer/SabrehavenOTClient/LICENSE new file mode 100644 index 0000000..509b9b2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/LICENSE @@ -0,0 +1,22 @@ +OTClientV8 is made available under the MIT License + +Copyright (c) 2010-2017 OTClient +Copyright (c) 2018-2021 OTClientV8 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/SabrehavenServer/SabrehavenOTClient/README.md b/app/SabrehavenServer/SabrehavenOTClient/README.md new file mode 100644 index 0000000..cd4b23e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/README.md @@ -0,0 +1,99 @@ +# OTClientV8 + +OTClientV8 is highly optimized, cross-platform tile based 2d game engine built with c++17, lua, physfs, OpenGL ES 2.0 and OpenAL. +It has been created as alternative client for game called [Tibia](https://tibia.com/), but now it's much more functional and powerful. +It works well even on 12 years old computers. In April 2021 it reached 290k unique installations, including 80k android installations. + +Supported platforms: +- Windows (min. Windows 7) +- Android (min. 5.0) + +On Windows you may need to install https://aka.ms/vs/16/release/vc_redist.x86.exe to make it work. + +### Forum: http://otclient.net +### Discord: https://discord.gg/feySup6 +### Website: http://otclient.ovh +### Wiki: https://github.com/OTCv8/otclientv8/wiki + +## Version for developers + +In this repository, you can find clean, always up-to-date, ready to use version of OTClientv8. Most commits from version 3.0 are automatic using GitHub Actions. If you want to help with development, please visit repository for developers - https://github.com/OTCv8/otcv8-dev + +## FEATURES +- Rewritten and optimized rendering (60 fps on 11 years old computer) +- Better DirectX9 and DirectX11 support +- Adaptive rendering (automated graphics optimizations) +- Rewritten and optimized light rendering +- Rewritten path finding and auto walking +- Rewritten walking system with animations +- HTTP/HTTPS lua API with JSON support +- WebSocket lua API +- Auto updater with failsafe (recovery) mode +- New filesystem +- File encryption and compression +- Automatic diagnostic system +- Refreshed interface +- New crash and error handler +- New HTTP login protocol +- Ingame shop +- Updated hotkey manager +- Updated and optimized battle list +- Crosshair, floor fading, extra health/mana bars and panels +- Much more client options +- Removed a lot of useless and outdated things +- Advanced bot +- Linux version +- Full tibia 11.00 support +- Layouts +- New login server (with ingame account and character creation) +- Support for proxies to lower latency and protect against DDoS (extra paid option) +- Bot protection (extra paid option) + +### And hundreds of smaller features, optimizations and bug fixes! +### Check out [Wiki page](https://github.com/OTCv8/otclientv8/wiki) to see how activate and use new features + +### Old tools, like updater and tutorials has been moved to: [OTCv8/otcv8-tools](https://github.com/OTCv8/otcv8-tools) +### There's github repo of tfs 1.3 with otclientv8 features: [OTCv8/otclientv8-tfs](https://github.com/OTCv8/forgottenserver) + +## Quick Start for players + +Download whole repository and run one of binary file. + +## Quick Start for server owners + +Open `init.lua` and edit: + +``` +-- CONFIG +APP_NAME = "otclientv8" -- important, change it, it's name for config dir and files in appdata +APP_VERSION = 1337 -- client version for updater and login to indentify outdated client +DEFAULT_LAYOUT = "retro" + +-- If you don't use updater or other service, set it to updater = "" +Services = { + website = "http://otclient.ovh", -- currently not used + updater = "http://otclient.ovh/api/updater.php", + news = "http://otclient.ovh/api/news.php", + stats = "", + crash = "http://otclient.ovh/api/crash.php", + feedback = "http://otclient.ovh/api/feedback.php" +} + +-- Servers accept http login url or ip:port:version +Servers = { + OTClientV8 = "http://otclient.ovh/api/login.php", + OTClientV8proxy = "http://otclient.ovh/api/login.php?proxy=1", + OTClientV8classic = "otclient.ovh:7171:1099", + OTClientV8cwithfeatures = "otclient.ovh:7171:1099:25:30:80:90", +} +ALLOW_CUSTOM_SERVERS = true -- if true it will show option ANOTHER on server list +-- CONFIG END +``` + +Also remember to add your sprite and data file to data/things + +That's it, you're ready to use OTClientV8. + +DirectX version requires 3 dlls: libEGL.dll libGLESv2.dll d3dcompiler_47.dll + +If it can't start (missing dlls) then user need to install visual studio 2019 redistributable x86: https://aka.ms/vs/16/release/vc_redist.x86.exe diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/cursors/cursors.otml b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/cursors.otml new file mode 100644 index 0000000..36c1f39 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/cursors.otml @@ -0,0 +1,13 @@ +Cursors + target: + image: targetcursor + hot-spot: 9 9 + horizontal: + image: horizontalcursor + hot-spot: 9 4 + vertical: + image: verticalcursor + hot-spot: 4 9 + text: + image: textcursor + hot-spot: 4 9 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/cursors/horizontalcursor.png b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/horizontalcursor.png new file mode 100644 index 0000000..6ba0f27 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/horizontalcursor.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/cursors/targetcursor.png b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/targetcursor.png new file mode 100644 index 0000000..3ce607b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/targetcursor.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/cursors/textcursor.png b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/textcursor.png new file mode 100644 index 0000000..abcd823 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/textcursor.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/cursors/verticalcursor.png b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/verticalcursor.png new file mode 100644 index 0000000..1f31d1c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/cursors/verticalcursor.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.otfont new file mode 100644 index 0000000..14de8dd --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.otfont @@ -0,0 +1,6 @@ +Font + name: cipsoftFont + texture: cipsoftFont + height: 8 + glyph-size: 8 8 + space-width: 2 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.png new file mode 100644 index 0000000..65efe25 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/cipsoftFont.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px.otfont new file mode 100644 index 0000000..bdf5cf4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px.otfont @@ -0,0 +1,6 @@ +Font + name: sans-bold-16px + texture: sans-bold-16px_cp1252 + height: 20 + glyph-size: 24 24 + space-width: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px_cp1252.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px_cp1252.png new file mode 100644 index 0000000..eef9474 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/sans-bold-16px_cp1252.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.otfont new file mode 100644 index 0000000..12c83bf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.otfont @@ -0,0 +1,7 @@ +Font + name: small-9px + texture: small-9px + height: 9 + glyph-size: 9 9 + space-width: 3 + spacing: 1 0 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.png new file mode 100644 index 0000000..37643a1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/small-9px.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.otfont new file mode 100644 index 0000000..957da0d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.otfont @@ -0,0 +1,8 @@ +Font + name: terminus-10px + texture: terminus-10px + height: 12 + y-offset: 0 + glyph-size: 16 16 + fixed-glyph-width: 6 + space-width: 6 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.png new file mode 100644 index 0000000..4e8f500 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-10px.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.otfont new file mode 100644 index 0000000..0814f4a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.otfont @@ -0,0 +1,8 @@ +Font + name: terminus-14px-bold + texture: terminus-14px-bold + height: 16 + y-offset: 2 + glyph-size: 16 16 + fixed-glyph-width: 8 + space-width: 8 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.png new file mode 100644 index 0000000..99962a5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/terminus-14px-bold.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised.otfont new file mode 100644 index 0000000..c6cac89 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised.otfont @@ -0,0 +1,7 @@ +Font + name: verdana-11px-antialised + texture: verdana-11px-antialised_cp1252 + height: 14 + glyph-size: 16 16 + space-width: 4 + default: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1250.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1250.png new file mode 100644 index 0000000..ba9e6cb Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1250.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1252.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1252.png new file mode 100644 index 0000000..c01e218 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-antialised_cp1252.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome.otfont new file mode 100644 index 0000000..e39cf0b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome.otfont @@ -0,0 +1,6 @@ +Font + name: verdana-11px-monochrome + texture: verdana-11px-monochrome_cp1252 + height: 14 + glyph-size: 16 16 + space-width: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1250.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1250.png new file mode 100644 index 0000000..11002ef Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1250.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1252.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1252.png new file mode 100644 index 0000000..d053ca5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-monochrome_cp1252.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded.otfont new file mode 100644 index 0000000..9c18e23 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded.otfont @@ -0,0 +1,8 @@ +Font + name: verdana-11px-rounded + texture: verdana-11px-rounded_cp1252 + height: 16 + glyph-size: 16 16 + y-offset: -2 + spacing: -1 -3 + space-width: 4 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1250.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1250.png new file mode 100644 index 0000000..bcf4638 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1250.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1252.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1252.png new file mode 100644 index 0000000..0ff0322 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-11px-rounded_cp1252.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.otfont new file mode 100644 index 0000000..beaf9ba --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.otfont @@ -0,0 +1,8 @@ +Font + name: verdana-9px-bold + texture: verdana-9px-bold + height: 12 + glyph-size: 13 13 + space-width: 4 + spacing: 0 0 + diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.png new file mode 100644 index 0000000..51ccd06 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-bold.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.otfont new file mode 100644 index 0000000..5e32e32 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.otfont @@ -0,0 +1,6 @@ +Font + name: verdana-9px-italic + texture: verdana-9px-italic + height: 12 + glyph-size: 13 13 + space-width: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.png new file mode 100644 index 0000000..b3646e1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px-italic.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.otfont b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.otfont new file mode 100644 index 0000000..f899759 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.otfont @@ -0,0 +1,7 @@ +Font + name: verdana-9px + texture: verdana-9px + height: 13 + glyph-size: 13 13 + space-width: 3 + spacing: 0 -4 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.png b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.png new file mode 100644 index 0000000..9f566ce Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/fonts/verdana-9px.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/background.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/background.png new file mode 100644 index 0000000..042269e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/background.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/health1.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/health1.png new file mode 100644 index 0000000..41579ba Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/health1.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/mana1.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/mana1.png new file mode 100644 index 0000000..c635dd5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/bars/mana1.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/clienticon.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/clienticon.png new file mode 100644 index 0000000..4200fb5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/clienticon.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/default.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/default.png new file mode 100644 index 0000000..1715d95 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/default.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/full.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/full.png new file mode 100644 index 0000000..b5c2173 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/crosshair/full.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/de.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/de.png new file mode 100644 index 0000000..103b04d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/de.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/en.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/en.png new file mode 100644 index 0000000..f36a688 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/en.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/es.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/es.png new file mode 100644 index 0000000..fda8ccc Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/es.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pl.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pl.png new file mode 100644 index 0000000..52795d0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pl.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pt.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pt.png new file mode 100644 index 0000000..b0c7a22 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/pt.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/sv.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/sv.png new file mode 100644 index 0000000..60932e2 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/flags/sv.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/actionbarslot.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/actionbarslot.png new file mode 100644 index 0000000..448b71e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/actionbarslot.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_monsters.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_monsters.png new file mode 100644 index 0000000..9c01361 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_monsters.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_npcs.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_npcs.png new file mode 100644 index 0000000..d19635d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_npcs.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_party.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_party.png new file mode 100644 index 0000000..77fd67e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_party.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_players.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_players.png new file mode 100644 index 0000000..84c74b9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_players.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_skulls.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_skulls.png new file mode 100644 index 0000000..3f3a2ec Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/battle/battle_skulls.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_empty.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_empty.png new file mode 100644 index 0000000..7868b9f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_empty.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_full.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_full.png new file mode 100644 index 0000000..dd9ecf4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/left_full.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_empty.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_empty.png new file mode 100644 index 0000000..a20de6a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_empty.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_full.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_full.png new file mode 100644 index 0000000..8d866d2 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/circle/right_full.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/chasemode.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/chasemode.png new file mode 100644 index 0000000..f3ef705 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/chasemode.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightbalanced.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightbalanced.png new file mode 100644 index 0000000..3113538 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightbalanced.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightdefensive.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightdefensive.png new file mode 100644 index 0000000..3829a21 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightdefensive.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightoffensive.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightoffensive.png new file mode 100644 index 0000000..2fb6e79 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/fightoffensive.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/mount.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/mount.png new file mode 100644 index 0000000..879646a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/mount.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/pvp.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/pvp.png new file mode 100644 index 0000000..b3087c0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/pvp.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/redfistmode.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/redfistmode.png new file mode 100644 index 0000000..e197210 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/redfistmode.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/safefight.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/safefight.png new file mode 100644 index 0000000..2117067 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/safefight.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitedovemode.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitedovemode.png new file mode 100644 index 0000000..29d4e0c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitedovemode.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitehandmode.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitehandmode.png new file mode 100644 index 0000000..c010d82 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/whitehandmode.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/yellowhandmode.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/yellowhandmode.png new file mode 100644 index 0000000..13a0b02 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/combatmodes/yellowhandmode.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/channels.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/channels.png new file mode 100644 index 0000000..885ba22 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/channels.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/clearchannel.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/clearchannel.png new file mode 100644 index 0000000..201bd82 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/clearchannel.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/closechannel.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/closechannel.png new file mode 100644 index 0000000..0b130d4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/closechannel.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/ignore.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/ignore.png new file mode 100644 index 0000000..8ed2df6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/ignore.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/leftarrow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/leftarrow.png new file mode 100644 index 0000000..7e065f5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/leftarrow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/rightarrow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/rightarrow.png new file mode 100644 index 0000000..4c51e9f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/rightarrow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/say.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/say.png new file mode 100644 index 0000000..82ffb5f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/say.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/whisper.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/whisper.png new file mode 100644 index 0000000..440ac16 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/whisper.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/yell.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/yell.png new file mode 100644 index 0000000..398bc28 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/console/yell.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_other.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_other.png new file mode 100644 index 0000000..6f6fa60 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_other.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_own.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_own.png new file mode 100644 index 0000000..0abcb94 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/creaturetype/summon_own.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/dangerous.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/dangerous.png new file mode 100644 index 0000000..c927c67 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/dangerous.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_blue.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_blue.png new file mode 100644 index 0000000..a018e3d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_blue.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_green.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_green.png new file mode 100644 index 0000000..e5ead37 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_green.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_member.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_member.png new file mode 100644 index 0000000..7af0ad9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_member.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_other.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_other.png new file mode 100644 index 0000000..2b2d5ad Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_other.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_red.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_red.png new file mode 100644 index 0000000..94d712a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/emblems/emblem_red.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/100percent.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/100percent.png new file mode 100644 index 0000000..9d26eb5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/100percent.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/clear.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/clear.png new file mode 100644 index 0000000..f18e667 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/clear.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_empty.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_empty.png new file mode 100644 index 0000000..641f14d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_empty.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_green.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_green.png new file mode 100644 index 0000000..19e4a92 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/imbue_green.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot.png new file mode 100644 index 0000000..d2b1e05 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_disabled.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_disabled.png new file mode 100644 index 0000000..5ac8e13 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_disabled.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_inactive.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_inactive.png new file mode 100644 index 0000000..11275ce Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/imbuing/slot_inactive.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/cross.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/cross.png new file mode 100644 index 0000000..fa8a7ad Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/cross.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag0.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag0.png new file mode 100644 index 0000000..1b80e29 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag0.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag1.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag1.png new file mode 100644 index 0000000..560bf79 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag1.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag10.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag10.png new file mode 100644 index 0000000..7cba49e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag10.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag11.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag11.png new file mode 100644 index 0000000..688175c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag11.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag12.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag12.png new file mode 100644 index 0000000..63ce6bf Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag12.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag13.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag13.png new file mode 100644 index 0000000..1ffb377 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag13.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag14.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag14.png new file mode 100644 index 0000000..c9d5182 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag14.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag15.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag15.png new file mode 100644 index 0000000..fc9d692 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag15.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag16.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag16.png new file mode 100644 index 0000000..da226e1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag16.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag17.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag17.png new file mode 100644 index 0000000..4000460 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag17.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag18.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag18.png new file mode 100644 index 0000000..7a625e7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag18.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag19.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag19.png new file mode 100644 index 0000000..8f46613 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag19.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag2.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag2.png new file mode 100644 index 0000000..226eb55 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag2.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag3.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag3.png new file mode 100644 index 0000000..1d7f8ef Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag3.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag4.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag4.png new file mode 100644 index 0000000..3f53f9d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag4.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag5.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag5.png new file mode 100644 index 0000000..8badc69 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag5.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag6.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag6.png new file mode 100644 index 0000000..8b71310 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag6.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag7.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag7.png new file mode 100644 index 0000000..866d079 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag7.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag8.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag8.png new file mode 100644 index 0000000..3c98633 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag8.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag9.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag9.png new file mode 100644 index 0000000..7a625e7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flag9.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flagcheckbox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flagcheckbox.png new file mode 100644 index 0000000..b479c40 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/flagcheckbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_down.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_down.png new file mode 100644 index 0000000..b508610 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_down.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_up.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_up.png new file mode 100644 index 0000000..804aa9e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/floor_up.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_in.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_in.png new file mode 100644 index 0000000..cca4c63 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_in.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_out.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_out.png new file mode 100644 index 0000000..6a56dd4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/minimap/zoom_out.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/attack.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/attack.png new file mode 100644 index 0000000..bea4701 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/attack.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/chat.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/chat.png new file mode 100644 index 0000000..ae3002b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/chat.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/follow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/follow.png new file mode 100644 index 0000000..760487c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/follow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad.png new file mode 100644 index 0000000..7198080 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad_pointer.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad_pointer.png new file mode 100644 index 0000000..c8385d3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/keypad_pointer.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/look.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/look.png new file mode 100644 index 0000000..bbdf54e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/look.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/use.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/use.png new file mode 100644 index 0000000..596954d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/mobile/use.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_chat.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_chat.png new file mode 100644 index 0000000..b0da15e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_chat.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_quest.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_quest.png new file mode 100644 index 0000000..dcc8860 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_quest.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_trade.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_trade.png new file mode 100644 index 0000000..87db9f0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_trade.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_tradequest.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_tradequest.png new file mode 100644 index 0000000..7f90a6c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/npcicons/icon_tradequest.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue.png new file mode 100644 index 0000000..2d05797 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_not_shared.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_not_shared.png new file mode 100644 index 0000000..6bd6a78 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_not_shared.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_shared.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_shared.png new file mode 100644 index 0000000..4cdc2b7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_shared.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_white.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_white.png new file mode 100644 index 0000000..f1aa8fe Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_blue_white.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_gray.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_gray.png new file mode 100644 index 0000000..aa4689e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_gray.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow.png new file mode 100644 index 0000000..eaee81c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_not_shared.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_not_shared.png new file mode 100644 index 0000000..85b0b30 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_not_shared.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_shared.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_shared.png new file mode 100644 index 0000000..196c4fd Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_shared.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_white.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_white.png new file mode 100644 index 0000000..7dc9899 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/shields/shield_yellow_white.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skull_socket.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skull_socket.png new file mode 100644 index 0000000..a2d30e2 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skull_socket.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_black.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_black.png new file mode 100644 index 0000000..8d3ddc0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_black.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_green.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_green.png new file mode 100644 index 0000000..382461f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_green.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_orange.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_orange.png new file mode 100644 index 0000000..0c906c1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_orange.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_red.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_red.png new file mode 100644 index 0000000..67245fa Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_red.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_white.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_white.png new file mode 100644 index 0000000..e2c3d55 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_white.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_yellow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_yellow.png new file mode 100644 index 0000000..2994f8e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/skulls/skull_yellow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo-blessed.png new file mode 100644 index 0000000..bed31d7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo.png new file mode 100644 index 0000000..345415f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/ammo.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back-blessed.png new file mode 100644 index 0000000..b369e6e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back.png new file mode 100644 index 0000000..dc874a7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/back.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body-blessed.png new file mode 100644 index 0000000..caa01ba Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body.png new file mode 100644 index 0000000..78dcbe2 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/body.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/coins.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/coins.png new file mode 100644 index 0000000..c8fe570 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/coins.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet-blessed.png new file mode 100644 index 0000000..29c011f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet.png new file mode 100644 index 0000000..4bdfd5f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/feet.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger-blessed.png new file mode 100644 index 0000000..575f34d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger.png new file mode 100644 index 0000000..61dec1e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/finger.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head-blessed.png new file mode 100644 index 0000000..418f4f6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head.png new file mode 100644 index 0000000..f2f3782 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/head.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand-blessed.png new file mode 100644 index 0000000..6140de8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand.png new file mode 100644 index 0000000..7ca84cf Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/left-hand.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs-blessed.png new file mode 100644 index 0000000..759f13b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs.png new file mode 100644 index 0000000..145121a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/legs.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck-blessed.png new file mode 100644 index 0000000..e3b6875 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck.png new file mode 100644 index 0000000..94a4e55 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/neck.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/purse.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/purse.png new file mode 100644 index 0000000..b2667a1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/purse.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand-blessed.png new file mode 100644 index 0000000..4a6bd9b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand.png new file mode 100644 index 0000000..0aa355b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/right-hand.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/soulcap.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/soulcap.png new file mode 100644 index 0000000..174d0df Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/slots/soulcap.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/cooldowns.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/cooldowns.png new file mode 100644 index 0000000..a8c9d64 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/cooldowns.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/defaultspells.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/defaultspells.png new file mode 100644 index 0000000..d1926f3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/spells/defaultspells.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/bleeding.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/bleeding.png new file mode 100644 index 0000000..024ee7e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/bleeding.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/burning.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/burning.png new file mode 100644 index 0000000..9d503ca Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/burning.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/cursed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/cursed.png new file mode 100644 index 0000000..6171bd9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/cursed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/dazzled.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/dazzled.png new file mode 100644 index 0000000..01e42ac Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/dazzled.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drowning.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drowning.png new file mode 100644 index 0000000..88c4dad Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drowning.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drunk.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drunk.png new file mode 100644 index 0000000..e83af44 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/drunk.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/electrified.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/electrified.png new file mode 100644 index 0000000..38e67a8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/electrified.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/freezing.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/freezing.png new file mode 100644 index 0000000..04acfb0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/freezing.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/haste.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/haste.png new file mode 100644 index 0000000..9f39a96 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/haste.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/hungry.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/hungry.png new file mode 100644 index 0000000..829e191 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/hungry.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/logout_block.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/logout_block.png new file mode 100644 index 0000000..4244dfe Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/logout_block.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/magic_shield.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/magic_shield.png new file mode 100644 index 0000000..4286a01 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/magic_shield.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/poisoned.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/poisoned.png new file mode 100644 index 0000000..3aae9cc Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/poisoned.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone.png new file mode 100644 index 0000000..741f4df Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone_block.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone_block.png new file mode 100644 index 0000000..47bcade Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/protection_zone_block.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/slowed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/slowed.png new file mode 100644 index 0000000..b1ab240 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/slowed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/strengthened.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/strengthened.png new file mode 100644 index 0000000..29e827d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/states/strengthened.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/icons.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/icons.png new file mode 100644 index 0000000..e0d67cc Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/icons.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/vipcheckbox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/vipcheckbox.png new file mode 100644 index 0000000..b479c40 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/game/viplist/vipcheckbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/loading.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/loading.png new file mode 100644 index 0000000..de8dff6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/loading.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/audio.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/audio.png new file mode 100644 index 0000000..e3aee23 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/audio.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/console.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/console.png new file mode 100644 index 0000000..d9ce1db Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/console.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/extras.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/extras.png new file mode 100644 index 0000000..ace46d8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/extras.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/game.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/game.png new file mode 100644 index 0000000..40892e4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/game.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/graphics.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/graphics.png new file mode 100644 index 0000000..ace46d8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/optionstab/graphics.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/brazil.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/brazil.png new file mode 100644 index 0000000..0c75047 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/brazil.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/gold.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/gold.png new file mode 100644 index 0000000..5bb7700 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/gold.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/rainbow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/rainbow.png new file mode 100644 index 0000000..2ee8654 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/rainbow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/stars.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/stars.png new file mode 100644 index 0000000..722e390 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/stars.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/sweden.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/sweden.png new file mode 100644 index 0000000..e7a52d4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/shaders/sweden.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio.png new file mode 100644 index 0000000..7bac9e9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio_mute.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio_mute.png new file mode 100644 index 0000000..a7dda9a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/audio_mute.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/battle.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/battle.png new file mode 100644 index 0000000..ed67347 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/battle.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/bot.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/bot.png new file mode 100644 index 0000000..8dc7d11 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/bot.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/buttons.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/buttons.png new file mode 100644 index 0000000..01cb280 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/buttons.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/combatcontrols.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/combatcontrols.png new file mode 100644 index 0000000..f39523e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/combatcontrols.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/cooldowns.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/cooldowns.png new file mode 100644 index 0000000..0e66ccf Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/cooldowns.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/debug.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/debug.png new file mode 100644 index 0000000..9768e4c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/debug.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/healthinfo.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/healthinfo.png new file mode 100644 index 0000000..c398bf6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/healthinfo.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/hotkeys.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/hotkeys.png new file mode 100644 index 0000000..9f0c25e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/hotkeys.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/inventory.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/inventory.png new file mode 100644 index 0000000..27876d9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/inventory.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/keypad.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/keypad.png new file mode 100644 index 0000000..ea01039 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/keypad.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/login.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/login.png new file mode 100644 index 0000000..55ec697 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/login.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/logout.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/logout.png new file mode 100644 index 0000000..91e2354 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/logout.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/minimap.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/minimap.png new file mode 100644 index 0000000..8ec6efe Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/minimap.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/modulemanager.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/modulemanager.png new file mode 100644 index 0000000..8089991 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/modulemanager.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/motd.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/motd.png new file mode 100644 index 0000000..01cb280 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/motd.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/options.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/options.png new file mode 100644 index 0000000..3a8aaf7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/options.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/particles.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/particles.png new file mode 100644 index 0000000..a52cc24 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/particles.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/prey.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/prey.png new file mode 100644 index 0000000..40346df Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/prey.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/questlog.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/questlog.png new file mode 100644 index 0000000..0ad6ea4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/questlog.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/shop.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/shop.png new file mode 100644 index 0000000..21d6980 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/shop.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/skills.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/skills.png new file mode 100644 index 0000000..52deb10 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/skills.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/spelllist.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/spelllist.png new file mode 100644 index 0000000..e1c01cc Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/spelllist.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/terminal.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/terminal.png new file mode 100644 index 0000000..768c2e9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/terminal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/unjustifiedpoints.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/unjustifiedpoints.png new file mode 100644 index 0000000..67245fa Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/unjustifiedpoints.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/viplist.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/viplist.png new file mode 100644 index 0000000..d0e69e7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/viplist.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomin.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomin.png new file mode 100644 index 0000000..bce83ed Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomin.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomout.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomout.png new file mode 100644 index 0000000..1dd6f96 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/topbuttons/zoomout.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/android.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/android.png new file mode 100644 index 0000000..6d14f24 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/android.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_horizontal.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_horizontal.png new file mode 100644 index 0000000..a0ec72c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_horizontal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_vertical.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_vertical.png new file mode 100644 index 0000000..d48aba3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/arrow_vertical.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_popupmenu.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_popupmenu.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_popupmenu.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_rounded.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_rounded.png new file mode 100644 index 0000000..eebfa2f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_rounded.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_square.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_square.png new file mode 100644 index 0000000..103f617 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_square.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top.png new file mode 100644 index 0000000..73703a0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top_blink.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top_blink.png new file mode 100644 index 0000000..9a66927 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_top_blink.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_topgame.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_topgame.png new file mode 100644 index 0000000..c6f115f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/button_topgame.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/checkbox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/checkbox.png new file mode 100644 index 0000000..e3aa5e1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/checkbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/colorbox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/colorbox.png new file mode 100644 index 0000000..9d2ef7f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/colorbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox.png new file mode 100644 index 0000000..8ef29ab Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_rounded.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_rounded.png new file mode 100644 index 0000000..3c00702 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_rounded.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_square.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_square.png new file mode 100644 index 0000000..8ef29ab Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/combobox_square.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/discord.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/discord.png new file mode 100644 index 0000000..2e0d957 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/discord.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/icon_add.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/icon_add.png new file mode 100644 index 0000000..f820a0b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/icon_add.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/ios.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/ios.png new file mode 100644 index 0000000..6c0ecb4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/ios.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item-blessed.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item-blessed.png new file mode 100644 index 0000000..41db9d9 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item-blessed.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item.png new file mode 100644 index 0000000..4230ace Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/item.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/menubox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/menubox.png new file mode 100644 index 0000000..307e5fd Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/menubox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow.png new file mode 100644 index 0000000..a8647c8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow_buttons.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow_buttons.png new file mode 100644 index 0000000..8bf5271 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/miniwindow_buttons.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/otcicon.rc b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/otcicon.rc new file mode 100644 index 0000000..52a9a6e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/otcicon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "otcicon.ico" \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_bottom.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_bottom.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_bottom.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_container.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_container.png new file mode 100644 index 0000000..23102a3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_container.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_content.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_content.png new file mode 100644 index 0000000..209b28f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_content.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_flat.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_flat.png new file mode 100644 index 0000000..d32beb7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_flat.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_lightflat.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_lightflat.png new file mode 100644 index 0000000..d32beb7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_lightflat.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_map.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_map.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_map.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_side.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_side.png new file mode 100644 index 0000000..8b9da27 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_side.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_top.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_top.png new file mode 100644 index 0000000..649874a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/panel_top.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/progressbar.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/progressbar.png new file mode 100644 index 0000000..d139932 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/progressbar.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/qauth.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/qauth.png new file mode 100644 index 0000000..12a45d3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/qauth.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/scrollbar.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/scrollbar.png new file mode 100644 index 0000000..f565ecd Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/scrollbar.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_horizontal.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_horizontal.png new file mode 100644 index 0000000..40484e3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_horizontal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_vertical.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_vertical.png new file mode 100644 index 0000000..ca3b5d3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/separator_vertical.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox.png new file mode 100644 index 0000000..8029090 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_down.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_down.png new file mode 100644 index 0000000..0f99b8e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_down.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_up.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_up.png new file mode 100644 index 0000000..8432b2d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/spinbox_up.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_rounded.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_rounded.png new file mode 100644 index 0000000..eebfa2f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_rounded.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_square.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_square.png new file mode 100644 index 0000000..1e1c583 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/tabbutton_square.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/textedit.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/textedit.png new file mode 100644 index 0000000..96062ef Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/textedit.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window.png new file mode 100644 index 0000000..84b3740 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window_headless.png b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window_headless.png new file mode 100644 index 0000000..598aa47 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/images/ui/window_headless.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/de.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/de.lua new file mode 100644 index 0000000..9637d66 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/de.lua @@ -0,0 +1,377 @@ + +locale = { + name = "de", + charset = "cp1252", + languageName = "Deutsch", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = false, + ["1b) Invalid Name Format"] = false, + ["1c) Unsuitable Name"] = false, + ["1d) Name Inciting Rule Violation"] = false, + ["2a) Offensive Statement"] = false, + ["2b) Spamming"] = false, + ["2c) Illegal Advertising"] = false, + ["2d) Off-Topic Public Statement"] = false, + ["2e) Non-English Public Statement"] = false, + ["2f) Inciting Rule Violation"] = false, + ["3a) Bug Abuse"] = false, + ["3b) Game Weakness Abuse"] = false, + ["3c) Using Unofficial Software to Play"] = false, + ["3d) Hacking"] = false, + ["3e) Multi-Clienting"] = false, + ["3f) Account Trading or Sharing"] = false, + ["4a) Threatening Gamemaster"] = false, + ["4b) Pretending to Have Influence on Rule Enforcement"] = false, + ["4c) False Report to Gamemaster"] = false, + ["Accept"] = "Annehmen", + ["Account name"] = "Benutzername", + ["Account Status:"] = false, + ["Action:"] = false, + ["Add"] = "Hinzufügen", + ["Add new VIP"] = "Neuen Freund hinzufügen", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Zur VIP Liste hinzufügen", + ["Adjust volume"] = "Lautstärke regeln", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = false, + ["All modules and scripts were reloaded."] = "Alle Module wurden neu geladen", + ["Allow auto chase override"] = false, + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = false, + ["Amount:"] = "Menge:", + ["Amount"] = "Menge", + ["Anonymous"] = "Anonym", + ["Are you sure you want to logout?"] = "Sind Sie sicher das du das Spiel verlassen willst?", + ["Attack"] = "Angreifen", + ["Author"] = "Autor", + ["Autoload"] = "Automatisch", + ["Autoload priority"] = "Ladepriorität", + ["Auto login"] = "Automatisch einloggen", + ["Auto login selected character on next charlist load"] = "Automatisches einloggen des ausgewählten Charakters", + ["Axe Fighting"] = "Axtkampf", + ["Balance:"] = "Guthaben:", + ["Banishment"] = false, + ["Banishment + Final Warning"] = false, + ["Battle"] = "Kampf", + ["Browse"] = false, + ["Bug report sent."] = "Bugreport würde versendet.", + ["Button Assign"] = "Button Assign", + ["Buy"] = "Kaufen", + ["Buy Now"] = "Jetzt kaufen", + ["Buy Offers"] = "Angebot", + ["Buy with backpack"] = "Im Backpack kaufen", + ["Cancel"] = "Abbrechen", + ["Cannot login while already in game."] = "Sie können sich nicht einloggen während Sie im Spiel sind.", + ["Cap"] = false, + ["Capacity"] = "Belastbarkeit", + ["Center"] = false, + ["Channels"] = false, + ["Character List"] = "Charakter Liste", + ["Classic control"] = "Klassische Steuerung", + ["Clear current message window"] = "Chatverlauf leeren", + ["Clear Messages"] = false, + ["Clear object"] = "Objekt leeren", + ["Client needs update."] = "Der Client muss geupdated werden.", + ["Close"] = "Schließen", + ["Close this channel"] = "Diesen Channel schließen", + ["Club Fighting"] = "Keulenkampf", + ["Combat Controls"] = "Kampfsteuerungen", + ["Comment:"] = "Kommentar:", + ["Connecting to game server..."] = "Verbindung zum Spielserver wird aufgebaut...", + ["Connecting to login server..."] = "Verbindung zum Loginserver wird aufgebaut...", + ["Console"] = false, + ["Cooldowns"] = false, + ["Copy message"] = "Nachricht kopieren", + ["Copy name"] = "Namen kopieren", + ["Copy Name"] = "Namen kopieren", + ["Create Map Mark"] = false, + ["Create mark"] = false, + ["Create New Offer"] = "Neues Angebot erstellen", + ["Create Offer"] = "Angebot erstellen", + ["Current hotkeys:"] = "Aktuelle Hotkeys", + ["Current hotkey to add: %s"] = "Hotkeys zum hinzufügen: %s", + ["Current Offers"] = "Aktuelle Angebote", + ["Default"] = "Standart", + ["Delete mark"] = false, + ["Description:"] = false, + ["Description"] = "Beschreibung", + ["Destructive Behaviour"] = false, + ["Detail"] = "Details", + ["Details"] = false, + ["Disable Shared Experience"] = "Expteilung deaktivieren", + ["Dismount"] = false, + ["Display connection speed to the server (milliseconds)"] = false, + ["Distance Fighting"] = "Fernkampf", + ["Don't stretch/shrink Game Window"] = false, + ["Edit hotkey text:"] = "Hotkeytext bearbeiten:", + ["Edit List"] = "Liste bearbeiten", + ["Edit Text"] = "Text bearbeiten", + ["Enable music"] = "Musik einschalten", + ["Enable Shared Experience"] = "Expteilung aktivieren", + ["Enable smart walking"] = false, + ["Enable vertical synchronization"] = "'Vertical Synchronization' aktivieren", + ["Enable walk booster"] = false, + ["Enter Game"] = "Dem Spiel beitreten", + ["Enter one name per line."] = "Gib einen Namen pro Zeile ein.", + ["Enter with your account again to update your client."] = false, + ["Error"] = "Error", + ["Error"] = "Error", + ["Excessive Unjustified Player Killing"] = false, + ["Exclude from private chat"] = "Aus dem Privatgespräch ausschließen", + ["Exit"] = false, + ["Experience"] = "Erfahrung", + ["Filter list to match your level"] = false, + ["Filter list to match your vocation"] = false, + ["Find:"] = false, + ["Fishing"] = "Fischen", + ["Fist Fighting"] = "Faustkampf", + ["Follow"] = "Verfolgen", + ["Force Exit"] = false, + ["For Your Information"] = false, + ["Free Account"] = false, + ["Fullscreen"] = "Vollbild", + ["Game"] = false, + ["Game framerate limit: %s"] = false, + ["Graphics"] = "Grafik", + ["Graphics card driver not detected"] = false, + ["Graphics Engine:"] = "Grafikengine:", + ["Head"] = "Kopf", + ["Healing"] = false, + ["Health Info"] = false, + ["Health Information"] = false, + ["Hide monsters"] = "Monster ausblenden", + ["Hide non-skull players"] = "Spieler ohne Skull ausblenden", + ["Hide Npcs"] = "NPCs ausblenden", + ["Hide Offline"] = false, + ["Hide party members"] = "Partymitglieder ausblenden", + ["Hide players"] = "Spieler ausblenden", + ["Hide spells for higher exp. levels"] = false, + ["Hide spells for other vocations"] = false, + ["Hit Points"] = "Lebenspunkte", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = false, + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Wenn du das Programm schließt kann es sein, dass dein Charakter im Spiel verweilt.nKlicke 'Logout' um sicherzustellen, dass dein Charakter das Spiel wirklich verlässt.\nKlicke 'Exit' wenn du das Programm beenden willst on deinen Charakter auszuloggen.", + ["Ignore"] = false, + ["Ignore capacity"] = "Belastbarkeit ignorieren", + ["Ignored players:"] = false, + ["Ignore equipped"] = "Equipment ignorieren", + ["Ignore List"] = false, + ["Ignore players"] = false, + ["Ignore Private Messages"] = false, + ["Ignore Yelling"] = false, + ["Interface framerate limit: %s"] = false, + ["Inventory"] = "Inventar", + ["Invite to Party"] = "Zur Party einladen", + ["Invite to private chat"] = "Zum Privatchat einladen", + ["IP Address Banishment"] = false, + ["Item Offers"] = false, + ["It is empty."] = false, + ["Join %s's Party"] = "%ss Party beitreten", + ["Leave Party"] = "Party verlassen", + ["Level"] = "Stufe", + ["Lifetime Premium Account"] = false, + ["Limits FPS to 60"] = "FPS auf 60 begrenzen", + ["List of items that you're able to buy"] = "Liste der Items die du kaufen kannst", + ["List of items that you're able to sell"] = "Liste der Items die du verkaufen kannst", + ["Load"] = "Laden", + ["Logging out..."] = "Ausloggen...", + ["Login"] = "Einloggen", + ["Login Error"] = false, + ["Login Error"] = false, + ["Logout"] = "Ausloggen", + ["Look"] = "Ansehen", + ["Magic Level"] = "Magie Level", + ["Make sure that your client uses\nthe correct game protocol version"] = "Vergewissere dich, dass der Client das richtige Protokoll verwendet.", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Hotkeys verwalten:", + ["Market"] = "Markt", + ["Market Offers"] = "Marktangebot", + ["Message of the day"] = "Nachricht des Tages", + ["Message to "] = "Nachricht an ", + ["Message to %s"] = "Nachricht an %s", + ["Minimap"] = "Minimap", + ["Module Manager"] = "Module verwalten", + ["Module name"] = "Modulname", + ["Mount"] = false, + ["Move Stackable Item"] = false, + ["Move up"] = false, + ["My Offers"] = "Mein Angebot", + ["Name:"] = "Name:", + ["Name Report"] = false, + ["Name Report + Banishment"] = false, + ["Name Report + Banishment + Final Warning"] = false, + ["No"] = false, + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Keine Items ausgewählt", + ["No Mount"] = "Kein Reittier", + ["No Outfit"] = "Kein Outfit", + ["No statement has been selected."] = false, + ["Notation"] = false, + ["NPC Trade"] = "NPC Handel", + ["Offer History"] = "Angebotsverlauf", + ["Offers"] = "Angebote", + ["Offer Type:"] = "Angebotstyp:", + ["Offline Training"] = false, + ["Ok"] = false, + ["on %s.\n"] = false, + ["Open"] = "Öffnen", + ["Open a private message channel:"] = "Privatchannel öffnen:", + ["Open charlist automatically when starting client"] = false, + ["Open in new window"] = "Im neuen Fenster öffnen", + ["Open new channel"] = "Neuen Channel öffnen", + ["Options"] = "Optionen", + ["Overview"] = false, + ["Pass Leadership to %s"] = "%s zum Anführer ernennen", + ["Password"] = "Passwort", + ["Piece Price:"] = "Stückpreis", + ["Please enter a character name:"] = "Bitte gib einen Charakternamen an:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Bitte die gewünschte Taste drücken", + ["Please Select"] = false, + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = false, + ["Please wait"] = "Warte bitte", + ["Port"] = "Port", + ["Position:"] = false, + ["Position: %i %i %i"] = false, + ["Premium Account (%s) days left"] = false, + ["Price:"] = "Preis", + ["Primary"] = "Primär", + ["Protocol"] = "Protokoll", + ["Quest Log"] = false, + ["Randomize"] = false, + ["Randomize characters outfit"] = "Zufälliges Outfit", + ["Reason:"] = "Grund:", + ["Refresh"] = "Aktualisieren", + ["Refresh Offers"] = false, + ["Regeneration Time"] = false, + ["Reject"] = "Ablehnen", + ["Reload All"] = "Alle neu laden", + ["Remember account and password when starts client"] = false, + ["Remember password"] = "Passwort speichern", + ["Remove"] = "Entfernen", + ["Remove %s"] = "%s entfernen", + ["Report Bug"] = false, + ["Reserved for more functionality later."] = false, + ["Reset Market"] = false, + ["Revoke %s's Invitation"] = "%ss Einladung zurückziehen", + ["Rotate"] = "Rotieren", + ["Rule Violation"] = false, + ["Save"] = false, + ["Save Messages"] = false, + ["Search:"] = "Suchen:", + ["Search all items"] = false, + ["Secondary"] = "Sekundär", + ["Select object"] = "Objekt auswählen", + ["Select Outfit"] = "Outfit auswählen", + ["Select your language"] = false, + ["Sell"] = "Verkaufen", + ["Sell Now"] = "Jetzt verkaufen", + ["Sell Offers"] = "Verkaufsangebote", + ["Send"] = "Versenden", + ["Send automatically"] = "Automatisch versenden", + ["Send Message"] = false, + ["Server"] = "Server", + ["Server Log"] = false, + ["Set Outfit"] = "Outfit ändern", + ["Shielding"] = "Verteidigung", + ["Show all items"] = "Alle Items anzeigen", + ["Show connection ping"] = false, + ["Show Depot Only"] = false, + ["Show event messages in console"] = "Event Nachrichten in der Konsole anzeigen", + ["Show frame rate"] = "FPS Rate anzeigen", + ["Show info messages in console"] = "Informations Nachrichten in der Konsole anzeigen", + ["Show left panel"] = false, + ["Show levels in console"] = "Level in der Konsole anzeigen", + ["Show Offline"] = false, + ["Show private messages in console"] = "Privatnachrichten in der Konsole anzeigen", + ["Show private messages on screen"] = "Privatenachrichten auf dem Bildschirm anzeigen", + ["Show Server Messages"] = false, + ["Show status messages in console"] = "Status Nachrichten in der Konsole anzeigen", + ["Show Text"] = "Text anzeigen", + ["Show timestamps in console"] = "Zeit in der Konsole anzeigen", + ["Show your depot items only"] = "Nur Depotitems anzeigen", + ["Skills"] = "Fähigkeiten", + ["Soul"] = false, + ["Soul Points"] = false, + ["Special"] = false, + ["Speed"] = false, + ["Spell Cooldowns"] = false, + ["Spell List"] = false, + ["Stamina"] = "Ausdauer", + ["Statement:"] = false, + ["Statement Report"] = false, + ["Statistics"] = "Statistiken", + ["Stop Attack"] = "Angriff abbrechen", + ["Stop Follow"] = "Verfolgen abbrechen", + ["Support"] = false, + ["%s: (use object)"] = "%s: (Objekt benutzen)", + ["%s: (use object on target)"] = "%s: (Objekt auf Ziel benutzen)", + ["%s: (use object on yourself)"] = false, + ["%s: (use object with crosshair)"] = false, + ["Sword Fighting"] = "Schwertkampf", + ["Terminal"] = "Terminal", + ["There is no way."] = "Es gibt keinen Weg dagin.", + ["Title"] = false, + ["Total Price:"] = "Gesamtpreis:", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handeln mit ...", + ["Trying to reconnect in %s seconds."] = "Versuch neu zu verbinden in %s Sekunden.", + ["Unable to load dat file, please place a valid dat in '%s'"] = false, + ["Unable to load spr file, please place a valid spr in '%s'"] = false, + ["Unable to logout."] = "Es ist nicht möglich auszuloggen.", + ["Unignore"] = false, + ["Unload"] = false, + ["Update needed"] = false, + ["Use"] = "Benutzen", + ["Use on target"] = "Auf Ziel benutzen", + ["Use on yourself"] = false, + ["Use with ..."] = "Benutzen mit ...", + ["Version"] = "Version", + ["VIP List"] = "VIP Liste", + ["Voc."] = false, + ["Vocation"] = false, + ["Waiting List"] = "Warteliste", + ["Website"] = false, + ["Weight:"] = "Gewicht:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = false, + ["Yes"] = false, + ["You are bleeding"] = "Du blutest", + ["You are burning"] = "Du brennst", + ["You are cursed"] = "Du bist verflucht", + ["You are dazzled"] = "Du bist geblendet", + ["You are dead."] = "Du bist tot.", + ["You are dead"] = "Du bist tot", + ["You are drowning"] = "Du ertrinkst", + ["You are drunk"] = "Du bist betrunken", + ["You are electrified"] = "Du bist elektrifiziert", + ["You are freezing"] = "Du bist am Erfrieren", + ["You are hasted"] = "Du bist am Eilen", + ["You are hungry"] = "Du bist hungrig", + ["You are paralysed"] = "Du bist paralysiert", + ["You are poisoned"] = "Du bist vergiftet", + ["You are protected by a magic shield"] = "Du wirst von einem magischen Schild beschützt", + ["You are strengthened"] = "Du bist gestärkt", + ["You are within a protection zone"] = "Du befindest dich in einer Schutzzone", + ["You can enter new text."] = "Du kannst einen neuen Text eingeben", + ["You have %s percent"] = "Du hast %d Prozent", + ["You have %s percent to go"] = "Dir fehlen %d Prozent", + ["You may not logout during a fight"] = "Du kannst nicht mitten im Kampf ausloggen", + ["You may not logout or enter a protection zone"] = "Du kannst nicht ausloggen oder eine Schutzzone betreten", + ["You must enter a comment."] = "Du musst einen Kommentar eingeben.", + ["You must enter a valid server address and port."] = "Du musst eine gültige Serveradresse und einen gültigen Port eingeben", + ["You must select a character to login!"] = "Du musst einen Charakter auswählen!", + ["Your Capacity:"] = "Deine Belastbarkeit:", + ["You read the following, written by \n%s\n"] = "Du liest das Folgende, geschrieben von \n%s\n", + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Dein Geld:", + } +} + +modules.client_locales.installLocale(locale) diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/en.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/en.lua new file mode 100644 index 0000000..5508110 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/en.lua @@ -0,0 +1,14 @@ +locale = { + name = "en", + charset = "cp1252", + languageName = "English", + + formatNumbers = true, + decimalSeperator = '.', + thousandsSeperator = ',', + + -- translations are not needed because everything is already in english + translation = {} +} + +modules.client_locales.installLocale(locale) diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/es.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/es.lua new file mode 100644 index 0000000..0e67deb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/es.lua @@ -0,0 +1,382 @@ +-- special thanks for Shaday, who made these translations +--Dominique120 edits: I made some statements to sound more formal and appropriate as well as correcting a few words that were not translated. I also added a few notes for future translators to keep in mind. + + +locale = { + name = "es", + charset = "cp1252", + languageName = "Español", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = '.', + + translation = { + ["1a) Offensive Name"] = "1a) Nombre ofensivo", + ["1b) Invalid Name Format"] = "1b) Formato invalido para nombre", + ["1c) Unsuitable Name"] = "1c) Nombre no adecuado", + ["1d) Name Inciting Rule Violation"] = "1d) Nombre que incita una violación al reglamento", + ["2a) Offensive Statement"] = "2a) Comentario ofensivo", + ["2b) Spamming"] = "2b) Spamming", + ["2c) Illegal Advertising"] = "2c) Publicidad ilícita", + ["2d) Off-Topic Public Statement"] = "2d) Publicación fuera de lugar", + ["2e) Non-English Public Statement"] = "2e) Publicación fuera del ingles", + ["2f) Inciting Rule Violation"] = "2f) Incitar a una violación al reglamento", + ["3a) Bug Abuse"] = "3a) Abuso de error", + ["3b) Game Weakness Abuse"] = "3b) Abuso de debilidad del juego", + ["3c) Using Unofficial Software to Play"] = "3c) Usando software ilegal para jugar", + ["3d) Hacking"] = "3d) Hackeo", + ["3e) Multi-Clienting"] = "3e) Uso de múltiples clientes", + ["3f) Account Trading or Sharing"] = "3f) Intercambio de cuenta", + ["4a) Threatening Gamemaster"] = "4a) Amenzar a un Gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Pretender tener influencia en una parte del reglamento", + ["4c) False Report to Gamemaster"] = "4c) Reporte falso a un Gamemaster", + ["Accept"] = "Aceptar", + ["Account name"] = "Nombre de cuenta", + ["Account Status:"] = "Estado de cuenta:", + ["Action:"] = "Acción:", + ["Add"] = "Añadir", + ["Add new VIP"] = "Añadir nuevo VIP", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Añadir a lista VIP", + ["Adjust volume"] = "Ajustar volumen", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = "¡Ay! Aventurero valiente, has conocido un triste destino. \nPero no se desespere, porque los dioses te llevarán de vuelta \na este mundo a cambio de un pequeño sacrificio \n\nSimplemente haga clic en Aceptar para continuar con sus viajes!", + ["All"] = "Todo", + ["All modules and scripts were reloaded."] = "Todos los módulos y scripts se vuelven a cargar.", + ["Allow auto chase override"] = "Permitur auto persecución override", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = "Conocido por la comunidad de tibia como dash.\nRecomenada para jugadores de alto nivel.", + ["Ambient light: %s%%"] = "Ambiente de luz: %s%%", + ["Amount:"] = "Cantidad:", + ["Amount"] = "Cantidad", + ["Anonymous"] = "Anónimo", + ["Are you sure you want to logout?"] = "¿Estas seguro de que deseas salir?", + ["Attack"] = "Atacar", + ["Author"] = "Autor", + ["Autoload"] = "Auto carga", + ["Autoload priority"] = "Auto carga prioritaria", + ["Auto login"] = "Auto ingresar", + ["Auto login selected character on next charlist load"] = "Ingresar la siguiente vez que aparece el charlist con el personaje seleccionado", + ["Axe Fighting"] = "Combate con hacha", + ["Balance:"] = "Saldo:", + ["Banishment"] = "Banishment", + ["Banishment + Final Warning"] = "Banishment + Final Warning", + ["Battle"] = "Batalla", + ["Browse"] = "Navegar", + ["Bug report sent."] = "Reporte de error enviado.", + ["Button Assign"] = "Botón asignado", + ["Buy"] = "Compra", + ["Buy Now"] = "Compra ahora", + ["Buy Offers"] = "Comprar oferta", + ["Buy with backpack"] = "Comprar con mochila", + ["Cancel"] = "Cancelar", + ["Cannot login while already in game."] = "No se puede iniciar sesión, mientras que estés en el juego.", + ["Cap"] = "Cap", + ["Capacity"] = "Capacidad", + ["Center"] = "Centrar", + ["Channels"] = "Canales", + ["Character List"] = "Lista de carácter", + ["Classic control"] = "Controles Clásicos", + ["Clear current message window"] = "Limpiar mensaje actual en ventana", + ["Clear Messages"] = "Limpiar mensaje", + ["Clear object"] = "Limpiar objeto", + ["Client needs update."] = "El cliente necesita una actualización.", + ["Close"] = "Cerrar", + ["Close this channel"] = "Cerrar este canal", + ["Club Fighting"] = "Combate con mazo", + ["Combat Controls"] = "Controles de combate", + ["Comment:"] = "Comentario:", + ["Connecting to game server..."] = "Conectando a servidor game...", + ["Connecting to login server..."] = "Conectando a servidor login...", + ["Console"] = "Consola", + ["Cooldowns"] = "Descansos", + ["Copy message"] = "Copiar mensaje", + ["Copy name"] = "Copiar nombre", + ["Copy Name"] = "Copiar nombre", + ["Create Map Mark"] = "Crear marca en mapa", + ["Create mark"] = "Crear marca", + ["Create New Offer"] = "Crear nueva oferta", + ["Create Offer"] = "Crear oferta", + ["Current hotkeys:"] = "Actuales hotkeys:", + ["Current hotkey to add: %s"] = "Actuales hotkeys a agregar: %s", + ["Current Offers"] = "Oferta actual", + ["Default"] = "Predeterminado", + ["Delete mark"] = "Borrar Marca", + ["Description:"] = "Descripción:", + ["Description"] = "Descripción", + ["Destructive Behaviour"] = "Comportamiento destructivo", + ["Detail"] = "Detalle", + ["Details"] = "Detalles", + ["Disable Shared Experience"] = "Desactivar experiencia compartida", + ["Dismount"] = "Desmontar", + ["Display connection speed to the server (milliseconds)"] = "Mostrar velocidad de conexión en el servidor (millisegundos)", + ["Distance Fighting"] = "Combate a distancia", + ["Don\'t stretch/shrink Game Window"] = "No estirar ni reducir el tamaño de ventana", + ["Edit hotkey text:"] = "Editar texto de hotkey:", + ["Edit List"] = "Editar lista", + ["Edit Text"] = "Editar texto", + ["Enable music"] = "Habilitar música", + ["Enable Shared Experience"] = "Habilitar experiencia compartida", + ["Enable smart walking"] = "Habilitar caminado inteligente", + ["Enable vertical synchronization"] = "Habilitar sincronización vertical", + ["Enable walk booster"] = "Habilitar caminado turbo", + ["Enter Game"] = "Entrar al juego", + ["Enter one name per line."] = "Introducir un nombre por linea.", + ["Enter with your account again to update your client."] = "Ingrese con su cuenta nuevamente para actualizar el cliente.", + ["Error"] = "Error", + ["Error"] = "Error", + ["Excessive Unjustified Player Killing"] = "Asesinato excesivo injustificado de jugadores", + ["Exclude from private chat"] = "Ejecutar desde un canal privado", + ["Exit"] = "Salir", + ["Experience"] = "Experiencia", + ["Filter list to match your level"] = "Lista de filtros que coincida con el nivel", + ["Filter list to match your vocation"] = "Lista de filtros que coincida con el vocación", + ["Find:"] = "Encontrar:", + ["Fishing"] = "Pesca", + ["Fist Fighting"] = "Combate con puños", + ["Follow"] = "Seguir", + ["Force Exit"] = "Forzar salida", + ["For Your Information"] = "Para tu información", + ["Free Account"] = "Cuenta gratis", + ["Fullscreen"] = "Pantalla completa", + ["Game"] = "Juego", + ["Game framerate limit: %s"] = "Limite de cuadros por segundo en el juego: %s", + ["Graphics"] = "Gráficos", + ["Graphics card driver not detected"] = "Controlador de tarjeta gráfica de video no detectado", + ["Graphics Engine:"] = "Motor Gráfico:", + ["Head"] = "Cabeza", + ["Healing"] = "Curación", + ["Health Info"] = "HP Info",--This can be better + ["Health Information"] = "HP Información",--This can be better + ["Hide monsters"] = "Ocultar monstruos", + ["Hide non-skull players"] = "Ocultar jugadores sin skull", + ["Hide Npcs"] = "Ocultar NPCs", + ["Hide Offline"] = "Ocultar fuera de linea", + ["Hide party members"] = "Ocultar miembros del party", + ["Hide players"] = "Ocultar players", + ["Hide spells for higher exp. levels"] = "Ocultar hechizos para niveles mas altos que tu experiencia.", + ["Hide spells for other vocations"] = "Ocultar hechizos que sean para otra vocación", + ["Hit Points"] = "Puntos de vida", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = "Mantenga presionado el botón derecho del ratón para navegar\nDezplaze la rueda central del ratón para ampliar\nbotón derecho del mouse para crear marcas del mapa", + ["Hotkeys"] = "Hotkeys", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Si se cierra el programa, tu personaje puede permanecer en el juego.\nHaga clic en 'Salir' para asegurarse de que personaje deja el juego correctamente.\nHaga click en 'Salir' si desea salir del programa sin tener que salir de tu personaje.", + ["Ignore"] = "Ignorar", + ["Ignore capacity"] = "Ignorar Capacidad", + ["Ignored players:"] = "Jugadores ignorados:", + ["Ignore equipped"] = "Ignorar lo equipado", + ["Ignore List"] = "Ignorar lista", + ["Ignore players"] = "Ignorar jugadores", + ["Ignore Private Messages"] = "Ignorar mensajes privados", + ["Ignore Yelling"] = "Ignorar gritos", + ["Interface framerate limit: %s"] = "Interface de cuadros por segundo: %s", + ["Inventory"] = "Inventario", + ["Invite to Party"] = "Ivitar al party", + ["Invite to private chat"] = "Invitar a canal privado", + ["IP Address Banishment"] = "Banishment - Dirección IP", + ["Item Offers"] = "Ofertas de objetos", + ["It is empty."] = "Está vació.", + ["Join %s\'s Party"] = "Unirse al party de %s ", + ["Leave Party"] = "Dejar el party", + ["Level"] = "Nivel", + ["Lifetime Premium Account"] = "Tiempo de Premium Account infinito", + ["Limits FPS to 60"] = "Limites FPS a 60", + ["List of items that you're able to buy"] = "Lista de objetos que puedes de comprar", + ["List of items that you're able to sell"] = "Lista de objetos que puedes de vender", + ["Load"] = "Cargar", + ["Logging out..."] = "Cerrando sesión...", + ["Login"] = "Ingresar", + ["Login Error"] = "Error de ingreso", + ["Login Error"] = "Error de ingreso", + ["Logout"] = "Salir", + ["Look"] = "Mirar", + ["Magic Level"] = "Nivel mágico", + ["Make sure that your client uses\nthe correct game protocol version"] = "Asegúrese de que el cliente este utilizando\nes el versión del protocolo adecuado", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Administrador de hotkeys:", + ["Market"] = "Mercado", + ["Market Offers"] = "Ofertas en mercado", + ["Message of the day"] = "Mensaje del día", + ["Message to "] = "Mensaje a", + ["Message to %s"] = "Mensaje a %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Administrador de módulos", + ["Module name"] = "Nombre del modulo", + ["Mount"] = "Montar", --Unique name? + ["Move Stackable Item"] = "Mover objeto apilable", + ["Move up"] = "Mover arriba", + ["My Offers"] = "Mis ofertas", + ["Name:"] = "Nombre:", + ["Name Report"] = "Name Report", + ["Name Report + Banishment"] = "Name Report + Banishment", + ["Name Report + Banishment + Final Warning"] = "Name Report + Banishment + Final Warning", + ["No"] = "No", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = "No se ha detectado una tarjeta gráfica y todo sera procesado por tu procesador,\npor lo tanto el rendimiento va a ser muy malo.\nPor favor, actualice su controlador de gráficos para tener un rendimiento optimo.", + ["No item selected."] = "No hay elemento seleccionado.", + ["No Mount"] = "No montura", + ["No Outfit"] = "No outfit", + ["No statement has been selected."] = "No hay comentario seleccionado.", + ["Notation"] = "Notation", + ["NPC Trade"] = "Intercambio con NPC", + ["Offer History"] = "Historial de oferta", + ["Offers"] = "Ofertas", + ["Offer Type:"] = "Tipo de oferta:", + ["Offline Training"] = "Entrenamiento offLine", + ["Ok"] = "OK", + ["on %s.\n"] = "en %s.\n", + ["Open"] = "Abierto", + ["Open a private message channel:"] = "Abrir mensaje en canal privado:", + ["Open charlist automatically when starting client"] = "Abrir lista de jugadores automáticamente al iniciar el cliente", + ["Open in new window"] = "Abrir en nueva ventana", + ["Open new channel"] = "Abrir nuevo canal", + ["Options"] = "Opciones", + ["Overview"] = "Descripción", + ["Pass Leadership to %s"] = "Pasar liderazgo a %s", + ["Password"] = "Contraseña", + ["Piece Price:"] = "Precio por pieza:", + ["Please enter a character name:"] = "Por favor ingresar nombre del jugador:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Por favor, presiona la tecla que desees para que sea registrada en\nel administrador de hotkeys", + ["Please Select"] = "Por favor seleccione", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Por favor usa este diálogo solo para reportar errores.\n¡No reportar violaciones del reglamento aquí!", + ["Please wait"] = "Por favor espere", + ["Port"] = "Puerto", + ["Position:"] = "Posición:", + ["Position: %i %i %i"] = "Posición: %i %i %i", + ["Premium Account (%s) days left"] = "Tienes (%s) días de Premium Account restantes", + ["Price:"] = "Precio:", + ["Primary"] = "Primario", + ["Protocol"] = "Protocolo", + ["Quest Log"] = "Quest Log", --Unique name + ["Randomize"] = "Combinar", + ["Randomize characters outfit"] = "Combinar vestimenta del jugador", + ["Reason:"] = "Razón:", + ["Refresh"] = "Refrescar", + ["Refresh Offers"] = "Refrescar ofertas", + ["Regeneration Time"] = "Tiempo de regeneración", + ["Reject"] = "Rechazar", + ["Reload All"] = "Cargar todo de nuevo", + ["Remember account and password when starts client"] = "Recordar cuenta y contraseña al iniciar el cliente", + ["Remember password"] = "Recordar contraseña", + ["Remove"] = "Remover", + ["Remove %s"] = "Remover %s", + ["Report Bug"] = "Reportar error", + ["Reserved for more functionality later."] = "Reservado para una función futura.", + ["Reset Market"] = "Reiniciar mercado", + ["Revoke %s\'s Invitation"] = "Anular %s\'s invitación", + ["Rotate"] = "Rotar", + ["Rule Violation"] = "Violación del reglamento", + ["Save"] = "Guardar", + ["Save Messages"] = "Guardar mensaje", + ["Search:"] = "Buscar:", + ["Search all items"] = "Buscar todos los objetos", + ["Secondary"] = "Secundario", + ["Select object"] = "Seleccionar objeto", + ["Select Outfit"] = "Seleccionar outfit", + ["Select your language"] = "Selectionar tu lenguaje", + ["Sell"] = "Vender", + ["Sell Now"] = "Vender ahora", + ["Sell Offers"] = "Ofertas de venta", + ["Send"] = "Enviar", + ["Send automatically"] = "Enviar automáticamente", + ["Send Message"] = "Enviar mensaje", + ["Server"] = "Server", --Unique name + ["Server Log"] = "Server Log", --Unique name + ["Set Outfit"] = "Escoger vestimenta", + ["Shielding"] = "Escudo", + ["Show all items"] = "Mostrar todos los objetos", + ["Show connection ping"] = "Mostrar ping de conexión", + ["Show Depot Only"] = "Mostrar solo el Depot", + ["Show event messages in console"] = "Mostrar mensajes de evento en consola", + ["Show frame rate"] = "Mostrar información de cuadros por secundo", + ["Show info messages in console"] = "Mostrar mensajes de información en consola", + ["Show left panel"] = "Mostrar panel izquierdo", + ["Show levels in console"] = "Mostrar niveles en consola", + ["Show Offline"] = "Mostrar Desconectados", + ["Show private messages in console"] = "Mostrar mensajes privados en consola", + ["Show private messages on screen"] = "Mostrar mensajes privados en pantalla", + ["Show Server Messages"] = "Mostrar mensajes del servidor", + ["Show status messages in console"] = "Mostrar mensajes de estado en consola", + ["Show Text"] = "Mostrar texto", + ["Show timestamps in console"] = "Mostrar marcas de tiempo en consola", + ["Show your depot items only"] = "Mostrar solo tus objetos en depot", + ["Skills"] = "Habilidades", + ["Soul"] = "Soul", + ["Soul Points"] = "Puntos de Soul", --I'm leaving these as is because its a unique name, if you want to change it it can be "Alma" or "Espíritu" + ["Special"] = "Especial", + ["Speed"] = "Velocidad", + ["Spell Cooldowns"] = "Spells Cooldowns", --Could be "Tiempo de recarga para los hechizos". + ["Spell List"] = "Lista de hechizos", + ["Stamina"] = "Resistencia", + ["Statement:"] = "Comentario:", + ["Statement Report"] = "Statement Report", --Could be "reporte del comentario" + ["Statistics"] = "Estadísticas", + ["Stop Attack"] = "Detener ataque", + ["Stop Follow"] = "Detener persecución", + ["Support"] = "Soporte", + ["%s: (use object)"] = "%s: (usar objeto)", + ["%s: (use object on target)"] = "%s: (usar objeto en un objetivo)", + ["%s: (use object on yourself)"] = "%s: (usar objeto en mi mismo)", + ["%s: (use object with crosshair)"] = "%s: (usar objeto con punto de mira)", + ["Sword Fighting"] = "Combate de espada", + ["Terminal"] = "Terminal", + ["There is no way."] = "No hay ninguna manera.", + ["Title"] = "Titulo", + ["Total Price:"] = "Total total:", + ["Trade"] = "Intercambio", + ["Trade with ..."] = "Intercambiar con ...", + ["Trying to reconnect in %s seconds."] = "", + ["Unable to load dat file, please place a valid dat in '%s'"] = "No se puede cargar el archivo dat, por favor coloque un dat válido en '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "No se puede cargar el archivo spr, por favor coloque un spr válido en '%s'", + ["Unable to logout."] = "No se puede cerrar sesión-", + ["Unignore"] = "Dejar de ignorar", + ["Unload"] = "No cargado", + ["Update needed"] = "Es necesario actualizar", + ["Use"] = "Uso", + ["Use on target"] = "Usar en un objetivo", + ["Use on yourself"] = "Usar en mi mismo", + ["Use with ..."] = "Usar en ...", + ["Version"] = "Versión", + ["VIP List"] = "Lista Vip", + ["Voc."] = "Voc.", + ["Vocation"] = "Vocación", + ["Waiting List"] = "Lista de espera", + ["Website"] = "Sitio Web", + ["Weight:"] = "Peso:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = "Detectara cuando se camina en diagonal usando las flechas", + ["With crosshair"] = "Con punto de mira", + ["Yes"] = "Si", + ["You are bleeding"] = "Te estas desangrando", + ["You are burning"] = "Te estas quemando", + ["You are cursed"] = "Tu estas maldecido", + ["You are dazzled"] = "Tu estas deslumbrado", + ["You are dead."] = "Tu estas muerto.", + ["You are dead"] = "Tu estas muerto", + ["You are drowning"] = "Te estas ahogando", + ["You are drunk"] = "Tu estas ebrio", + ["You are electrified"] = "Tu estas electrocutado", + ["You are freezing"] = "Te estas congelando", + ["You are hasted"] = "Tu estas rápido", --I dont know what is the best way to translate this so I'm leaving it as I found it. + ["You are hungry"] = "Tu estas hambriento", + ["You are paralysed"] = "Tu estas paralizado", + ["You are poisoned"] = "Tu estas envenedado", + ["You are protected by a magic shield"] = "Tu estas protegido por un escudo mágico", + ["You are strengthened"] = "Tu estas reforzado", + ["You are within a protection zone"] = "Tu estas dentro de una zona de protección", + ["You can enter new text."] = "Tu puedes ingresar un texto nuevo.", + ["You have %s percent"] = "Tu tienes %s por ciento", + ["You have %s percent to go"] = "Tu tienes %s por ciento para ir", + ["You may not logout during a fight"] = "No puedes salir durante una pelea", + ["You may not logout or enter a protection zone"] = "No puedes salir o entrar en una zona de protección", + ["You must enter a comment."] = "Debes ingresar un comentario.", + ["You must enter a valid server address and port."] = "Debes ingresar una dirección válida de servidor y el puerto.", + ["You must select a character to login!"] = "¡Debes seleccionar un personaje para ingresar!", + ["Your Capacity:"] = "Tu capacidad:", + ["You read the following, written by \n%s\n"] = "Lees lo siguiente, escrito por \n%s\n", + ["You read the following, written on \n%s.\n"] = "Lees lo siguiente, escrito en \n%s\n", + ["Your Money:"] = "Tu dinero:", + ["Change language"] = "Cambiar idioma", + ["Don't stretch or shrink Game Window"] = "No estirar o encoger Ventana de Juego" + } +} + +modules.client_locales.installLocale(locale) \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/pl.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/pl.lua new file mode 100644 index 0000000..a7c3813 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/pl.lua @@ -0,0 +1,419 @@ +locale = { + name = "pl", + languageName = "Polski", + + formatNumbers = true, + decimalSeperator = '.', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = "1a) Obrazliwe Imie", + ["1b) Invalid Name Format"] = "1b) Niepoprawny Format Imienia", + ["1c) Unsuitable Name"] = "1c) Nieodpowiednie Imie", + ["1d) Name Inciting Rule Violation"] = "1d) Imie Nawolujace Do Lamania Regulaminu", + ["2a) Offensive Statement"] = "2a) Obrazliwa wypowiedz", + ["2b) Spamming"] = "2b) Spamowanie", + ["2c) Illegal Advertising"] = "2c) Nielegalne Reklamy", + ["2d) Off-Topic Public Statement"] = "2d) Publiczne Wypowiadanie Sie Nie Na Temat", + ["2e) Non-English Public Statement"] = "2e) Publiczne wypowiadanie Sie W Jezyku Innym Niz Angielski", + ["2f) Inciting Rule Violation"] = "2f) Nawolywanie Do Lamania Regulaminu ", + ["3a) Bug Abuse"] = "3a) Wykorzystywanie Bledow", + ["3b) Game Weakness Abuse"] = "3b) Wykorzystanie Slabosci Gry", + ["3c) Using Unofficial Software to Play"] = "3c) Gra Przy Uzyciu Nieoficjalnego Oprogramowania", + ["3d) Hacking"] = "3d) Wlamywanie Sie", + ["3e) Multi-Clienting"] = "3e) Uzycie Multi-Klienta", + ["3f) Account Trading or Sharing"] = "3f) Handel Lub Udostepnianie Kont", + ["4a) Threatening Gamemaster"] = "4a) Grozby Pod Adresem Mistrza Gry", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Udawanie Wplywu na Ustanawianie Regul Gry", + ["4c) False Report to Gamemaster"] = "4c) Wyslanie Falszywego Raportu Mistrzowi Gry", + ["Accept"] = "Akceptuj", + ["Account name"] = "Numer konta", + ["Account Status:"] = "Status Konta:", + ["Action:"] = "Akcja:", + ["Add"] = "Dodaj", + ["Add new VIP"] = "Nowy VIP", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Add to VIP list"] = "Dodaj do VIPow", + ["Adjust volume"] = "Zmien glosnosc", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = "Wszystkie", + ["All modules and scripts were reloaded."] = "Wszystkie moduly ", + ["Allow auto chase override"] = false, + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = "Swiatlo tla: %s%%", + ["Amount:"] = "Ilosc:", + ["Amount"] = "Ilosc", + ["Anonymous"] = "Anonimowy", + ["Are you sure you want to logout?"] = "Czy jestes pewien, ze sie chcesz wylogowac?", + ["Attack"] = "Atak", + ["Author"] = "Autor", + ["Autoload"] = "Autoladowanie", + ["Autoload priority"] = "Priorytet autoladowania", + ["Auto login"] = "Loguj automatycznie", + ["Auto login selected character on next charlist load"] = "Automatycznie zaloguj wybrana postac podczas kolejnego ladowaia listy postaci", + ["Axe Fighting"] = "Walka toporem", + ["Balance:"] = false, + ["Banishment"] = false, + ["Banishment + Final Warning"] = false, + ["Battle"] = "Bitwa", + ["Browse"] = "Przegladaj", + ["Bug report sent."] = "Raport o bledzie zostal wyslany.", + ["Button Assign"] = "Przypisanie Klawisza", + ["Buy"] = "Kup", + ["Buy Now"] = "Kup Teraz", + ["Buy Offers"] = "Oferty Kupna", + ["Buy with backpack"] = "Kupuj z plecakami", + ["Cancel"] = "Anuluj", + ["Cannot login while already in game."] = "Nie mozna zalogowac gdy juz w grze", + ["Cap"] = "Ladownosc", + ["Capacity"] = "Ladownosc", + ["Center"] = "Wysrodkuj", + ["Channels"] = "Kanaly", + ["Character List"] = "Lista postaci", + ["Classic control"] = "Klasyczne sterowaie", + ["Clear current message window"] = "Wyczysc bierzace okno", + ["Clear Messages"] = "Wyczysc wiadomosci", + ["Clear object"] = "Wyczysc obiekt", + ["Client needs update."] = "Klient wymaga aktalizacji", + ["Close"] = "Zamknij", + ["Close this channel"] = "Zamknij kanal", + ["Club Fighting"] = "Walka obuchem", + ["Combat Controls"] = "Kontrola walki", + ["Comment:"] = "Komentarz:", + ["Connecting to game server..."] = "Laczenie z serwerem gry...", + ["Connecting to login server..."] = "Laczenie z serwerem logowania...", + ["Console"] = "Konsola", + ["Cooldowns"] = "Czasy odnowienia", + ["Copy message"] = "Kopiuj wiadomosc", + ["Copy name"] = "Kopiuj imie", + ["Copy Name"] = "Kopiuj Imie", + ["Create Map Mark"] = "Utworz Znacznik na Mapie", + ["Create mark"] = "Utworz znacznik", + ["Create New Offer"] = "Utworz Nowa Oferte", + ["Create Offer"] = "Utworz Oferte", + ["Current hotkeys:"] = "Aktualny hotkey:", + ["Current hotkey to add: %s"] = "Aktualny hotkey do dodania: %s", + ["Current Offers"] = "Obecne Oferty", + ["Default"] = "Domyslny", + ["Delete mark"] = "Usun znacznik", + ["Description:"] = "Opis:", + ["Description"] = "Opis", + ["Destructive Behaviour"] = "Destrukcyjne Zachowanie", + ["Detail"] = "Szczegoly", + ["Details"] = "Szczegoly", + ["Disable Shared Experience"] = "Wylacz Dzielenie Doswiadczenia", + ["Dismount"] = "Zejdz z wierzchowca", + ["Display connection speed to the server (milliseconds)"] = "Wyswietl ping do serwera (ms)", + ["Distance Fighting"] = "Walka na odleglosc", + ["Don't stretch/shrink Game Window"] = "Nie rozszerzaj/zwezaj Okna Gry", + ["Edit hotkey text:"] = "Edytuj tresc hotkeya:", + ["Edit List"] = "Lista Edycji", + ["Edit Text"] = "Edytuj tekst", + ["Enable music"] = "Odtwarzaj muzyke", + ["Enable Shared Experience"] = "Wlacz dzielenie doswiadczenia", + ["Enable smart walking"] = "Wlacz inteligentne chodzenie", + ["Enable vertical synchronization"] = "Wlacz synchronizacje pionowa", + ["Enable walk booster"] = false, + ["Enter Game"] = "Wejdz do gry", + ["Enter one name per line."] = "Wprowadz jedno imie na linie", + ["Enter with your account again to update your client."] = "Zaloguj sie ponownie by zaktualizowac klienta", + ["Error"] = "Blad", + ["Excessive Unjustified Player Killing"] = "Nadmierne Nieusprawiedliwione Zabijanie Graczy", + ["Exclude from private chat"] = "Wyrzuc w prywatnej konwersacji", + ["Exit"] = "Wyjdz", + ["Experience"] = "Doswiadczenie", + ["Filter list to match your level"] = "Wyswietl tylko odpowiednie dla mojego poziomu", + ["Filter list to match your vocation"] = "Wyswietl tylko odpowiednie dla mojej klasy", + ["Find:"] = "Szukaj:", + ["Fishing"] = "Wedkarstwo", + ["Fist Fighting"] = "Walka wrecz", + ["Follow"] = "Podazaj", + ["Force Exit"] = "Wymus Zamkniecie", + ["For Your Information"] = "Dla twojej informacji", + ["Free Account"] = "Darmowe Konto", + ["Fullscreen"] = "Pelen ekran", + ["Game"] = "Gra", + ["Game framerate limit: %s"] = "Limit FPS: %s", + ["Graphics"] = "Grafika", + ["Graphics card driver not detected"] = "Nie wykryto karty graficznej", + ["Graphics Engine:"] = "Silnik graficzny:", + ["Head"] = "Glowa", + ["Healing"] = "Leczenie", + ["Health Info"] = "Info o zyciu", + ["Health Information"] = "Informacje o zyciu", + ["Hide monsters"] = "Ukryj potwory", + ["Hide non-skull players"] = "Ukryj graczy bez skulla", + ["Hide Npcs"] = "Ukryj NPCe", + ["Hide Offline"] = "Ukryj Niedostepnych", + ["Hide party members"] = "Ukryj czlonkow druzyny", + ["Hide players"] = "Ukryj graczy", + ["Hide spells for higher exp. levels"] = "Ukryj zaklecia wyzszych poziomow postaci", + ["Hide spells for other vocations"] = "Ukryj zaklecia innych klas", + ["Hit Points"] = "Punkty uderzen", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = "Hotkeye", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = false, + ["Ignore"] = "Ignoruj", + ["Ignore capacity"] = "Ignoruj pojemnosc", + ["Ignored players:"] = "Ignorowani gracze:", + ["Ignore equipped"] = "Ignoruj ekwipunek", + ["Ignore List"] = "Lista Ignorowanych", + ["Ignore players"] = "Ignoruj graczy", + ["Ignore Private Messages"] = "Ignoruj Prywatne Wiadomosci", + ["Ignore Yelling"] = "Ignoruj Krzyki", + ["Interface framerate limit: %s"] = "Limit FPS interfejsu: %s", + ["Inventory"] = "Ekwipunek", + ["Invite to Party"] = "Zapros do druzyny", + ["Invite to private chat"] = "Zapros do prywatnej konwersacji", + ["IP Address Banishment"] = "Blokada adresu IP", + ["Item Offers"] = "Oferty Przedmiotow", + ["It is empty."] = "To jest puste.", + ["Join %s's Party"] = "Dolacz do druzyny gracza %s", + ["Leave Party"] = "Opusc druzyne", + ["Level"] = "Poziom", + ["Lifetime Premium Account"] = "Konto Premium na Stale", + ["Limits FPS to 60"] = "Ogranicz FPS do 60", + ["List of items that you're able to buy"] = "Lista przedmiotow, ktore mozesz kupic", + ["List of items that you're able to sell"] = "Lista przedmiotow, ktore mozesz sprzedac", + ["Load"] = "Wczytaj", + ["Logging out..."] = "Wylogowuje...", + ["Login"] = "Zaloguj", + ["Login Error"] = "Blad Logowania", + ["Login Error"] = "Blad Logowania", + ["Logout"] = "Wyloguj", + ["Look"] = "Spojrz", + ["Magic Level"] = "Poziom Magiczny", + ["Make sure that your client uses\nthe correct game protocol version"] = "Upewnij sie, ze twoj klient\nuzywa wlasciwego protokolu gry.", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Zarzadzaj hotkeyami:", + ["Market"] = false, + ["Market Offers"] = "Oferty", + ["Message of the day"] = "Wiadomosc dnia", + ["Message to "] = "Wiadomosc do ", + ["Message to %s"] = "Wiadomosc do %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Menedzer modulow", + ["Module name"] = "Nazwa modulu", + ["Mount"] = "Wierzchowiec", + ["Move Stackable Item"] = "Przenies przedmiot", + ["Move up"] = "Przenies wyzej", + ["My Offers"] = "Moje Oferty", + ["Name:"] = "Nazwa:", + ["Name Report"] = false, + ["Name Report + Banishment"] = false, + ["Name Report + Banishment + Final Warning"] = false, + ["No"] = "Nie", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Nie wybrano przedmiotu.", + ["No Mount"] = "Brak wierzchowca", + ["No Outfit"] = "Brak stroju", + ["No statement has been selected."] = false, + ["Notation"] = false, + ["NPC Trade"] = "Handel NPC", + ["Offer History"] = "Historia Ofert", + ["Offers"] = "Oferty", + ["Offer Type:"] = "Typ Oferty:", + ["Offline Training"] = "Trening Offline", + ["Ok"] = "Ok", + ["on %s.\n"] = false, + ["Open"] = "Otworz", + ["Open a private message channel:"] = "Otworz prywatny kanal:", + ["Open charlist automatically when starting client"] = "Automatycznie otworz liste postaci przy starcie gry", + ["Open in new window"] = "Otworz w nowym oknie", + ["Open new channel"] = "Otworz nowy kanal", + ["Options"] = "Opcje", + ["Overview"] = "Podsumowanie", + ["Pass Leadership to %s"] = "Oddaj przywodztwo %s", + ["Password"] = "Haslo", + ["Piece Price:"] = "Cena jednego przedmiotu", + ["Please enter a character name:"] = "Podaj nazwe postaci:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Nacisnij klawisz, ktory chcesz dodac do menedzera skrotow klawiszowych", + ["Please Select"] = "Prosze wybrac", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Zglaszaj tylko bledy gry, nie lamanie zasad", + ["Please wait"] = "Prosze czekac", + ["Port"] = "Port", + ["Position:"] = "Pozycja:", + ["Position: %i %i %i"] = "Pozycja: %i %i %i", + ["Premium Account (%s) days left"] = "Konto Premium (%s) dni", + ["Price:"] = "Cena:", + ["Primary"] = "Podstawowy", + ["Protocol"] = "Protokol", + ["Quest Log"] = "Dziennik Misji", + ["Randomize"] = "Losuj", + ["Randomize characters outfit"] = "Ustaw losowy wyglad", + ["Reason:"] = "Powod:", + ["Refresh"] = "Odswiez", + ["Refresh Offers"] = "Odswiez Oferty", + ["Regeneration Time"] = "Czas Regeneracji", + ["Reject"] = "Odrzuc", + ["Reload All"] = "Zaladuj ponownie wszystko", + ["Remember account and password when starts client"] = "Zapamietaj identyfikator konta oraz haslo", + ["Remember password"] = "Zapamietaj haslo", + ["Remove"] = "Usun", + ["Remove %s"] = "Usun %s", + ["Report Bug"] = "Zglos Blad", + ["Reserved for more functionality later."] = "Zarezerowane dla przyszlych funkcjonalnosci.", + ["Reset Market"] = "Zaladuj market ponownie", + ["Revoke %s's Invitation"] = "Odmow na zaproszenie gracza %s", + ["Rotate"] = "Obroc", + ["Rule Violation"] = "Zlamanie Regul", + ["Save"] = "Zapisz", + ["Save Messages"] = "Zapisz Wiadomosci", + ["Search:"] = "Szukaj:", + ["Search all items"] = "Znajdz wszystkie przedmioty", + ["Secondary"] = "Drugorzedny", + ["Select object"] = "Wybierz obiekt", + ["Select Outfit"] = "Wybierz outfit", + ["Select your language"] = "Wybierz jezyk", + ["Sell"] = "Sprzedaj", + ["Sell Now"] = "Sprzedaj Teraz", + ["Sell Offers"] = "Oferty Sprzedazy", + ["Send"] = "Wyslij", + ["Send automatically"] = "Wyslij automatycznie", + ["Send Message"] = "Wyslij Wiadomosc", + ["Server"] = "Serwer", + ["Server Log"] = "Log Serwera", + ["Set Outfit"] = "Ustaw outfit", + ["Shielding"] = "Obrona tarcza", + ["Show all items"] = "Pokaz wszystkie przedmioty", + ["Show connection ping"] = "Wyswietl ping", + ["Show Depot Only"] = "Pokaz tylko przedmioty z depozytu", + ["Show event messages in console"] = "Pokaz wydarzenia w konsoli", + ["Show frame rate"] = "Pokaz FPS", + ["Show info messages in console"] = "Pokaz informacje w konsoli", + ["Show left panel"] = "Pokaz lewy panel", + ["Show levels in console"] = "Pokaz poziomy w konsoli", + ["Show Offline"] = "Pokaz Niedostepnych", + ["Show private messages in console"] = "Pokaz prywatne wiadomosci w konsoli", + ["Show private messages on screen"] = "Pokaz prywatne wiadomosci na ekranie", + ["Show Server Messages"] = "Pokaz Wiadomosci Serwera", + ["Show status messages in console"] = "Pokaz status w konsoli", + ["Show Text"] = "Pokaz Tekst", + ["Show timestamps in console"] = "Pokaz znaczniki czasu w konsoli", + ["Show your depot items only"] = "Pokaz tylko przedmioty z depozytu", + ["Skills"] = "Umiejetnosci", + ["Soul"] = "Dusze", + ["Soul Points"] = "Punktey Duszy", + ["Special"] = "Specialne", + ["Speed"] = "Predkosc", + ["Spell Cooldowns"] = "Czas odnowienia czaru", + ["Spell List"] = "Lista Zaklec", + ["Stamina"] = "Wytrzymalosc", + ["Statement:"] = "Wyrazenie", + ["Statement Report"] = "Reportuj wyrazenie", + ["Statistics"] = "Statystki", + ["Stop Attack"] = "Anuluj atak", + ["Stop Follow"] = "Przestan podazac", + ["Support"] = "Wsparcie", + ["%s: (use object)"] = "%s: (uzyj obiekt)", + ["%s: (use object on target)"] = "%s: (uzyj obiektu na celu)", + ["%s: (use object on yourself)"] = "%s: (uzyj obiektu na sobie)", + ["%s: (use object with crosshair)"] = "%s: (uzyj obiektu z celownikiem)", + ["Sword Fighting"] = "Atak mieczem", + ["Terminal"] = "Terminal", + ["There is no way."] = "Nie ma drogi.", + ["Title"] = "Tytul", + ["Total Price"] = "Cena calosci", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handluj z ...", + ["Trying to reconnect in %s seconds."] = "Ponowna proba laczenia za %s sekund.", + ["Unable to load dat file, please place a valid dat in '%s'"] = "Nie mozna zaladowac pliku .dat z '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "Nie mozna zaladowac pliku .spr z '%s'", + ["Unable to logout."] = "Nie mozna sie wylogowac.", + ["Unignore"] = "Anuluj Ignorowanie", + ["Unload"] = "Wylacz", + ["Update needed"] = "Wymagana aktualizacja", + ["Use"] = "Uzyj", + ["Use on target"] = "Uzyj na celu", + ["Use on yourself"] = "Uzyj na sobie", + ["Use with ..."] = "Uzyj z ...", + ["Version"] = "Wersja", + ["VIP List"] = "Lista VIP", + ["Voc."] = "Profesja", + ["Vocation"] = "Profesja", + ["Waiting List"] = "Lista Oczekujacych", + ["Website"] = "Strona:", + ["Weight:"] = "Waga:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = "Z celownikiem", + ["Yes"] = "Tak", + ["You are bleeding"] = "Krwawisz", + ["You are burning"] = "Palisz sie", + ["You are cursed"] = "Jestes przeklety", + ["You are dazzled"] = "Jestes oslepiony", + ["You are dead."] = "Jestes martwy.", + ["You are dead"] = "Jestes martwy", + ["You are drowning"] = "Topisz sie", + ["You are drunk"] = "Jestes pijany", + ["You are electrified"] = "Jestes porazony pradem", + ["You are freezing"] = "Zamarzasz", + ["You are hasted"] = "Masz zwiekszona predkosc ruchu", + ["You are hungry"] = "Jestes glodny", + ["You are paralysed"] = "Jestes sparalizowany", + ["You are poisoned"] = "Jestes zatruty", + ["You are protected by a magic shield"] = "Jestes chroniony magiczna tarcza", + ["You are strengthened"] = "Jestes wzmocniony", + ["You are within a protection zone"] = "Jestes w strefie ochronnej", + ["You can enter new text."] = "Mozesz wprowadzic nowy tekst.", + ["You have %s percent"] = "Masz %s procent", + ["You have %s percent to go"] = "Brakuje Ci %s procent", + ["You may not logout during a fight"] = "Nie mozesz sie wylogowac w trakcie walki", + ["You may not logout or enter a protection zone"] = "Nie mozesz sie wylogowac ani wejsc do strefy ochronnej", + ["You must enter a comment."] = "Prosze wprowadzic komentarz", + ["You must enter a valid server address and port."] = "Prosze wprowadzic poprawny adres i port.", + ["You must select a character to login!"] = "Musisz wybrac postac aby sie zalogowac!", + ["Your Capacity:"] = "Twoja Ladownosc:", + ["You read the following, written by \n%s\n"] = false, + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Twoje pieniadze", + ["Enable dash walking"] = "Wlacz szybsze chodzenie (dash walking)", + ["Will boost your walk on high speed characters"] = "Przyspieszy poruszanie sie postaci o wysokiej predkosci", + ["Display creature names"] = "Wyswietlaj nazwy potworow", + ["Display creature health bars"] = "Wyswietlaj paski zycia potworow", + ["Display text messages"] = "Wyswietlaj wiadomosci tekstowe", + ["Change language"] = "Zmien jezyk", + ["Enable lights"] = "Wlacz oswietlenie", + ["Enable audio"] = "Wlacz dzwiek", + ["Enable music sound"] = "Wlacz muzyke", + ["Music volume: %d"] = "Glosnosc muzyki: %d", + ["Audio"] = "Dzwiek", + ["Server List"] = "Lista serwerow", + ["Server list"] = "Lista serwerow", + ["Client Version"] = "Wersja klienta", + ["Add new server"] = "Dodaj nowy serwer", + ["Select"] = "Wybierz", + ["New Server"] = "Nowy serwer", + ["Host"] = false, + ["Reset All"] = "Ustaw domyslne", + ["Disable chat mode, allow to walk using ASDW"] = "Zablokuj tryb rozmow, zezwol na poruszanie sie za pomoca klawiszy ADSW", + ["Name"] = "Imie", + ["Price"] = "Cena", + ["Your Money"] = "Twoje fundusze", + ["Weight"] = "Waga", + ["Your Capacity"] = "Twoj udzwig", + ["Search"] = "Szukaj", + ["Sell All"] = "Sprzedaj wszystko", + ["Statement"] = "Stanowisko", + ["Reason"] = "Powod", + ["Action"] = "Akcja", + ["Comment"] = "Komentarz", + ["Balance"] = "Stan konta", + ["Offer Type"] = "Typ oferty", + ["Piece Price"] = "Cena jednego", + ["Find"] = "Szukaj", + ["Formula"] = "Formula", + ["Group"] = "Groupa", + ["Type"] = "Typ", + ["Cooldown"] = "Czas odnowienia", + ["Premium"] = false, + ["Any"] = "Dowolny", + ["Sorcerer"] = "Czarodziej", + ["Druid"] = false, + ["Paladin"] = "Paladyn", + ["Knight"] = "Rycerz" + } +} + +modules.client_locales.installLocale(locale) diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/pt.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/pt.lua new file mode 100644 index 0000000..e5dc3b0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/pt.lua @@ -0,0 +1,413 @@ +locale = { + name = "pt", + charset = "cp1252", + languageName = "Português", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = '.', + + -- As traduções devem vir sempre em ordem alfabética. + translation = { + ["%d of experience per hour"] = "%d de experiência por hora", + ["%s of experience left"] = "%s de experiência faltando", + ["%s: (use object on target)"] = "%s: (usar objeto no alvo)", + ["%s: (use object on yourself)"] = "%s: (usar objeto em si)", + ["%s: (use object with crosshair)"] = "%s: (usar objeto com mira)", + ["%s: (use object)"] = "%s: (usar objeto)", + ["1a) Offensive Name"] = "1a) Nome ofensivo", + ["1b) Invalid Name Format"] = "1b) Nome com formato inválido", + ["1c) Unsuitable Name"] = "1c) Nome não adequado", + ["1d) Name Inciting Rule Violation"] = "1d) Nome estimulando violação de regra", + ["2a) Offensive Statement"] = "2a) Afirmação ofensiva", + ["2b) Spamming"] = "2b) Spamming", + ["2c) Illegal Advertising"] = "2c) Anúncio ilegal", + ["2d) Off-Topic Public Statement"] = "2d) Afirmação pública fora de contexto", + ["2e) Non-English Public Statement"] = "2e) Afirmação pública em lingua não inglesa", + ["2f) Inciting Rule Violation"] = "2f) Estimulando violação de regra", + ["3a) Bug Abuse"] = "3a) Abuso de falhas", + ["3b) Game Weakness Abuse"] = "3b) Abuso de falhas no jogo", + ["3c) Using Unofficial Software to Play"] = "3c) Uso de programas ilegais para jogar", + ["3d) Hacking"] = "3d) Hacking", + ["3e) Multi-Clienting"] = "3e) Uso de mais de um cliente para jogar", + ["3f) Account Trading or Sharing"] = "3f) Troca de contas ou compartilhamento", + ["4a) Threatening Gamemaster"] = "4a) Ameaçar Gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Fingir ter influencia sobre a execução de regras", + ["4c) False Report to Gamemaster"] = "4c) Relatório falso para Gamemaster", + ["Accept"] = "Aceitar", + ["Account name"] = "Nome da conta", + ["Account Status"] = "Estado da Conta", + ["Action"] = "Ação", + ["Add new server"] = "Adicionar novo servidor", + ["Add new VIP"] = "Adicionar nova VIP", + ["Add to VIP list"] = "Adicionar a lista VIP", + ["Add"] = "Adicionar", + ["Addon 1"] = "Addon 1", + ["Addon 2"] = "Addon 2", + ["Addon 3"] = "Addon 3", + ["Adjust volume"] = "Ajustar volume", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All modules and scripts were reloaded."] = "Todos módulos e scripts foram recarregados.", + ["All"] = "Todos", + ["Allow auto chase override"] = "Permitir sobrescrever o modo de perseguição", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = "Também conhecido como dash na comunidade tibiana, recomendado\npara jogar com personagem que possuam velocidade alta", + ["Ambient light: %s%%"] = "Luz ambiente: %s%%", + ["Amount"] = "Quantidade", + ["Anonymous"] = "Anônimo", + ["Any"] = "Qualquer", + ["Are you sure you want to logout?"] = "Você tem certeza que quer sair?", + ["Attack"] = "Atacar", + ["Audio"] = "Áudio", + ["Author"] = "Autor", + ["Auto login selected character on next charlist load"] = "Entrar automaticamente com o personagem quando reabrir a lista de personagens", + ["Auto login"] = "Entrar automaticamente", + ["Autoload priority"] = "Prioridade de carregamento", + ["Autoload"] = "Carregar automaticamente", + ["Axe Fighting"] = "Combate com Machado", + ["Balance"] = "Saldo", + ["Banishment + Final Warning"] = "Banimento + Aviso final", + ["Banishment"] = "Banimento", + ["Battle"] = "Batalha", + ["Browse"] = "Navegar", + ["Bug report sent."] = "Reporte de bug enviado.", + ["Button Assign"] = "Selecionar botão", + ["Buy Now"] = "Comparar agora", + ["Buy Offers"] = "Ofertas de compra", + ["Buy with backpack"] = "Comprar com mochila", + ["Buy"] = "Comprar", + ["Cancel"] = "Cancelar", + ["Cannot login while already in game."] = "Não é possivel logar enquanto já estiver jogando.", + ["Cap"] = "Cap", + ["Capacity"] = "Capacidade", + ["Center"] = "Centro", + ["Change language"] = "Trocar língua", + ["Channels"] = "Canais", + ["Character List"] = "Lista de personagens", + ["Classic control"] = "Controle clássico", + ["Clear current message window"] = "Apagar mensagens", + ["Clear Messages"] = "Limpar mensagens", + ["Clear object"] = "Limpar objeto", + ["Client needs update."] = "O client do jogo precisa ser atualizado", + ["Close this channel"] = "Fechar esse canal", + ["Close"] = "Fechar", + ["Club Fighting"] = "Combate com Porrete", + ["Combat Controls"] = "Controles de combate", + ["Comment"] = "Comentário", + ["Connecting to game server..."] = "Conectando no servidor do jogo...", + ["Connecting to login server..."] = "Conectando no servidor de autenticação...", + ["Connection Error"] = "Erro de Conexão", + ["Console"] = "Console", + ["Cooldown"] = "Cooldown", + ["Cooldowns"] = "Cooldowns", + ["Copy message"] = "Copiar mensagem", + ["Copy name"] = "Copiar nome", + ["Copy Name"] = "Copiar Nome", + ["Create Map Mark"] = "Criar marca no mapa", + ["Create mark"] = "Criar marca", + ["Create New Offer"] = "Criar nova oferta", + ["Create Offer"] = "Criar oferta", + ["Current hotkey to add: %s"] = "Atalho atual para adicionar: %s", + ["Current hotkeys:"] = "Atalhos atuais", + ["Current Offers"] = "Ofertas atuais", + ["Default"] = "Padrão", + ["Delete mark"] = "Deletar marca", + ["Description"] = "Descrição", + ["Description:"] = "Descrição", + ["Destructive Behaviour"] = "Comportamento destrutivo", + ["Detail"] = "Detalhe", + ["Details"] = "Detalhes", + ["Disable Shared Experience"] = "Desativar experiência compartilhada", + ["Dismount"] = "Desmontar", + ["Display connection speed to the server (milliseconds)"] = "Exibir a velocidade de conexão com o servidor (milisegundos)", + ["Display creature health bars"] = "Exibir barras de vida das criaturas", + ["Display creature names"] = "Exibir nomes das criaturas", + ["Display text messages"] = "Exibir mensagens de texto", + ["Distance Fighting"] = "Combate a Distância", + ["Don't stretch or shrink Game Window"] = "Não esticar ou contrair a janela do game", + ["Druid"] = "Druid", + ["Edit hotkey text:"] = "Editar texto do atalho", + ["Edit List"] = "Editar lista", + ["Edit Text"] = "Editar Texto", + ["Enable audio"] = "Ativar áudio", + ["Enable dash walking"] = "Ativar andar rápido", + ["Enable lights"] = "Ativar luzes", + ["Enable music sound"] = "Ativar música", + ["Enable music"] = "Ativar musica", + ["Enable shader effects"] = "Ativar efeitos shader", + ["Enable Shared Experience"] = "Ativar experiência compartilhada", + ["Enable smart walking"] = "Ativar andar inteligente", + ["Enable vertical synchronization"] = "Ativar sincronização vertical", + ["Enable walk booster"] = "Ativar andar intensificado", + ["Enter Game"] = "Entrar no jogo", + ["Enter one name per line."] = "Entre somente um nome por linha.", + ["Enter with your account again to update your client."] = "Entre com sua conta denovo para atualizar o client.", + ["Error"] = "Erro", + ["Excessive Unjustified Player Killing"] = "Assassinato em excesso, sem justificativa, de jogadores", + ["Exclude from private chat"] = "Excluir do canal privado", + ["Exit"] = "Sair", + ["Experience"] = "Experiência", + ["Filter list to match your level"] = "Filtrar a lista para o seu level", + ["Filter list to match your vocation"] = "Filtrar a lista para a sua vocação", + ["Find"] = "Procurar", + ["Fishing"] = "Pesca", + ["Fist Fighting"] = "Porrada", + ["Follow"] = "Seguir", + ["For Your Information"] = "Para sua informação", + ["Force Exit"] = "Forçar Saida", + ["Formula"] = "Fórmula", + ["Free Account"] = "Conta Grátis", + ["Fullscreen"] = "Tela cheia", + ["Game framerate limit: %s"] = "Limite da taxa de quadros do jogo: %s", + ["Game"] = "Jogo", + ["Graphics card driver not detected"] = "Driver da placa de vídeo não detectado", + ["Graphics Engine:"] = "Motor Gráfico:", + ["Graphics"] = "Gráficos", + ["Group"] = "Grupo", + ["Head"] = "Cabeça", + ["Healing"] = "Curando", + ["Health Info"] = "Barra de Vida", + ["Health Information"] = "Informação de vida", + ["Hide monsters"] = "Esconder montros", + ["Hide non-skull players"] = "Esconder jogadores sem caveira", + ["Hide Npcs"] = "Esconder NPCs", + ["Hide Offline"] = "Esconder Offline", + ["Hide party members"] = "Esconder membros do grupo", + ["Hide players"] = "Esconder jogadores", + ["Hide spells for higher exp. levels"] = "Esconder feitiços de nível maior", + ["Hide spells for other vocations"] = "Esconder feitiços de outras vocações", + ["Hit Points"] = "Pontos de Vida", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = "Segure o botão esquerdo para navegar\nGire o botão do centro do mouse para ampliar\nClique com o botão direito do mouse para criar marcas", + ["Host"] = "Host", + ["Hotkeys"] = "Atalhos", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Se você desligar o programa o seu personagem pode continuar no jogo.\nClique em 'Sair' para assegurar que seu personagem saia do jogo adequadamente.\nClique em 'Forçar Saida' para fechar o programa sem desconectar seu personagem.", + ["Ignore capacity"] = "Ignorar capacidade", + ["Ignore equipped"] = "Ignorar equipado", + ["Ignore List"] = "Lista de Ignorados", + ["Ignore players"] = "Jogadores ignorados", + ["Ignore Private Messages"] = "Ignorar mensagens privadas", + ["Ignore Yelling"] = "Ignorar gritos", + ["Ignore"] = "Ignorar", + ["Ignored players:"] = "Joadores ignorados:", + ["Interface framerate limit: %s"] = "Limite da taxa de quadros da interface: %s", + ["Inventory"] = "Inventário", + ["Invite to Party"] = "Convidar para o grupo", + ["Invite to private chat"] = "Convidar para o canal privado", + ["IP Address Banishment"] = "Banimento de endereço IP", + ["It is empty."] = "Está vazio.", + ["Item Offers"] = "Ofertas de items", + ["Join %s's Party"] = "Entrar na party do %s", + ["Knight"] = "Knight", + ["Leave Party"] = "Sair do grupo", + ["Level"] = "Nível", + ["Lifetime Premium Account"] = "Conta Premium para a vida toda.", + ["Limits FPS to 60"] = "Limita o FPS para 60", + ["List of items that you're able to buy"] = "Lista de itens que você pode comprar", + ["List of items that you're able to sell"] = "Lista de itens que você pode vender", + ["Load"] = "Carregar", + ["Logging out..."] = "Saindo...", + ["Login Error"] = "Erro de Autenticação", + ["Login"] = "Entrar", + ["Logout"] = "Sair", + ["Look"] = "Olhar", + ["Magic Level"] = "Nível Mágico", + ["Make sure that your client uses\nthe correct game protocol version"] = "Tenha certeza que o seu cliente use\no mesmo protocolo do servidor do jogo", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Configurar atalhos:", + ["Market Offers"] = "Ofertas do mercado", + ["Market"] = "Mercado", + ["Message of the day"] = "Mensagem do dia", + ["Message to "] = "Mensagem para ", + ["Message to %s"] = "Mandar mensagem para %s", + ["Minimap"] = "Minimapa", + ["Module Manager"] = "Gerenciador de Módulos", + ["Module name"] = "Nome do módulo", + ["Mount"] = "Montar", + ["Move Stackable Item"] = "Mover item contável", + ["Move up"] = "Mover para cima", + ["Music volume: %d"] = "Volume da música: %d", + ["My Offers"] = "Minhas ofertas", + ["Name Report + Banishment + Final Warning"] = "Reportar Nome + Banimento + Aviso Final", + ["Name Report + Banishment"] = "Reportar Nome + Banimento", + ["Name Report"] = "Reportar Nome", + ["Name"] = "Nome", + ["New Server"] = "Novo Servidor", + ["Next level in %d hours and %d minutes"] = "Próximo nível em %d horas e %d minutos", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Nenhum item selecionado", + ["No Mount"] = "Sem montaria", + ["No Outfit"] = "Sem roupa", + ["No statement has been selected."] = "Nenhuma afirmação foi selecionada.", + ["No"] = "Não", + ["Notation"] = "Notação", + ["NPC Trade"] = "Troca com NPC", + ["Offer History"] = "Histórico de ofertas", + ["Offer Type"] = "Tipo de oferta", + ["Offers"] = "Ofertas", + ["Offline Training"] = "Treino Offline", + ["Ok"] = "Ok", + ["on %s.\n"] = "em %s.\n", + ["Open a private message channel:"] = "Abrir um canal privado:", + ["Open charlist automatically when starting client"] = "Abrir lista de personagens ao iniciar o cliente", + ["Open in new window"] = "Abrir em nova janela", + ["Open new channel"] = "Abrir novo canal", + ["Open"] = "Abrir", + ["Options"] = "Opções", + ["Overview"] = "Visão geral", + ["Paladin"] = "Paladin", + ["Pass Leadership to %s"] = "Passar liderança para %s", + ["Password"] = "Senha", + ["Piece Price"] = "Preço por peça", + ["Please enter a character name:"] = "Por favor, entre com o nome do personagem:", + ["Please Select"] = "Por favor, selecione algo", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Por favor, use este campo apenas para reportar defeitos. Não reporte violação de regras aqui!", + ["Please wait"] = "Por favor, espere", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Por favor, pressione a tecla que você deseja\nadicionar no gerenciador de atalhos", + ["Port"] = "Porta", + ["Position"] = "Posição", + ["Position: %i %i %i"] = "Posição: %i %i %i", + ["Premium Account (%s) days left"] = "Conta Premium (%s) dias faltando", + ["Premium"] = "Premium", + ["Price"] = "Preço", + ["Primary"] = "Primário", + ["Protocol"] = "Protocolo", + ["Quest Log"] = "Registro de Quest", + ["Randomize characters outfit"] = "Gerar roupa aleatória", + ["Randomize"] = "Embaralhar", + ["Reason"] = "Motivo", + ["Refresh Offers"] = "Atualizar Ofertas", + ["Refresh"] = "Atualizar", + ["Regeneration Time"] = "Tempo de Regeneração", + ["Reject"] = "Rejeitar", + ["Reload All"] = "Recarregar Todos", + ["Remember account and password when starts client"] = "Lembrar conta e senha quando iniciar o cliente", + ["Remember password"] = "Lembrar senha", + ["Remove %s"] = "Remover %s", + ["Remove"] = "Remover", + ["Report Bug"] = "Reportar defeito", + ["Reserved for more functionality later."] = "Reservado para futura maior funcionalidade.", + ["Reset All"] = "Resetar Todos", + ["Reset Market"] = "Resetar Mercado", + ["Revoke %s's Invitation"] = "Não aceitar o convite do %s", + ["Rotate"] = "Girar", + ["Rule Violation"] = "Violação de regra", + ["Save Messages"] = "Salvar Mensagens", + ["Save"] = "Salvar", + ["Search all items"] = "Procurar todos os items", + ["Search"] = "Procurar", + ["Secondary"] = "Secundário", + ["Select object"] = "Selecionar objeto", + ["Select Outfit"] = "Selecionar Roupa", + ["Select your language"] = "Selecione sua língua", + ["Select"] = "Selecionar", + ["Sell All"] = "Vender Todos", + ["Sell Now"] = "Vender agora", + ["Sell Offers"] = "Ofertas de venda", + ["Sell"] = "Vender", + ["Send automatically"] = "Enviar automaticamente", + ["Send Message"] = "Enviar Mensagem", + ["Send"] = "Enviar", + ["Server List"] = "Lista de Servidores", + ["Server list"] = "Lista de servidores", + ["Server Log"] = "Registro do servidor", + ["Server"] = "Servidor", + ["Set Outfit"] = "Escolher Roupa", + ["Shielding"] = "Defesa", + ["Show all items"] = "Exibir todos os itens", + ["Show connection ping"] = "Mostrar latência de conexão", + ["Show Depot Only"] = "Mostrar somente o depósito", + ["Show event messages in console"] = "Exibir mensagens de eventos no console", + ["Show frame rate"] = "Exibir FPS", + ["Show info messages in console"] = "Exibir mensagens informativas no console", + ["Show left panel"] = "Mostrar barra lateral esquerda", + ["Show levels in console"] = "Exibir níveis no console", + ["Show Offline"] = "Mostrar Offline", + ["Show private messages in console"] = "Exibir mensagens privadas no console", + ["Show private messages on screen"] = "Exibir mensagens na tela", + ["Show Server Messages"] = "Mostrar Mensagens do Servidor", + ["Show status messages in console"] = "Exibir mensagens de estado no console", + ["Show Text"] = "Mostrar texto", + ["Show timestamps in console"] = "Exibir o horário no console", + ["Show your depot items only"] = "Mostrar os itens somentedo depósito", + ["Skills"] = "Habilidades", + ["Sorcerer"] = "Sorcerer", + ["Soul Points"] = "Pontos de Alma", + ["Soul"] = "Alma", + ["Special"] = "Especial", + ["Speed"] = "Velocidade", + ["Spell Cooldowns"] = "", + ["Spell List"] = "Lista de Feitiços", + ["Stamina"] = "Vigor", + ["Statement Report"] = "Afirmar Relato", + ["Statement"] = "Afirmação", + ["Statistics"] = "Estatísticas", + ["Stop Attack"] = "Parar de Atacar", + ["Stop Follow"] = "Parar de Seguir", + ["Support"] = "Suporte", + ["Sword Fighting"] = "Combate com Espada", + ["Terminal"] = "Terminal", + ["There is no way."] = "Não há rota", + ["Title"] = "Título", + ["Total Price"] = "Preço total", + ["Trade with ..."] = "Trocar com ...", + ["Trade"] = "Trocar", + ["Trying to reconnect in %s seconds."] = "Tentando reconectar em %s segundos.", + ["Type"] = "Tipo", + ["Unable to establish a connection. (err: %d)"] = "Não foi possível estabilizar a conexã. (err: %d)", + ["Unable to load dat file, please place a valid dat in '%s'"] = "Não foi possível carregar o arquivo DAT, por favor coloque um arquivo válido em %s", + ["Unable to load spr file, please place a valid spr in '%s'"] = "Não foi possível carregar o arquivo SPR, por favor coloque um arquivo válido em %s", + ["Unable to logout."] = "Não é possivel sair", + ["Unignore"] = "Designorar", + ["Unload"] = "Descarregar", + ["Update needed"] = "Atualização necessária", + ["Use on target"] = "Usar no alvo", + ["Use on yourself"] = "Usar em si", + ["Use with ..."] = "Usar com ...", + ["Use"] = "Usar", + ["Version"] = "Versão", + ["VIP List"] = "Lista VIP", + ["Voc."] = "Voc.", + ["Vocation"] = "Vocação", + ["Waiting List"] = "Lista de espera", + ["Website"] = "Website", + ["Weight"] = "Peso", + ["Will boost your walk on high speed characters"] = "Irá melhorar o andar de persnagens rápidos", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = "Detectar quando usar o passo diagonal\nbaseado nas teclas pressionadas", + ["With crosshair"] = "Com mira", + ["Yes"] = "Sim", + ["You are bleeding"] = "Você está sangrando", + ["You are burning"] = "Você está queimando", + ["You are cursed"] = "Você está amaldiçoado", + ["You are dazzled"] = "Você está deslumbrado", + ["You are dead"] = "Você está morto", + ["You are dead."] = "Você está morto.", + ["You are drowning"] = "Você está se afogando", + ["You are drunk"] = "Você está bêbado", + ["You are electrified"] = "Você está eletrificado", + ["You are freezing"] = "Você está congelando", + ["You are hasted"] = "Você está com pressa", + ["You are hungry"] = "Você está faminto", + ["You are paralysed"] = "Você está paralizado", + ["You are poisoned"] = "Você está envenenado", + ["You are protected by a magic shield"] = "Você está protegido com um escudo mágico", + ["You are strengthened"] = "Você está reforçado", + ["You are within a protection zone"] = "Você está dentro de uma zona de proteção", + ["You can enter new text."] = "Você pode entrar com um novo texto.", + ["You have %d%% to advance to level %d."] = "Você tem %d%% para avançar para o nível %d.", + ["You have %s percent to go"] = "Você tem %s porcento para avançar", + ["You have %s percent"] = "Você tem %s porcento", + ["You may not logout during a fight"] = "Você não pode sair durante um combate", + ["You may not logout or enter a protection zone"] = "Você não pode sair ou entrar em uma zona de proteção", + ["You must enter a comment."] = "Você precisa entrar com um comentário", + ["You must enter a valid server address and port."] = "Você precisa colocar um endereço e uma porta do servidor válidos.", + ["You must select a character to login!"] = "Você deve selecionar um personagem para entrar!", + ["You read the following, written by \n%s\n"] = "Você lê o seguinte, escrito por \n%s\n", + ["You read the following, written on \n%s.\n"] = "Você lê o seguinte, escrito em \n%s.\n", + ["Your Capacity"] = "Sua capacidade", + ["Your character health is %d out of %d."] = "A vida do seu personagem é %d de %d.", + ["Your character mana is %d out of %d."] = "A mana do seu personagem é %d de %d.", + ["Your connection has been lost. (err: %d)"] = "A sua conexão foi perdida. (err: %d)", + ["Your Money"] = "Seu dinheiro", + } +} + +modules.client_locales.installLocale(locale) diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/locales/sv.lua b/app/SabrehavenServer/SabrehavenOTClient/data/locales/sv.lua new file mode 100644 index 0000000..d6263f4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/locales/sv.lua @@ -0,0 +1,378 @@ +-- thanks cometangel, who made these translations + +locale = { + name = "sv", + charset = "cp1252", + languageName = "Svenska", + + formatNumbers = true, + decimalSeperator = ',', + thousandsSeperator = ' ', + + translation = { + ["1a) Offensive Name"] = "1a) Offensivt Namn", + ["1b) Invalid Name Format"] = "1b) Ogiltigt Namnformat", + ["1c) Unsuitable Name"] = "1c) Opassande Namn", + ["1d) Name Inciting Rule Violation"] = "1d) Namn anstiftar regelbrott.", + ["2a) Offensive Statement"] = "2a) Offensivt Uttryck", + ["2b) Spamming"] = "2b) Spammning", + ["2c) Illegal Advertising"] = "2c) Olaglig Reklamföring", + ["2d) Off-Topic Public Statement"] = "2d) Icke-Ämneförhållande publiskt uttryck", + ["2e) Non-English Public Statement"] = "2e) Icke-Engelskt publiskt uttryck", + ["2f) Inciting Rule Violation"] = "2f) Antyder regelbrytande", + ["3a) Bug Abuse"] = "3a) Missbrukande av bugg", + ["3b) Game Weakness Abuse"] = "3b) Spelsvaghetsmissbruk", + ["3c) Using Unofficial Software to Play"] = "3c) Använder Icke-officiel mjukvara för att spela", + ["3d) Hacking"] = "3d) Hackar", + ["3e) Multi-Clienting"] = "3e) Multi-klient", + ["3f) Account Trading or Sharing"] = "3f) Kontohandel", + ["4a) Threatening Gamemaster"] = "4a) Hotar gamemaster", + ["4b) Pretending to Have Influence on Rule Enforcement"] = "4b) Låtsas ha inflytande på Regelsystem", + ["4c) False Report to Gamemaster"] = "4c) Falsk rapport till gamemaster", + ["Accept"] = "Acceptera", + ["Account name"] = "Konto namn", + ["Account Status:"] = false, + ["Action:"] = "Handling:", + ["Add"] = "Lägg till", + ["Add new VIP"] = "Ny VIP", + ["Addon 1"] = "Tillägg 1", + ["Addon 2"] = "Tillägg 2", + ["Addon 3"] = "Tillägg 3", + ["Add to VIP list"] = "Lägg till på VIP Listan", + ["Adjust volume"] = "Justera Volym", + ["Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!"] = false, + ["All"] = false, + ["All modules and scripts were reloaded."] = "Alla moduler och skript laddades om", + ["Allow auto chase override"] = "Tillåt Jaktstyrning", + ["Also known as dash in tibia community, recommended\nfor playing characters with high speed"] = false, + ["Ambient light: %s%%"] = false, + ["Amount:"] = "Antal:", + ["Amount"] = "Antal", + ["Anonymous"] = "Anonym", + ["Are you sure you want to logout?"] = "Är du säker att du vill logga ut?", + ["Attack"] = "Attackera", + ["Author"] = "Översättare", + ["Autoload"] = "Automatisk Laddning", + ["Autoload priority"] = "Laddningsprioritet", + ["Auto login"] = "Autoinloggning", + ["Auto login selected character on next charlist load"] = "Logga in Näst laddad karaktär automatisk nästa gång karaktärlistan laddar", + ["Axe Fighting"] = "Yx Stridande", + ["Balance:"] = "Balans:", + ["Banishment"] = "Bannlysning", + ["Banishment + Final Warning"] = "Bannlysning + Sista varning", + ["Battle"] = "Strid", + ["Browse"] = "Bläddra", + ["Bug report sent."] = "Buggrapport Skickad.", + ["Button Assign"] = "Assignera Knapp", + ["Buy"] = "Köp", + ["Buy Now"] = "Köp Nu", + ["Buy Offers"] = "Köp Offerter", + ["Buy with backpack"] = "Köp med ryggsäck", + ["Cancel"] = "Avbryt", + ["Cannot login while already in game."] = "Kan ej logga in medan du redan är i spelet.", + ["Cap"] = "Kap", + ["Capacity"] = "Kapacitet", + ["Center"] = "Centrera", + ["Channels"] = "Kanaler", + ["Character List"] = "Karaktär lista", + ["Classic control"] = "Klassisk kontroll", + ["Clear current message window"] = "Rensa nuvarande meddelanderuta", + ["Clear Messages"] = false, + ["Clear object"] = "Rensa objekt", + ["Client needs update."] = "Klienten behöver uppdateras.", + ["Close"] = "Stäng", + ["Close this channel"] = "Stäng Denna Kanal", + ["Club Fighting"] = "Klubb Stridande", + ["Combat Controls"] = "Krigs Kontroller", + ["Comment:"] = "Kommentar:", + ["Connecting to game server..."] = "Kopplar upp till spelserver...", + ["Connecting to login server..."] = "Kopplar upp till autentiseringserver...", + ["Console"] = false, + ["Cooldowns"] = false, + ["Copy message"] = "Kopiera meddelande", + ["Copy name"] = "Kopiera namn", + ["Copy Name"] = "Kopiera Namn", + ["Create Map Mark"] = false, + ["Create mark"] = false, + ["Create New Offer"] = "Skapa ny offert.", + ["Create Offer"] = "Skapa Offert", + ["Current hotkeys:"] = "Aktuella snabbtangenter", + ["Current hotkey to add: %s"] = "Ny Snabbtangent: %s", + ["Current Offers"] = "Nuvarande Offerter", + ["Default"] = "Standard", + ["Delete mark"] = false, + ["Description:"] = false, + ["Description"] = "Beskrivning", + ["Destructive Behaviour"] = "Destruktivt beteende", + ["Detail"] = "Detalj", + ["Details"] = "Detaljer", + ["Disable Shared Experience"] = "Avaktivera delad erfarenhet", + ["Dismount"] = false, + ["Display connection speed to the server (milliseconds)"] = false, + ["Distance Fighting"] = "Distans Stridande", + ["Don't stretch/shrink Game Window"] = false, + ["Edit hotkey text:"] = "Ändra Snabbtangent:", + ["Edit List"] = "Ändra Lista", + ["Edit Text"] = "Ändra text", + ["Enable music"] = "Aktivera musik", + ["Enable Shared Experience"] = "Aktivera delad erfarenhet", + ["Enable smart walking"] = false, + ["Enable vertical synchronization"] = "Aktivera vertikal synkronisering", + ["Enable walk booster"] = false, + ["Enter Game"] = "Gå in i Spelet", + ["Enter one name per line."] = "Skriv ett namn per linje.", + ["Enter with your account again to update your client."] = false, + ["Error"] = "Fel", + ["Error"] = "Fel", + ["Excessive Unjustified Player Killing"] = "Överdrivet oberättigat dödande av spelare", + ["Exclude from private chat"] = "Exkludera från privat chat", + ["Exit"] = "Avsluta", + ["Experience"] = "Erfarenhet", + ["Filter list to match your level"] = "Filtrera efter nivå", + ["Filter list to match your vocation"] = "Filtrera efter kallelse", + ["Find:"] = false, + ["Fishing"] = "Fiske", + ["Fist Fighting"] = "Hand Stridande", + ["Follow"] = "Följ", + ["Force Exit"] = false, + ["For Your Information"] = false, + ["Free Account"] = false, + ["Fullscreen"] = "Helskärm", + ["Game"] = false, + ["Game framerate limit: %s"] = "Spelets FPS gräns: %s", + ["Graphics"] = "Grafik", + ["Graphics card driver not detected"] = false, + ["Graphics Engine:"] = "Grafikmotor:", + ["Head"] = "Huvud", + ["Healing"] = false, + ["Health Info"] = "Livsinfo", + ["Health Information"] = "Livsinformation", + ["Hide monsters"] = "Göm Monster", + ["Hide non-skull players"] = "Göm icke-skullad spelare", + ["Hide Npcs"] = "Göm NPCs", + ["Hide Offline"] = false, + ["Hide party members"] = "Göm gruppmedlemmar", + ["Hide players"] = "Göm spelare", + ["Hide spells for higher exp. levels"] = false, + ["Hide spells for other vocations"] = false, + ["Hit Points"] = "Livspoäng", + ["Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks"] = false, + ["Hotkeys"] = "Snabbtangenter", + ["If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."] = "Om du stänger av programmet kan din karaktär stanna i spelet.\nKlicka på 'Logga ut' för att säkerställa att din karaktär lämnar spelet korrekt.\nKlicka på 'Avsluta' om du vill avsluta programmet utan att logga ut din karaktär.", + ["Ignore"] = false, + ["Ignore capacity"] = "Ignorera kapacitet", + ["Ignored players:"] = false, + ["Ignore equipped"] = "Ignorera utrustning", + ["Ignore List"] = false, + ["Ignore players"] = false, + ["Ignore Private Messages"] = false, + ["Ignore Yelling"] = false, + ["Interface framerate limit: %s"] = "Gränssnitt FPS gräns: %s", + ["Inventory"] = "Utrustning", + ["Invite to Party"] = "Bjud till grupp", + ["Invite to private chat"] = "Bjud in i privat chat", + ["IP Address Banishment"] = "Bannlysning av IP", + ["Item Offers"] = "Objekt offert", + ["It is empty."] = false, + ["Join %s's Party"] = "Gå med i %s's Grupp", + ["Leave Party"] = "Lämna Grupp", + ["Level"] = "Nivå", + ["Lifetime Premium Account"] = false, + ["Limits FPS to 60"] = "Stoppa FPS vid 60", + ["List of items that you're able to buy"] = "Lista av saker du kan köpa", + ["List of items that you're able to sell"] = "Lista av saker du kan sälja", + ["Load"] = "Ladda", + ["Logging out..."] = "Loggar ut...", + ["Login"] = "Logga in", + ["Login Error"] = "Autentifikations fel", + ["Login Error"] = "Autentifikations fel", + ["Logout"] = "Logga ut", + ["Look"] = "Kolla", + ["Magic Level"] = "Magisk Nivå", + ["Make sure that your client uses\nthe correct game protocol version"] = "Var säker på att din client\n andvänder rätt protokol version", + ["Mana"] = "Mana", + ["Manage hotkeys:"] = "Ändra snabbtangenten:", + ["Market"] = "Marknad", + ["Market Offers"] = "Marknadsofferter", + ["Message of the day"] = "Dagens meddelande", + ["Message to "] = "Meddelande till ", + ["Message to %s"] = "Meddelande till %s", + ["Minimap"] = "Minikarta", + ["Module Manager"] = "Modul Manager", + ["Module name"] = "Modul namn", + ["Mount"] = false, + ["Move Stackable Item"] = "Flytta stapelbart föremål", + ["Move up"] = "Flytta upp", + ["My Offers"] = "Mina offerter", + ["Name:"] = "Namn:", + ["Name Report"] = "Namn Rapport", + ["Name Report + Banishment"] = "Namn rapport + Bannlysning", + ["Name Report + Banishment + Final Warning"] = "Namn rapport + Bannlysning + Sista varning", + ["No"] = "Nej", + ["No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance."] = false, + ["No item selected."] = "Ingen sak vald.", + ["No Mount"] = "Ingen Mount", + ["No Outfit"] = "Ingen Utstyrsel", + ["No statement has been selected."] = "Inget påstående är valt.", + ["Notation"] = "Notering", + ["NPC Trade"] = "Handel NPC", + ["Offer History"] = "Offert Historia", + ["Offers"] = "Offerter", + ["Offer Type:"] = "Offert typ:", + ["Offline Training"] = false, + ["Ok"] = "Ok", + ["on %s.\n"] = "på %s.\n", + ["Open"] = "Öppna", + ["Open a private message channel:"] = "Öppna en privat meddelandekanal:", + ["Open charlist automatically when starting client"] = false, + ["Open in new window"] = "Öppna i nytt fönster", + ["Open new channel"] = "Öppna ny kanal", + ["Options"] = "Inställningar", + ["Overview"] = false, + ["Pass Leadership to %s"] = "Ge ledarskap till %s", + ["Password"] = "Lösenord", + ["Piece Price:"] = "Per Styck:", + ["Please enter a character name:"] = "Skriv in ett karaktärsnamn:", + ["Please, press the key you wish to add onto your hotkeys manager"] = "Tryck på knappen som du\nvill lägga till som snabbtangent", + ["Please Select"] = "Välj", + ["Please use this dialog to only report bugs. Do not report rule violations here!"] = "Använd den här dialogrutan endast för att rapportera buggar. Rapportera inte regelbrott här!", + ["Please wait"] = "Var God Vänta", + ["Port"] = "Port", + ["Position:"] = false, + ["Position: %i %i %i"] = false, + ["Premium Account (%s) days left"] = false, + ["Price:"] = "Pris", + ["Primary"] = "Primär", + ["Protocol"] = "Protokoll", + ["Quest Log"] = "Uppdragslog", + ["Randomize"] = "Slumpa", + ["Randomize characters outfit"] = "Slumpa karaktärs utstyrsel", + ["Reason:"] = "Anledning:", + ["Refresh"] = "Uppdatera", + ["Refresh Offers"] = false, + ["Regeneration Time"] = false, + ["Reject"] = "Avvisa", + ["Reload All"] = "Ladda om allt", + ["Remember account and password when starts client"] = false, + ["Remember password"] = "Kom ihåg lösenord", + ["Remove"] = "Ta bort", + ["Remove %s"] = "Ta bort %s", + ["Report Bug"] = "Rapportera Bugg", + ["Reserved for more functionality later."] = false, + ["Reset Market"] = false, + ["Revoke %s's Invitation"] = "Annulera %s's Inbjudan", + ["Rotate"] = "Rotera", + ["Rule Violation"] = "Regel Brott", + ["Save"] = false, + ["Save Messages"] = false, + ["Search:"] = "Sök:", + ["Search all items"] = false, + ["Secondary"] = "Sekundär", + ["Select object"] = "Välj Objekt", + ["Select Outfit"] = "Välj Utstyrsel", + ["Select your language"] = false, + ["Sell"] = "Sälj", + ["Sell Now"] = "Sälj Nu", + ["Sell Offers"] = "Sälj Offerter", + ["Send"] = "Skicka", + ["Send automatically"] = "Skicka automatiskt", + ["Send Message"] = false, + ["Server"] = "Server", + ["Server Log"] = "Server Log", + ["Set Outfit"] = "Bestäm Utstyrsel", + ["Shielding"] = "Sköld", + ["Show all items"] = "Visa alla saker", + ["Show connection ping"] = false, + ["Show Depot Only"] = "Visa bara förråd", + ["Show event messages in console"] = "Visa event meddelanden i konsol", + ["Show frame rate"] = "Visa FPS", + ["Show info messages in console"] = "Visa info meddelanden i konsol", + ["Show left panel"] = "Visa vänster panel", + ["Show levels in console"] = "Visa nivåer i konsol", + ["Show Offline"] = false, + ["Show private messages in console"] = "Visa privata meddelanden i konsol", + ["Show private messages on screen"] = "Visa privata meddelanden på skärmen", + ["Show Server Messages"] = false, + ["Show status messages in console"] = "Visa statusmeddelanden i konsol", + ["Show Text"] = "Visa Text", + ["Show timestamps in console"] = "Visa tidstämpel i konsol", + ["Show your depot items only"] = "Visa mitt förråd endast", + ["Skills"] = "Förmågor", + ["Soul"] = "Själ", + ["Soul Points"] = "Själpoäng", + ["Special"] = false, + ["Speed"] = false, + ["Spell Cooldowns"] = false, + ["Spell List"] = false, + ["Stamina"] = "Uthållighet", + ["Statement:"] = "Påstående:", + ["Statement Report"] = "Påståenderapport", + ["Statistics"] = "Statistik", + ["Stop Attack"] = "Sluta Attackera", + ["Stop Follow"] = "Sluta Följa", + ["Support"] = false, + ["%s: (use object)"] = "%s: (Använd objekt)", + ["%s: (use object on target)"] = "%s: (Använd objekt på mål)", + ["%s: (use object on yourself)"] = "%s: (Använd objekt på mig)", + ["%s: (use object with crosshair)"] = "%s: (Använd objekt med sikte)", + ["Sword Fighting"] = "Svärd Stridning", + ["Terminal"] = "Terminal", + ["There is no way."] = "Det finns ingen väg.", + ["Title"] = false, + ["Total Price:"] = "Totalt Pris:", + ["Trade"] = "Handel", + ["Trade with ..."] = "Handla med ...", + ["Trying to reconnect in %s seconds."] = "Försöker koppla upp igen om %s sekunder.", + ["Unable to load dat file, please place a valid dat in '%s'"] = "kan ej ladda dat filen, lägg en giltig dat fil i '%s'", + ["Unable to load spr file, please place a valid spr in '%s'"] = "kan ej ladda spr filen, lägg en giltig spr fil i '%s'", + ["Unable to logout."] = "Kan ej logga ut.", + ["Unignore"] = false, + ["Unload"] = "Avladda", + ["Update needed"] = false, + ["Use"] = "Använd", + ["Use on target"] = "Använd på mål", + ["Use on yourself"] = "Använd på mig", + ["Use with ..."] = "Använd med ...", + ["Version"] = "Version", + ["VIP List"] = "VIP Lista", + ["Voc."] = "Kallelse", + ["Vocation"] = false, + ["Waiting List"] = "Kölista", + ["Website"] = "Websida", + ["Weight:"] = "Vikt:", + ["Will detect when to use diagonal step based on the\nkeys you are pressing"] = false, + ["With crosshair"] = "Med sikte", + ["Yes"] = "Ja", + ["You are bleeding"] = "Du Blöder", + ["You are burning"] = "Du brinner", + ["You are cursed"] = "Du är fördömd", + ["You are dazzled"] = "Du är chockad", + ["You are dead."] = "Du är död.", + ["You are dead"] = "Du är död", + ["You are drowning"] = "Du drunknar", + ["You are drunk"] = "Du är full.", + ["You are electrified"] = "Du är elektrifierad", + ["You are freezing"] = "Du Fryser", + ["You are hasted"] = "Du är i hast", + ["You are hungry"] = "Du är hungrig", + ["You are paralysed"] = "Du är paralyserad", + ["You are poisoned"] = "Du är förgiftad", + ["You are protected by a magic shield"] = "Du är skyddad av en magisk sköld", + ["You are strengthened"] = "Du är förstärkt", + ["You are within a protection zone"] = "Du är inom en skyddszon", + ["You can enter new text."] = "Du kan skriva i ny text.", + ["You have %s percent"] = "Du har %s procent", + ["You have %s percent to go"] = "Du har %s procent kvar", + ["You may not logout during a fight"] = "Du kan ej logga ut i strid", + ["You may not logout or enter a protection zone"] = "Du kan ej logga ut eller gå in i en skyddszon", + ["You must enter a comment."] = "Du måste skriva en kommentar", + ["You must enter a valid server address and port."] = "Du måste fylla i en giltig server adress och port", + ["You must select a character to login!"] = "Du måste välja en karaktär för att logga in!", + ["Your Capacity:"] = "Din Kapacitet:", + ["You read the following, written by \n%s\n"] = "Du läser följande, Skrivet av \n%s\n", + ["You read the following, written on \n%s.\n"] = false, + ["Your Money:"] = "Dina Pengar:", + } +} + +modules.client_locales.installLocale(locale) \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_fragment.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_fragment.frag new file mode 100644 index 0000000..4e7785a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_fragment.frag @@ -0,0 +1,11 @@ + +varying vec2 v_TexCoord; +uniform vec4 u_Color; +uniform sampler2D u_Tex0; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_vertex.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_vertex.frag new file mode 100644 index 0000000..0d5f690 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_default_vertex.frag @@ -0,0 +1,14 @@ + +attribute vec2 a_Vertex; +attribute vec2 a_TexCoord; + +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_fragment.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_fragment.frag new file mode 100644 index 0000000..6757750 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_fragment.frag @@ -0,0 +1,15 @@ + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform vec4 u_Color; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; + +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord) * u_Color; + gl_FragColor += texture2D(u_Tex1, v_TexCoord2); + if(gl_FragColor.a < 0.01) + discard; +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_vertex.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_vertex.frag new file mode 100644 index 0000000..f38475b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/map_rainbow_vertex.frag @@ -0,0 +1,33 @@ +attribute vec2 a_TexCoord; +attribute vec2 a_Vertex; + +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; + +uniform mat3 u_TextureMatrix; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; + +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + + v_TexCoord2 = ((a_Vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_fragment.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_fragment.frag new file mode 100644 index 0000000..1f205d1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_fragment.frag @@ -0,0 +1,17 @@ +uniform mat4 u_Color; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +uniform sampler2D u_Tex0; +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2); + if(texcolor.r > 0.9) { + gl_FragColor *= texcolor.g > 0.9 ? u_Color[0] : u_Color[1]; + } else if(texcolor.g > 0.9) { + gl_FragColor *= u_Color[2]; + } else if(texcolor.b > 0.9) { + gl_FragColor *= u_Color[3]; + } + if(gl_FragColor.a < 0.01) discard; +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_vertex.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_vertex.frag new file mode 100644 index 0000000..44f0d76 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_default_vertex.frag @@ -0,0 +1,16 @@ +attribute vec2 a_Vertex; +attribute vec2 a_TexCoord; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform vec2 u_Offset; + +void main() +{ + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy; +} + diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_fragment.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_fragment.frag new file mode 100644 index 0000000..3c884a8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_fragment.frag @@ -0,0 +1,16 @@ +uniform mat4 u_Color; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +uniform sampler2D u_Tex0; +uniform sampler2D u_Tex1; +void main() +{ + gl_FragColor = texture2D(u_Tex0, v_TexCoord); + vec4 texcolor = texture2D(u_Tex0, v_TexCoord2); + vec4 effectColor = texture2D(u_Tex1, v_TexCoord3); + if(texcolor.a > 0.1) { + gl_FragColor *= effectColor; + } + if(gl_FragColor.a < 0.01) discard; +} \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_vertex.frag b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_vertex.frag new file mode 100644 index 0000000..ca554bd --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/shaders/outfit_rainbow_vertex.frag @@ -0,0 +1,47 @@ +attribute vec2 a_TexCoord; +uniform mat3 u_TextureMatrix; +varying vec2 v_TexCoord; +varying vec2 v_TexCoord2; +varying vec2 v_TexCoord3; +attribute vec2 a_Vertex; +uniform mat3 u_TransformMatrix; +uniform mat3 u_ProjectionMatrix; +uniform vec2 u_Offset; +uniform vec2 u_Center; +uniform float u_Time; + +vec2 effectTextureSize = vec2(466.0, 342.0); +vec2 direction = vec2(1.0,0.2); +float speed = 200.0; + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void main() +{ + vec2 offset = direction * speed * u_Time; + gl_Position = vec4((u_ProjectionMatrix * u_TransformMatrix * vec3(a_Vertex.xy, 1.0)).xy, 1.0, 1.0); + v_TexCoord = (u_TextureMatrix * vec3(a_TexCoord,1.0)).xy; + v_TexCoord2 = (u_TextureMatrix * vec3(a_TexCoord + u_Offset,1.0)).xy; + + vec2 vertex = a_Vertex; + if(vertex.x < u_Center.x) { + vertex.x = effectTextureSize.x / 10.0; + } + if(vertex.x > u_Center.x) { + vertex.x = effectTextureSize.x - effectTextureSize.x / 10.0; + } + if(vertex.y < u_Center.y) { + vertex.y = effectTextureSize.y / 10.0; + } + if(vertex.y > u_Center.y) { + vertex.y = effectTextureSize.y - effectTextureSize.y / 10.0; + } + + v_TexCoord3 = ((vertex + direction * u_Time * speed) / effectTextureSize); +} + diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Creature_Detected.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Creature_Detected.ogg new file mode 100644 index 0000000..fd36978 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Creature_Detected.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Health.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Health.ogg new file mode 100644 index 0000000..d4b1062 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Health.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Mana.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Mana.ogg new file mode 100644 index 0000000..e2d7e1d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Low_Mana.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Attack.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Attack.ogg new file mode 100644 index 0000000..84e9ef4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Attack.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Detected.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Detected.ogg new file mode 100644 index 0000000..34de63e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Player_Detected.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Private_Message.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Private_Message.ogg new file mode 100644 index 0000000..f3e5984 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/Private_Message.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/alarm.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/alarm.ogg new file mode 100644 index 0000000..a45d4c8 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/alarm.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/sounds/magnum.ogg b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/magnum.ogg new file mode 100644 index 0000000..d910ca1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/sounds/magnum.ogg differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-buttons.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-buttons.otui new file mode 100644 index 0000000..4a81411 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-buttons.otui @@ -0,0 +1,91 @@ +Button < UIButton + font: verdana-11px-antialised + color: #dfdfdfff + size: 106 23 + text-offset: 0 1 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + opacity: 1.0 + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +TabButton < UIButton + size: 22 23 + image-source: /images/ui/tabbutton_rounded + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + text-offset: 0 1 + icon-color: #dfdfdf + color: #dfdfdf + + $hover !on: + image-clip: 0 23 22 23 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf66 + icon-color: #dfdfdf + + $on: + image-clip: 0 46 22 23 + color: #dfdfdf + +NextButton < UIButton + size: 12 21 + image-source: /images/ui/arrow_horizontal + image-clip: 12 0 12 21 + image-color: #ffffff + text-offset: 0 1 + + $hover !disabled: + image-clip: 12 21 12 21 + + $pressed: + image-clip: 12 21 12 21 + + $disabled: + image-color: #dfdfdf88 + +PreviousButton < UIButton + size: 12 21 + image-source: /images/ui/arrow_horizontal + image-clip: 0 0 12 21 + image-color: #ffffff + text-offset: 0 1 + + $hover !disabled: + image-clip: 0 21 12 21 + + $pressed: + image-clip: 0 21 12 21 + + $disabled: + image-color: #dfdfdf88 + +AddButton < UIButton + size: 20 20 + image-source: /images/ui/icon_add + image-color: #dfdfdfff + text-offset: 0 1 + + $hover !disabled: + image-color: #dfdfdf99 + + $pressed: + image-color: #dfdfdf44 + + $disabled: + image-color: #dfdfdf55 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-checkboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-checkboxes.otui new file mode 100644 index 0000000..3edccec --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-checkboxes.otui @@ -0,0 +1,64 @@ +CheckBox < UICheckBox + size: 16 16 + text-align: left + text-offset: 18 1 + color: #dfdfdf + image-color: #dfdfdfff + image-rect: 0 0 15 15 + image-source: /images/ui/checkbox + + $hover !disabled: + color: #ffffff + + $!checked: + image-clip: 0 0 15 15 + + $hover !checked: + image-clip: 0 15 15 15 + + $checked: + image-clip: 0 30 15 15 + + $hover checked: + image-clip: 0 45 15 15 + + $disabled: + image-color: #dfdfdf88 + color: #dfdfdf88 + opacity: 0.8 + +ColorBox < UICheckBox + size: 16 16 + image-color: #dfdfdfff + image-source: /images/ui/colorbox + + $checked: + image-clip: 16 0 16 16 + + $!checked: + image-clip: 0 0 16 16 + +ButtonBox < UICheckBox + font: verdana-11px-antialised + color: #dfdfdfff + size: 106 23 + text-offset: 0 0 + text-align: center + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + + $hover !disabled: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + color: #dfdfdf + + $disabled: + color: #dfdfdf88 + image-color: #dfdfdf88 + +ButtonBoxRounded < ButtonBox + image-source: /images/ui/button_rounded \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-comboboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-comboboxes.otui new file mode 100644 index 0000000..a4ad9da --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-comboboxes.otui @@ -0,0 +1,106 @@ +ComboBoxPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBoxPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBox < UIComboBox + font: verdana-11px-antialised + color: #dfdfdf + size: 91 23 + text-offset: 3 0 + text-align: left + image-source: /images/ui/combobox_square + image-border: 3 + image-border-right: 19 + image-clip: 0 0 91 23 + + $hover !disabled: + image-clip: 0 23 91 23 + + $on: + image-clip: 0 46 91 23 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +ComboBoxRoundedPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRoundedPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRounded < ComboBox + image-source: /images/ui/combobox_rounded + image-border: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creaturebuttons.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creaturebuttons.otui new file mode 100644 index 0000000..d96356c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creaturebuttons.otui @@ -0,0 +1,49 @@ +CreatureButton < UICreatureButton + height: 20 + margin-bottom: 5 + + UICreature + id: creature + size: 22 22 + anchors.left: parent.left + anchors.top: parent.top + phantom: true + + UIWidget + id: spacer + width: 3 + anchors.left: creature.right + anchors.top: creature.top + phantom: true + + UIWidget + id: skull + height: 11 + anchors.left: spacer.right + anchors.top: spacer.top + phantom: true + + UIWidget + id: emblem + height: 11 + anchors.left: skull.right + anchors.top: creature.top + phantom: true + + Label + id: label + anchors.left: emblem.right + anchors.right: parent.right + anchors.top: creature.top + color: #888888 + margin-left: 2 + phantom: true + + LifeProgressBar + id: lifeBar + height: 5 + anchors.left: spacer.right + anchors.right: parent.right + anchors.top: label.bottom + margin-top: 2 + phantom: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creatures.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creatures.otui new file mode 100644 index 0000000..c6664ec --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-creatures.otui @@ -0,0 +1,10 @@ +Creature < UICreature + size: 80 80 + padding: 1 + image-source: /images/ui/panel_flat + image-border: 1 + border-width: 1 + border-color: alpha + + $checked: + border-color: white diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-items.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-items.otui new file mode 100644 index 0000000..18bfa29 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-items.otui @@ -0,0 +1,10 @@ +Item < UIItem + size: 34 34 + padding: 1 + image-source: /images/ui/item + font: verdana-11px-rounded + border-color: white + color: white + + $disabled: + color: #646464 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-labels.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-labels.otui new file mode 100644 index 0000000..8edfbb1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-labels.otui @@ -0,0 +1,23 @@ +Label < UILabel + font: verdana-11px-antialised + color: #dfdfdf + + $disabled: + color: #dfdfdf88 + +FlatLabel < UILabel + font: verdana-11px-antialised + color: #dfdfdf + size: 86 20 + text-offset: 3 3 + image-source: /images/ui/panel_flat + image-border: 1 + + $disabled: + color: #dfdfdf88 + +MenuLabel < Label + +GameLabel < UILabel + font: verdana-11px-antialised + color: #dfdfdf diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-listboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-listboxes.otui new file mode 100644 index 0000000..d52ff2f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-listboxes.otui @@ -0,0 +1,19 @@ +TextList < UIScrollArea + layout: verticalBox + border-width: 1 + border-color: #272727 + background-color: #636363 + padding: 1 + auto-focus: none + +HorizontalList < UIScrollArea + layout: horizontalBox + border-width: 1 + border-color: #272727 + background-color: #636363 + +VerticalList < UIScrollArea + layout: verticalBox + border-width: 1 + border-color: #272727 + background-color: #636363 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-panels.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-panels.otui new file mode 100644 index 0000000..15fbbc3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-panels.otui @@ -0,0 +1,19 @@ +Panel < UIWidget + phantom: true + auto-focus: first + +ScrollablePanel < UIScrollArea + phantom: true + auto-focus: first + +FlatPanel < Panel + image-source: /images/ui/panel_flat + image-border: 1 + +ScrollableFlatPanel < ScrollablePanel + image-source: /images/ui/panel_flat + image-border: 1 + +LightFlatPanel < Panel + image-source: /images/ui/panel_lightflat + image-border: 1 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-progressbars.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-progressbars.otui new file mode 100644 index 0000000..6eac011 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-progressbars.otui @@ -0,0 +1,34 @@ +ProgressBar < UIProgressBar + height: 16 + background-color: red + image-source: /images/ui/progressbar + image-border: 1 + font: verdana-11px-rounded + text-offset: 0 2 + + $!on: + visible: false + margin-top: 0 + margin-bottom: 0 + height: 0 + +LifeProgressBar < UIProgressBar + height: 16 + background-color: green + border: 1 black + font: verdana-11px-rounded + text-offset: 0 2 + margin: 2 + +ProgressRect < UIProgressRect + anchors.fill: parent + phantom: true + color: white + background-color: #00000088 + font: verdana-11px-rounded + +HealthBar < ProgressBar + background-color: #ff4444 + +ManaBar < ProgressBar + background-color: #4444ff diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-scrollbars.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-scrollbars.otui new file mode 100644 index 0000000..7f6b5d1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-scrollbars.otui @@ -0,0 +1,108 @@ +ScrollBarSlider < UIButton + id: sliderButton + anchors.centerIn: parent + size: 13 17 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + +ScrollBarValueLabel < Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center + +VerticalScrollBar < UIScrollBar + orientation: vertical + width: 13 + height: 39 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 13 13 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 13 13 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel + +HorizontalScrollBar < UIScrollBar + orientation: horizontal + height: 13 + width: 39 + image-source: /images/ui/scrollbar + image-clip: 0 65 52 13 + image-border: 1 + + $disabled: + color: #bbbbbb88 + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 39 13 13 + image-color: #ffffffff + size: 13 13 + $hover: + image-clip: 13 39 13 13 + $pressed: + image-clip: 26 39 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 13 13 + image-source: /images/ui/scrollbar + image-clip: 0 52 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 52 13 13 + $pressed: + image-clip: 26 52 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-separators.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-separators.otui new file mode 100644 index 0000000..420d0fa --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-separators.otui @@ -0,0 +1,13 @@ +HorizontalSeparator < UIWidget + image-source: /images/ui/separator_horizontal + image-border: 1 + height: 2 + phantom: true + focusable: false + +VerticalSeparator < UIWidget + image-source: /images/ui/separator_vertical + image-border: 1 + width: 2 + phantom: true + focusable: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-splitters.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-splitters.otui new file mode 100644 index 0000000..ca9a4f6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-splitters.otui @@ -0,0 +1,9 @@ +Splitter < UISplitter + size: 4 4 + opacity: 0 + background: #ffffff44 + +ResizeBorder < UIResizeBorder + size: 4 4 + opacity: 0 + background: #ffffff44 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-textedits.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-textedits.otui new file mode 100644 index 0000000..b5d09bc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-textedits.otui @@ -0,0 +1,20 @@ +TextEdit < UITextEdit + font: verdana-11px-antialised + color: #272727 + size: 86 22 + text-offset: 0 4 + opacity: 1 + padding: 4 + image-source: /images/ui/textedit + image-border: 1 + selection-color: #272727 + selection-background-color: #cccccc + $disabled: + color: #27272788 + opacity: 0.5 + +PasswordTextEdit < TextEdit + text-hidden: true + +MultilineTextEdit < TextEdit + multiline: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-windows.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-windows.otui new file mode 100644 index 0000000..9250784 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/10-windows.otui @@ -0,0 +1,35 @@ +Window < UIWindow + font: verdana-11px-antialised + size: 200 200 + opacity: 1 + color: #dfdfdf + text-offset: 0 6 + text-align: top + image-source: /images/ui/window + image-border: 6 + image-border-top: 27 + padding-top: 36 + padding-left: 16 + padding-right: 16 + padding-bottom: 16 + + $disabled: + color: #dfdfdf88 + + $dragging: + opacity: 0.8 + +HeadlessWindow < UIWindow + image-source: /images/ui/window_headless + image-border: 5 + padding: 5 + +MainWindow < Window + anchors.centerIn: parent + +StaticWindow < Window + &static: true + +StaticMainWindow < StaticWindow + anchors.centerIn: parent + \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-imageview.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-imageview.otui new file mode 100644 index 0000000..73bda84 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-imageview.otui @@ -0,0 +1,6 @@ +ImageView < UIImageView + image-smooth: false + image-fixed-ratio: true + draggable: true + border-width: 2 + border-color: #000000 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-popupmenus.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-popupmenus.otui new file mode 100644 index 0000000..d7b71fc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-popupmenus.otui @@ -0,0 +1,83 @@ +PopupMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupMenu < UIPopupMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 + +PopupScrollMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupScrollMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupScrollMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupScrollMenu < UIPopupScrollMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-smallscrollbar.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-smallscrollbar.otui new file mode 100644 index 0000000..4396a0b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-smallscrollbar.otui @@ -0,0 +1,60 @@ +SmallScrollBar < UIScrollBar + orientation: vertical + margin-bottom: 1 + step: 20 + width: 8 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 8 8 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 8 8 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: sliderButton + anchors.centerIn: parent + size: 8 11 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + + Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-spinboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-spinboxes.otui new file mode 100644 index 0000000..1ceddf4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-spinboxes.otui @@ -0,0 +1,34 @@ +SpinBox < TextEdit + __class: UISpinBox + text-align: left + size: 86 22 + padding: 0 + padding-left: 2 + + Button + id: up + size: 11 11 + image-source: /images/ui/spinbox_up + image-border: 1 + image-clip: 0 0 10 10 + anchors.top: parent.top + anchors.right: parent.right + + $hover: + image-clip: 0 10 10 10 + $pressed: + image-clip: 0 20 10 10 + + Button + id: down + size: 11 11 + image-source: /images/ui/spinbox_down + image-border: 1 + image-clip: 0 0 10 10 + anchors.bottom: parent.bottom + anchors.right: parent.right + + $hover: + image-clip: 0 10 10 10 + $pressed: + image-clip: 0 20 10 10 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tabbars.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tabbars.otui new file mode 100644 index 0000000..7e6b9b9 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tabbars.otui @@ -0,0 +1,131 @@ +MoveableTabBar < UIMoveableTabBar + size: 80 21 +MoveableTabBarPanel < Panel +MoveableTabBarButton < UIButton + size: 20 21 + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 20 21 + image-border: 3 + image-border-bottom: 0 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + anchors.left: parent.left + padding: 5 + + $hover !checked: + image-clip: 0 21 20 21 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 42 20 21 + color: #dfdfdf + + $on !checked: + color: #de6f6f + +TabBar < UITabBar + size: 80 21 + Panel + id: buttonsPanel + anchors.fill: parent +TabBarPanel < Panel +TabBarButton < UIButton + size: 20 21 + image-source: /images/ui/tabbutton_square + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 20 21 + image-border: 3 + image-border-bottom: 0 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + padding: 5 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + margin-left: 5 + + $hover !checked: + image-clip: 0 21 20 21 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 42 20 21 + color: #dfdfdf + + $on !checked: + color: #dfdfdf + +TabBarRounded < TabBar +TabBarRoundedPanel < TabBarPanel +TabBarRoundedButton < TabBarButton + image-source: /images/ui/tabbutton_rounded + size: 22 23 + image-clip: 0 0 22 23 + + $hover !checked: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + +TabBarVertical < UITabBar + width: 96 + ScrollableFlatPanel + id: buttonsPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: scrollBar.left + anchors.bottom: parent.bottom + vertical-scrollbar: scrollBar + margin-right: 1 + padding-top: 10 + + VerticalScrollBar + id: scrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 16 + pixels-scroll: true + $!on: + width: 0 +TabBarVerticalPanel < Panel +TabBarVerticalButton < UIButton + size: 48 48 + color: #aaaaaa + anchors.left: parent.left + anchors.right: parent.right + text-align: bottom + icon-align: top + icon-offset-y: 2 + icon-color: #888888 + $first: + anchors.top: parent.top + $!first: + anchors.top: prev.bottom + margin-top: 10 + $hover !checked: + color: white + icon-color: #dfdfdf + $disabled: + icon-color: #333333 + $checked: + icon-color: #ffffff + color: #80c7f8 + $on !checked: + color: #F55E5E diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tables.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tables.otui new file mode 100644 index 0000000..51e53e5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-tables.otui @@ -0,0 +1,62 @@ +Table < UITable + layout: verticalBox + header-column-style: TableHeaderColumn + header-row-style: TableHeaderRow + column-style: TableColumn + row-style: TableRow + +TableData < UIScrollArea + layout: verticalBox + +TableRow < UITableRow + layout: horizontalBox + height: 10 + text-wrap: true + focusable: true + even-background-color: alpha + odd-background-color: #00000022 + + $focus: + background-color: #294f6d + color: #ffffff + +TableColumn < Label + width: 30 + text-wrap: true + focusable: false + +TableHeaderRow < Label + layout: horizontalBox + focusable: false + height: 10 + text-wrap: true + +TableHeaderColumn < UITableHeaderColumn + font: verdana-11px-antialised + background-color: alpha + color: #dfdfdfff + height: 23 + focusable: true + text-offset: 0 0 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + enabled: false + focusable: false + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +SortableTableHeaderColumn < TableHeaderColumn + enabled: true + focusable: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-topmenu.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-topmenu.otui new file mode 100644 index 0000000..cfed71a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/20-topmenu.otui @@ -0,0 +1,116 @@ +TopButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-border: 3 + image-color: #ffffffff + icon-color: #ffffffff + + $on: + image-source: /images/ui/button_top_blink + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopToggleButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-color: #ffffffff + image-border: 3 + icon-color: #ffffffff + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopMenuButtonsPanel < Panel + layout: + type: horizontalBox + spacing: 4 + fit-children: true + padding: 6 4 + +TopMenuPanel < Panel + height: 36 + image-source: /images/ui/panel_top + image-repeated: true + focusable: false + +TopMenuFrameCounterLabel < Label + font: verdana-11px-rounded + color: white + margin-top: 4 + margin-left: 5 + +TopMenuPingLabel < Label + font: verdana-11px-rounded + +TopMenu < TopMenuPanel + id: topMenu + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + TopMenuButtonsPanel + id: leftButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + + TopMenuButtonsPanel + id: leftGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + visible: false + + TopMenuFrameCounterLabel + id: fpsLabel + text-auto-resize: true + anchors.top: parent.top + anchors.left: leftGameButtonsPanel.right + + TopMenuPingLabel + color: white + id: pingLabel + text-auto-resize: true + anchors.top: fpsLabel.bottom + anchors.left: fpsLabel.left + + Label + id: onlineLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + font: verdana-11px-antialised + text-align: center + text-auto-resize: true + + TopMenuButtonsPanel + id: rightButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + TopMenuButtonsPanel + id: rightGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: prev.left + visible: false \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-inputboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-inputboxes.otui new file mode 100644 index 0000000..90cbb65 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-inputboxes.otui @@ -0,0 +1,30 @@ +InputBoxLabel < Label + fixed-size: true + text-align: left +InputBoxLineEdit < TextEdit +InputBoxTextEdit < MultilineTextEdit + text-wrap: true +InputBoxSpinBox < SpinBox +InputBoxCheckBox < CheckBox +InputBoxComboBox < ComboBox +InputBoxComboBoxPopupMenu < ComboBoxPopupMenu +InputBoxComboBoxPopupMenuButton < ComboBoxPopupMenuButton +InputBoxButton < Button + margin-left: 10 + fixed-size: true + +InputBoxButtonsPanel < Panel + height: 20 + margin-top: 4 + focusable: false + layout: + type: horizontalBox + align-right: true + +InputBoxWindow < MainWindow + __class: UIInputBox + width: 260 + layout: + type: verticalBox + fit-children: true + spacing: 2 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-messageboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-messageboxes.otui new file mode 100644 index 0000000..04fcdf6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-messageboxes.otui @@ -0,0 +1,15 @@ +MessageBoxLabel < Label + id: messageBoxLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + text-wrap: true + text-auto-resize: true + +MessageBoxButtonHolder < UIWidget + id: buttonHolder + margin-top: 10 + anchors.bottom: parent.bottom + +MessageBoxButton < Button + margin-left: 10 + width: 80 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-miniwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-miniwindow.otui new file mode 100644 index 0000000..2e4a363 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/30-miniwindow.otui @@ -0,0 +1,128 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 4 16 16 + width: 192 + height: 200 + text-offset: 24 5 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 23 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 24 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 18 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 5 + margin-right: 5 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 112 0 14 14 + + $hover: + image-clip: 112 14 14 14 + + $pressed: + image-clip: 112 28 14 14 + + $on: + image-clip: 98 0 14 14 + + $on hover: + image-clip: 98 14 14 14 + + $on pressed: + image-clip: 98 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 22 + margin-right: 3 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 3 + margin-top: 22 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < MiniWindow diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-console.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-console.otui new file mode 100644 index 0000000..ed9e0ae --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-console.otui @@ -0,0 +1,186 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 2 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 28 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 28 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 4 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 13 + margin-top: 8 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-left: 3 + margin-top: 6 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-left: 5 + margin-top: 3 + margin-right: 5 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 6 + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 6 + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 4 + margin-top: 4 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 20 20 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + margin-bottom: 6 + @onClick: sayModeChange() + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 6 + margin-left: 6 + margin-bottom: 6 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-container.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-container.otui new file mode 100644 index 0000000..ddfada8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-container.otui @@ -0,0 +1,74 @@ +PageButton < Button + size: 30 18 + margin: 1 + + +ContainerWindow < MiniWindow + height: 150 + &save: true + &containerWindow: true + + UIItem + id: containerItemWidget + virtual: true + size: 16 16 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 1 + margin-left: 3 + + UIButton + id: upButton + anchors.top: lockButton.top + anchors.right: lockButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 42 0 14 14 + + $hover: + image-clip: 42 14 14 14 + + $pressed: + image-clip: 42 28 14 14 + + Panel + id: pagePanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: miniwindowTopBar.bottom + margin: 1 3 0 3 + background: #00000066 + height: 20 + + $on: + visible: true + + $!on: + visible: false + + Label + id: pageLabel + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 2 + text-auto-resize: true + + PageButton + id: prevPageButton + text: < + anchors.top: parent.top + anchors.left: parent.left + + PageButton + id: nextPageButton + text: > + anchors.top: parent.top + anchors.right: parent.right + + MiniWindowContents + padding-right: 0 + layout: + type: grid + cell-size: 34 34 + flow: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-entergame.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-entergame.otui new file mode 100644 index 0000000..71f850b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-entergame.otui @@ -0,0 +1,3 @@ +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') + size: 260 354 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-gamebuttons.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-gamebuttons.otui new file mode 100644 index 0000000..76c3b9e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-gamebuttons.otui @@ -0,0 +1,2 @@ +GameButtonsWindow < MiniWindow + height: 26 diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-healthinfo.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-healthinfo.otui new file mode 100644 index 0000000..f676a1c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-healthinfo.otui @@ -0,0 +1,147 @@ +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + image-auto-resize: true + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < MiniWindow + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + height: 123 + + MiniWindowContents + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 1 + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-inventory.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-inventory.otui new file mode 100644 index 0000000..f6fa1c5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-inventory.otui @@ -0,0 +1,299 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + font: verdana-11px-antialised + height: 18 + margin-top: 2 + text-align: center + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < MiniWindow + icon: /images/topbuttons/inventory + height: 200 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + + MiniWindowContents + anchors.left: parent.left + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-minimap.otui b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-minimap.otui new file mode 100644 index 0000000..8a7a55e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/data/styles/40-minimap.otui @@ -0,0 +1,268 @@ +MinimapFlag < UIWidget + size: 11 11 + focusable: false + +MinimapCross < UIWidget + focusable: false + phantom: true + image: /images/game/minimap/cross + size: 16 16 + +MinimapFloorUpButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_up + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapFloorDownButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_down + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapZoomInButton < Button + text: + + size: 20 20 + margin-right: 4 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_in + +MinimapZoomOutButton < Button + text: - + size: 20 20 + margin-right: 4 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_out + +MinimapResetButton < Button + !text: tr('Center') + size: 44 20 + anchors.left: parent.left + anchors.top: parent.top + margin: 4 + +Minimap < UIMinimap + draggable: true + focusable: false + cross: true + color: black + + MinimapFloorUpButton + id: floorUpWidget + @onClick: self:getParent():floorUp(1) + + MinimapFloorDownButton + id: floorDownWidget + @onClick: self:getParent():floorDown(1) + + MinimapZoomInButton + id: zoomInWidget + @onClick: self:getParent():zoomIn() + + MinimapZoomOutButton + id: zoomOutWidget + @onClick: self:getParent():zoomOut() + + MinimapResetButton + id: resetWidget + @onClick: self:getParent():reset() + + +// Minimap Flag Create Window + + +MinimapFlagCheckBox < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-offset: 2 4 + anchors.left: prev.right + anchors.top: prev.top + $!checked: + image-clip: 26 0 26 26 + $hover !checked: + image-clip: 78 0 26 26 + $checked: + image-clip: 0 0 26 26 + $hover checked: + image-clip: 52 0 26 26 + +MinimapFlagWindow < MainWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + id: position + text-auto-resize: true + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + + MinimapFlagCheckBox + id: flag0 + icon-source: /images/game/minimap/flag0 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag1 + icon-source: /images/game/minimap/flag1 + + MinimapFlagCheckBox + id: flag2 + icon-source: /images/game/minimap/flag2 + + MinimapFlagCheckBox + id: flag3 + icon-source: /images/game/minimap/flag3 + + MinimapFlagCheckBox + id: flag4 + icon-source: /images/game/minimap/flag4 + + MinimapFlagCheckBox + id: flag5 + icon-source: /images/game/minimap/flag5 + + MinimapFlagCheckBox + id: flag6 + icon-source: /images/game/minimap/flag6 + + MinimapFlagCheckBox + id: flag7 + icon-source: /images/game/minimap/flag7 + + MinimapFlagCheckBox + id: flag8 + icon-source: /images/game/minimap/flag8 + + MinimapFlagCheckBox + id: flag9 + icon-source: /images/game/minimap/flag9 + + MinimapFlagCheckBox + id: flag10 + icon-source: /images/game/minimap/flag10 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag11 + icon-source: /images/game/minimap/flag11 + + MinimapFlagCheckBox + id: flag12 + icon-source: /images/game/minimap/flag12 + + MinimapFlagCheckBox + id: flag13 + icon-source: /images/game/minimap/flag13 + + MinimapFlagCheckBox + id: flag14 + icon-source: /images/game/minimap/flag14 + + MinimapFlagCheckBox + id: flag15 + icon-source: /images/game/minimap/flag15 + + MinimapFlagCheckBox + id: flag16 + icon-source: /images/game/minimap/flag16 + + MinimapFlagCheckBox + id: flag17 + icon-source: /images/game/minimap/flag17 + + MinimapFlagCheckBox + id: flag18 + icon-source: /images/game/minimap/flag18 + + MinimapFlagCheckBox + id: flag19 + icon-source: /images/game/minimap/flag19 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + +MinimapWindow < MiniWindow + height: 150 + + Label + text: ? + text-align: center + phantom: false + !tooltip: tr('Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks\nPress Ctrl+Shift+M to view the entire game map') + anchors.top: lockButton.top + anchors.right: lockButton.left + margin-right: 3 + size: 14 14 + + MiniWindowContents + Minimap + id: minimap + anchors.fill: parent + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.dat b/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.dat new file mode 100644 index 0000000..4f85a49 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.dat differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.spr b/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.spr new file mode 100644 index 0000000..0ba1fc4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/data/things/792/Tibia.spr differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/init.lua b/app/SabrehavenServer/SabrehavenOTClient/init.lua new file mode 100644 index 0000000..77bb6bf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/init.lua @@ -0,0 +1,86 @@ +-- CONFIG +APP_NAME = "tibianus" -- important, change it, it's name for config dir and files in appdata +APP_VERSION = 1341 -- client version for updater and login to identify outdated client +DEFAULT_LAYOUT = "retro" -- on android it's forced to "mobile", check code bellow + +-- If you don't use updater or other service, set it to updater = "" +Services = { + website = "https://tibianus.com", -- currently not used + updater = "https://tibianus.com/api/updater.php", + stats = "", + crash = "", + feedback = "", + status = "https://tibianus.com/status.php" +} + +-- Servers accept http login url, websocket login url or ip:port:version +Servers = { + Tibianus = "127.0.0.1:7171:792" +} + +--Server = "ws://otclient.ovh:3000/" +--Server = "ws://127.0.0.1:88/" +--USE_NEW_ENERGAME = true -- uses entergamev2 based on websockets instead of entergame +ALLOW_CUSTOM_SERVERS = true -- if true it shows option ANOTHER on server list + +g_app.setName("OTCv8") +-- CONFIG END + +-- print first terminal message +g_logger.info(os.date("== application started at %b %d %Y %X")) +g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. g_app.getBuildCommit() .. ') made by ' .. g_app.getAuthor() .. ' built on ' .. g_app.getBuildDate() .. ' for arch ' .. g_app.getBuildArch()) + +if not g_resources.directoryExists("/data") then + g_logger.fatal("Data dir doesn't exist.") +end + +if not g_resources.directoryExists("/modules") then + g_logger.fatal("Modules dir doesn't exist.") +end + +-- settings +g_configs.loadSettings("/config.otml") + +-- set layout +local settings = g_configs.getSettings() +local layout = DEFAULT_LAYOUT +if g_app.isMobile() then + layout = "mobile" +elseif settings:exists('layout') then + layout = settings:getValue('layout') +end +g_resources.setLayout(layout) + +-- load mods +g_modules.discoverModules() +g_modules.ensureModuleLoaded("corelib") + +local function loadModules() + -- libraries modules 0-99 + g_modules.autoLoadModules(99) + g_modules.ensureModuleLoaded("gamelib") + + -- client modules 100-499 + g_modules.autoLoadModules(499) + g_modules.ensureModuleLoaded("client") + + -- game modules 500-999 + g_modules.autoLoadModules(999) + g_modules.ensureModuleLoaded("game_interface") + + -- mods 1000-9999 + g_modules.autoLoadModules(9999) +end + +-- report crash +if type(Services.crash) == 'string' and Services.crash:len() > 4 and g_modules.getModule("crash_reporter") then + g_modules.ensureModuleLoaded("crash_reporter") +end + +-- run updater, must use data.zip +if type(Services.updater) == 'string' and Services.updater:len() > 4 + and g_resources.isLoadedFromArchive() and g_modules.getModule("updater") then + g_modules.ensureModuleLoaded("updater") + return Updater.init(loadModules) +end +loadModules() diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/README.md b/app/SabrehavenServer/SabrehavenOTClient/layouts/README.md new file mode 100644 index 0000000..4cbc6ea --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/README.md @@ -0,0 +1,4 @@ +## Layouts overwrite files from `/data` +Foe example, if you have file `/data/images/background.png` and `/layouts/dragonball/images/background.png`, and dragonball layout is selected, then `/layouts/dragonball/images/background.png` will be loaded instead of `/data/images/background.png`. + +## Dont make layout named `default`, this name is reserved diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/README.md b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/README.md new file mode 100644 index 0000000..b0367ed --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/README.md @@ -0,0 +1 @@ +Min. height for mobile is 360, don't make windows bigger than that \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/10-scrollbars.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/10-scrollbars.otui new file mode 100644 index 0000000..63a0ebf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/10-scrollbars.otui @@ -0,0 +1,108 @@ +ScrollBarSlider < UIButton + id: sliderButton + anchors.centerIn: parent + size: 16 20 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + +ScrollBarValueLabel < Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center + +VerticalScrollBar < UIScrollBar + orientation: vertical + width: 16 + height: 39 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel + +HorizontalScrollBar < UIScrollBar + orientation: horizontal + height: 16 + width: 39 + image-source: /images/ui/scrollbar + image-clip: 0 65 52 13 + image-border: 1 + + $disabled: + color: #bbbbbb88 + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 39 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 39 13 13 + $pressed: + image-clip: 26 39 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 52 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 52 13 13 + $pressed: + image-clip: 26 52 13 13 + $disabled: + image-color: #ffffff66 + + ScrollBarSlider + + ScrollBarValueLabel diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/20-smallscrollbar.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/20-smallscrollbar.otui new file mode 100644 index 0000000..e498c0f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/20-smallscrollbar.otui @@ -0,0 +1,60 @@ +SmallScrollBar < UIScrollBar + orientation: vertical + margin-bottom: 1 + step: 20 + width: 16 + image-source: /images/ui/scrollbar + image-clip: 39 0 13 65 + image-border: 1 + pixels-scroll: true + + UIButton + id: decrementButton + anchors.top: parent.top + anchors.left: parent.left + image-source: /images/ui/scrollbar + image-clip: 0 0 13 13 + image-color: #ffffffff + size: 16 16 + $hover: + image-clip: 13 0 13 13 + $pressed: + image-clip: 26 0 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: incrementButton + anchors.bottom: parent.bottom + anchors.right: parent.right + size: 16 16 + image-source: /images/ui/scrollbar + image-clip: 0 13 13 13 + image-color: #ffffffff + $hover: + image-clip: 13 13 13 13 + $pressed: + image-clip: 26 13 13 13 + $disabled: + image-color: #ffffff66 + + UIButton + id: sliderButton + anchors.centerIn: parent + size: 16 20 + image-source: /images/ui/scrollbar + image-clip: 0 26 13 13 + image-border: 2 + image-color: #ffffffff + $hover: + image-clip: 13 26 13 13 + $pressed: + image-clip: 26 26 13 13 + $disabled: + image-color: #ffffff66 + + Label + id: valueLabel + anchors.fill: parent + color: white + text-align: center \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/30-miniwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/30-miniwindow.otui new file mode 100644 index 0000000..41ad5b0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/30-miniwindow.otui @@ -0,0 +1,128 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 4 16 16 + width: 192 + height: 200 + text-offset: 24 5 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 23 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 24 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 18 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 5 + margin-right: 5 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 112 0 14 14 + + $hover: + image-clip: 112 14 14 14 + + $pressed: + image-clip: 112 28 14 14 + + $on: + image-clip: 98 0 14 14 + + $on hover: + image-clip: 98 14 14 14 + + $on pressed: + image-clip: 98 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 22 + margin-right: 3 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 8 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 8 + margin-top: 22 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < MiniWindow diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-console.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-console.otui new file mode 100644 index 0000000..25f9730 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-console.otui @@ -0,0 +1,165 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 2 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 22 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 22 + padding: 5 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 4 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + @onCheckChange: toggleChat() + visible: false + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: parent.left + anchors.top: parent.top + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 22 22 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 6 + @onClick: sayModeChange() + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 22 + margin-right: 6 + margin-left: 6 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-inventory.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-inventory.otui new file mode 100644 index 0000000..d414065 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/mobile/styles/40-inventory.otui @@ -0,0 +1,299 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + font: verdana-11px-antialised + height: 18 + margin-top: 2 + text-align: center + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < MiniWindow + icon: /images/topbuttons/inventory + height: 200 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + + MiniWindowContents + anchors.left: parent.left + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Quests') + @onClick: g_game.requestQuestLog() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/background.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/background.png new file mode 100644 index 0000000..14236e4 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/background.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/channels.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/channels.png new file mode 100644 index 0000000..017d2eb Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/channels.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/clearchannel.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/clearchannel.png new file mode 100644 index 0000000..201bd82 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/clearchannel.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/closechannel.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/closechannel.png new file mode 100644 index 0000000..46ea537 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/closechannel.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/ignore.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/ignore.png new file mode 100644 index 0000000..ab08dc1 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/ignore.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/leftarrow.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/leftarrow.png new file mode 100644 index 0000000..7e065f5 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/leftarrow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/rightarrow.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/rightarrow.png new file mode 100644 index 0000000..4c51e9f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/rightarrow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/say.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/say.png new file mode 100644 index 0000000..7f28bae Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/say.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/trademsg.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/trademsg.png new file mode 100644 index 0000000..9d96ffb Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/trademsg.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/whisper.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/whisper.png new file mode 100644 index 0000000..a66f48f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/whisper.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/yell.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/yell.png new file mode 100644 index 0000000..3fedf22 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/game/console/yell.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/analyzers.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/analyzers.png new file mode 100644 index 0000000..db2fa61 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/analyzers.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio.png new file mode 100644 index 0000000..d4c6dc6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio_mute.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio_mute.png new file mode 100644 index 0000000..33863ad Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/audio_mute.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/battle.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/battle.png new file mode 100644 index 0000000..939baba Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/battle.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/bot.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/bot.png new file mode 100644 index 0000000..508b2b7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/bot.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/ciclopedia.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/ciclopedia.png new file mode 100644 index 0000000..8ecd0bf Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/ciclopedia.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/compedium.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/compedium.png new file mode 100644 index 0000000..6fe888f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/compedium.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/cooldowns.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/cooldowns.png new file mode 100644 index 0000000..fd5d845 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/cooldowns.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/dailyreward.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/dailyreward.png new file mode 100644 index 0000000..1aa046a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/dailyreward.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/debug.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/debug.png new file mode 100644 index 0000000..a04c2fe Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/debug.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/exit.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/exit.png new file mode 100644 index 0000000..15f4b71 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/exit.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/healthinfo.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/healthinfo.png new file mode 100644 index 0000000..68ac924 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/healthinfo.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/hotkeys.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/hotkeys.png new file mode 100644 index 0000000..bbb9901 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/hotkeys.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/inventory.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/inventory.png new file mode 100644 index 0000000..f5a3479 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/inventory.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/login.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/login.png new file mode 100644 index 0000000..3019fae Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/login.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/logout.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/logout.png new file mode 100644 index 0000000..4813b91 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/logout.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/minimap.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/minimap.png new file mode 100644 index 0000000..c397923 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/minimap.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/modulemanager.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/modulemanager.png new file mode 100644 index 0000000..2a5fc48 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/modulemanager.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/motd.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/motd.png new file mode 100644 index 0000000..793fe7a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/motd.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/options.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/options.png new file mode 100644 index 0000000..64a2186 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/options.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/particles.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/particles.png new file mode 100644 index 0000000..2452eed Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/particles.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/prey.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/prey.png new file mode 100644 index 0000000..ddef6ee Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/prey.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/questlog.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/questlog.png new file mode 100644 index 0000000..105e529 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/questlog.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/shop.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/shop.png new file mode 100644 index 0000000..21d5e95 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/shop.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/skills.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/skills.png new file mode 100644 index 0000000..9424cea Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/skills.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/spelllist.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/spelllist.png new file mode 100644 index 0000000..b21d17a Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/spelllist.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/terminal.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/terminal.png new file mode 100644 index 0000000..3d113ea Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/terminal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png new file mode 100644 index 0000000..81de760 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/unjustifiedpoints.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/viplist.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/viplist.png new file mode 100644 index 0000000..646091d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/topbuttons/viplist.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_horizontal.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_horizontal.png new file mode 100644 index 0000000..a0ec72c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_horizontal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_vertical.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_vertical.png new file mode 100644 index 0000000..d48aba3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/arrow_vertical.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/broder_panel.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/broder_panel.png new file mode 100644 index 0000000..da36bae Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/broder_panel.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/button.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/button.png new file mode 100644 index 0000000..3c8f17e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/button.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/checkbox.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/checkbox.png new file mode 100644 index 0000000..829cc59 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/checkbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/colorbox.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/colorbox.png new file mode 100644 index 0000000..9d2ef7f Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/colorbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox.png new file mode 100644 index 0000000..18dc0ac Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_rounded.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_rounded.png new file mode 100644 index 0000000..faf8607 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_rounded.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_square.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_square.png new file mode 100644 index 0000000..ec0ed70 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/combobox_square.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/icon_add.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/icon_add.png new file mode 100644 index 0000000..f820a0b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/icon_add.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/item.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/item.png new file mode 100644 index 0000000..6b605a3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/item.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubarleft.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubarleft.png new file mode 100644 index 0000000..4ca138b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubarleft.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubox.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubox.png new file mode 100644 index 0000000..169e36d Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/menubox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/minibroder.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/minibroder.png new file mode 100644 index 0000000..3dfe1b7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/minibroder.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow.png new file mode 100644 index 0000000..6886d44 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow_buttons.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow_buttons.png new file mode 100644 index 0000000..3fbb870 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/miniwindow_buttons.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/noimage.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/noimage.png new file mode 100644 index 0000000..efd98b7 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/noimage.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/option_button.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/option_button.png new file mode 100644 index 0000000..5e92446 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/option_button.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/options_broder.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/options_broder.png new file mode 100644 index 0000000..b7d7a36 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/options_broder.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_bottom.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_bottom.png new file mode 100644 index 0000000..c87e086 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_bottom.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_flat.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_flat.png new file mode 100644 index 0000000..2da41dd Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_flat.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_map.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_map.png new file mode 100644 index 0000000..ea2b90e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_map.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_side.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_side.png new file mode 100644 index 0000000..d7df3ba Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_side.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_top.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_top.png new file mode 100644 index 0000000..e403577 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/panel_top.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progress_icons.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progress_icons.png new file mode 100644 index 0000000..4f34ef3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progress_icons.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbar.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbar.png new file mode 100644 index 0000000..503d340 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbar.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarhpmana.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarhpmana.png new file mode 100644 index 0000000..3ce0657 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarhpmana.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarskills.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarskills.png new file mode 100644 index 0000000..d139932 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/progressbarskills.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/scrollbar.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/scrollbar.png new file mode 100644 index 0000000..4bbd1c0 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/scrollbar.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_horizontal.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_horizontal.png new file mode 100644 index 0000000..9eb47b3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_horizontal.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_vertical.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_vertical.png new file mode 100644 index 0000000..ca3b5d3 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/separator_vertical.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/special_miniwindow.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/special_miniwindow.png new file mode 100644 index 0000000..906d537 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/special_miniwindow.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox.png new file mode 100644 index 0000000..cef075c Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_down.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_down.png new file mode 100644 index 0000000..b124103 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_down.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_up.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_up.png new file mode 100644 index 0000000..bf1b6d6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/spinbox_up.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_rounded.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_rounded.png new file mode 100644 index 0000000..3c8f17e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_rounded.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_square.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_square.png new file mode 100644 index 0000000..f669be6 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/tabbutton_square.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/textedit.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/textedit.png new file mode 100644 index 0000000..9b35c60 Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/textedit.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/window.png b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/window.png new file mode 100644 index 0000000..167690e Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/images/ui/window.png differ diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-checkboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-checkboxes.otui new file mode 100644 index 0000000..5a51a05 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-checkboxes.otui @@ -0,0 +1,59 @@ +CheckBox < UICheckBox + size: 12 12 + text-align: left + text-offset: 18 -1 + color: #dfdfdf + image-color: #dfdfdfff + image-rect: 0 0 12 12 + image-source: /images/ui/checkbox + + $hover !disabled: + color: #ffffff + + $!checked: + image-clip: 0 0 12 12 + + $checked: + image-clip: 0 12 12 12 + + $disabled: + image-color: #dfdfdf88 + color: #dfdfdf88 + opacity: 0.8 + +ColorBox < UICheckBox + size: 16 16 + image-color: #dfdfdfff + image-source: /images/ui/colorbox + + $checked: + image-clip: 16 0 16 16 + + $!checked: + image-clip: 0 0 16 16 + +ButtonBox < UICheckBox + font: verdana-11px-antialised + color: white + size: 106 23 + text-offset: 0 0 + text-align: center + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + + $hover !disabled: + image-clip: 0 23 22 23 + + $checked: + image-clip: 0 46 22 23 + color: white + text-offset: 2 2 + + $disabled: + color: #dfdfdf88 + image-color: #dfdfdf88 + +ButtonBoxRounded < ButtonBox + image-source: /images/ui/button_rounded \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-comboboxes.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-comboboxes.otui new file mode 100644 index 0000000..a94cbce --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-comboboxes.otui @@ -0,0 +1,106 @@ +ComboBoxPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 4 0 + color: #BFBFBF + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #595959 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBoxPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + margin: 1 + + $hover !disabled: + color: #dfdfdf + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_square + image-clip: 0 69 91 23 + image-border: 1 + +ComboBox < UIComboBox + font: verdana-11px-antialised + color: #dfdfdf + size: 91 23 + text-offset: 5 2 + text-align: left + image-source: /images/ui/combobox_square + image-border: 3 + image-border-right: 19 + image-clip: 0 0 91 23 + + $hover !disabled: + image-clip: 0 23 91 23 + + $on: + image-clip: 0 46 91 23 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 + +ComboBoxRoundedPopupScrollMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupScrollMenu < UIPopupScrollMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRoundedPopupMenuButton < UIButton + height: 23 + font: verdana-11px-antialised + text-align: left + text-offset: 5 2 + color: #dfdfdf + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #355d89 + + $disabled: + color: #dfdfdf88 + +ComboBoxRoundedPopupMenu < UIPopupMenu + image-source: /images/ui/combobox_rounded + image-clip: 0 69 91 23 + image-border: 3 + +ComboBoxRounded < ComboBox + image-source: /images/ui/combobox_rounded + image-border: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-panels.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-panels.otui new file mode 100644 index 0000000..15fbbc3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-panels.otui @@ -0,0 +1,19 @@ +Panel < UIWidget + phantom: true + auto-focus: first + +ScrollablePanel < UIScrollArea + phantom: true + auto-focus: first + +FlatPanel < Panel + image-source: /images/ui/panel_flat + image-border: 1 + +ScrollableFlatPanel < ScrollablePanel + image-source: /images/ui/panel_flat + image-border: 1 + +LightFlatPanel < Panel + image-source: /images/ui/panel_lightflat + image-border: 1 diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-progressbars.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-progressbars.otui new file mode 100644 index 0000000..4a8af2f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-progressbars.otui @@ -0,0 +1,38 @@ +ProgressBar < UIProgressBar + height: 16 + background-color: red + image-source: /images/ui/progressbar + image-border: 2 + font: verdana-11px-rounded + text-offset: 0 2 + + $!on: + visible: false + margin-top: 0 + margin-bottom: 0 + height: 0 + +LifeProgressBar < UIProgressBar + height: 16 + background-color: green + border: 1 black + font: verdana-11px-rounded + text-offset: 0 2 + margin: 2 + +ProgressRect < UIProgressRect + anchors.fill: parent + phantom: true + color: white + background-color: #00000088 + font: verdana-11px-rounded + +HealthBar < ProgressBar + image-source: /images/ui/progressbarhpmana + image-border: 3 + background-color: #ff4444 + +ManaBar < ProgressBar + image-source: /images/ui/progressbarhpmana + image-border: 4 + background-color: #4444ff diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-textedits.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-textedits.otui new file mode 100644 index 0000000..fefa3bc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-textedits.otui @@ -0,0 +1,20 @@ +TextEdit < UITextEdit + font: verdana-11px-antialised + color: white + size: 86 22 + text-offset: 0 4 + opacity: 1 + padding: 4 + image-source: /images/ui/textedit + image-border: 1 + selection-color: #272727 + selection-background-color: #cccccc + $disabled: + color: #27272788 + opacity: 0.5 + +PasswordTextEdit < TextEdit + text-hidden: true + +MultilineTextEdit < TextEdit + multiline: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-windows.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-windows.otui new file mode 100644 index 0000000..9d05389 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/10-windows.otui @@ -0,0 +1,32 @@ +Window < UIWindow + font: verdana-11px-antialised + size: 236 207 + opacity: 1 + color: #AFAFAF + text-offset: 0 2 + text-align: top + image-source: /images/ui/window + image-border: 4 + image-border-top: 17 + padding-top: 25 + padding-left: 16 + padding-right: 16 + padding-bottom: 16 + + $disabled: + color: #dfdfdf + +HeadlessWindow < UIWindow + image-source: /images/ui/window_headless + image-border: 5 + padding: 5 + +MainWindow < Window + anchors.centerIn: parent + +StaticWindow < Window + &static: true + +StaticMainWindow < StaticWindow + anchors.centerIn: parent + \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-popupmenus.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-popupmenus.otui new file mode 100644 index 0000000..ceeb532 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-popupmenus.otui @@ -0,0 +1,83 @@ +PopupMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 2 + text-align: left + font: verdana-11px-antialised + + color: white + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/separator_horizontal + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupMenu < UIPopupMenu + width: 120 + image-source: /images/ui/menubox + image-border: 3 + padding: 5 + +PopupScrollMenuButton < UIButton + height: 18 + size: 0 21 + text-offset: 4 0 + text-align: left + font: verdana-11px-antialised + + color: #aaaaaa + background-color: alpha + + $hover !disabled: + color: #ffffff + background-color: #ffffff44 + image-clip: 0 40 20 20 + + $disabled: + color: #555555 + +PopupScrollMenuShortcutLabel < Label + font: verdana-11px-antialised + text-align: right + anchors.fill: parent + margin-right: 2 + margin-left: 5 + +PopupScrollMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + margin-bottom: 1 + image-source: /images/ui/menubox + image-border-left: 1 + image-border-right: 1 + image-clip: 0 0 32 2 + height: 2 + phantom: true + +PopupScrollMenu < UIPopupScrollMenu + width: 50 + image-source: /images/ui/menubox + image-border: 3 + padding: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-tabbars.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-tabbars.otui new file mode 100644 index 0000000..a819635 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-tabbars.otui @@ -0,0 +1,113 @@ +MoveableTabBar < UIMoveableTabBar + size: 80 21 +MoveableTabBarPanel < Panel +MoveableTabBarButton < UIButton + size: 96 18 + image-source: /images/ui/tabbutton_square + image-clip: 0 0 96 22 + image-border: 3 + image-border-bottom: 0 + color: #7F7F7F + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + padding: 3 + + $checked: + image-clip: 0 36 96 22 + color: #dfdfdf + + $on !checked: + color: #F75F5F + +TabBar < UITabBar + size: 80 21 + Panel + id: buttonsPanel + anchors.fill: parent +TabBarPanel < Panel +TabBarButton < UIButton + size: 17 18 + image-source: /images/ui/tabbutton_square + image-color: #dfdfdf + image-clip: 0 0 98 18 + image-border: 3 + icon-color: #dfdfdf + color: #dfdfdf + anchors.top: parent.top + padding: 5 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + margin-left: 5 + + $hover !checked: + image-clip: 0 18 98 18 + color: #dfdfdf + + $disabled: + image-color: #dfdfdf88 + icon-color: #dfdfdf + + $checked: + image-clip: 0 36 98 18 + color: #dfdfdf + + $on !checked: + color: #de6f6f + +TabBarRounded < TabBar +TabBarRoundedPanel < TabBarPanel +TabBarRoundedButton < TabBarButton + image-source: /images/ui/tabbutton_rounded + +TabBarVertical < UITabBar + width: 96 + ScrollableFlatPanel + id: buttonsPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: scrollBar.left + anchors.bottom: parent.bottom + vertical-scrollbar: scrollBar + margin-right: 1 + padding-top: 10 + + VerticalScrollBar + id: scrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 16 + pixels-scroll: true + $!on: + width: 0 + +TabBarVerticalPanel < Panel +TabBarVerticalButton < UIButton + size: 48 48 + color: #aaaaaa + anchors.left: parent.left + anchors.right: parent.right + text-align: bottom + icon-align: top + icon-offset-y: 2 + icon-color: #888888 + $first: + anchors.top: parent.top + $!first: + anchors.top: prev.bottom + margin-top: 10 + $hover !checked: + color: white + icon-color: #dfdfdf + $disabled: + icon-color: #333333 + $checked: + icon-color: #ffffff + color: #80c7f8 + $on !checked: + color: #F55E5E \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-topmenu.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-topmenu.otui new file mode 100644 index 0000000..0e6657a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/20-topmenu.otui @@ -0,0 +1,131 @@ +TopButton < UIButton + size: 26 26 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-border: 3 + image-color: #ffffffff + icon-color: #ffffffff + icon-clip: 0 0 20 20 + + $on: + image-source: /images/ui/button_top_blink + icon-clip: 0 20 20 20 + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + icon-clip: 0 20 20 20 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopToggleButton < UIButton + size: 20 20 + image-source: /images/ui/button_top + image-clip: 0 0 26 26 + image-color: #ffffffff + image-border: 3 + icon-clip: 0 0 20 20 + icon-color: #ffffffff + + $on: + icon-clip: 0 20 20 20 + + $hover !disabled: + image-color: #ffffff99 + image-clip: 26 0 26 26 + + $pressed: + image-clip: 52 0 26 26 + icon-clip: 0 20 20 20 + + $disabled: + image-color: #ffffff44 + icon-color: #ffffff44 + +TopMenuButtonsPanel < Panel + layout: + type: horizontalBox + spacing: 4 + fit-children: true + padding: 6 4 + +TopMenuPanel < Panel + height: 36 + image-source: /images/ui/panel_top + image-repeated: true + image-border: 3 + image-border-top: 0 + focusable: false + +TopMenuFrameCounterLabel < Label + font: verdana-11px-rounded + color: white + margin-top: 4 + margin-left: 5 + +TopMenuPingLabel < Label + font: verdana-11px-rounded + +TopMenu < TopMenuPanel + id: topMenu + width: 800 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + &hideIngame: true + &reverseButtons: true + + UIWidget + id: discord + anchors.top: parent.top + anchors.left: parent.left + margin-top: 3 + margin-left: 5 + image-source: /images/ui/discord + + Label + id: discordLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + text-align: center + margin-left: 2 + text-auto-resize: true + + TopMenuButtonsPanel + id: rightButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + + TopMenuButtonsPanel + id: rightGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + visible: false + + Label + id: onlineLabel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + text-align: center + text-auto-resize: true + + TopMenuButtonsPanel + id: leftButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + + TopMenuButtonsPanel + id: leftGameButtonsPanel + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: prev.left + visible: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/30-miniwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/30-miniwindow.otui new file mode 100644 index 0000000..3742701 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/30-miniwindow.otui @@ -0,0 +1,215 @@ +MiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 2 13 13 + icon-clip: 0 0 20 20 + color: #9F9F9F + width: 190 + height: 200 + text-offset: 24 2 + text-align: topLeft + image-source: /images/ui/miniwindow + image-border: 4 + image-border-top: 20 + image-border-bottom: 4 + focusable: false + &minimizedHeight: 20 + + $on: + image-border-bottom: 2 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 14 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + margin-right: 4 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 28 0 14 14 + + $hover: + image-clip: 28 14 14 14 + + $pressed: + image-clip: 28 28 14 14 + + UIButton + id: minimizeButton + anchors.top: closeButton.top + anchors.right: closeButton.left + margin-right: 3 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + UIButton + id: lockButton + anchors.top: minimizeButton.top + anchors.right: minimizeButton.left + margin-right: 2 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 98 0 14 14 + + $hover: + image-clip: 98 14 14 14 + + $pressed: + image-clip: 98 28 14 14 + + $on: + image-clip: 84 0 14 14 + + $on hover: + image-clip: 84 14 14 14 + + $on pressed: + image-clip: 84 28 14 14 + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 18 + margin-right: 4 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 + +MiniWindowContents < ScrollablePanel + id: contentsPanel + anchors.fill: parent + anchors.right: miniwindowScrollBar.left + margin-left: 3 + margin-bottom: 3 + margin-top: 18 + margin-right: 1 + vertical-scrollbar: miniwindowScrollBar + +HeadlessMiniWindow < UIMiniWindow + font: verdana-11px-antialised + icon-rect: 4 2 13 12 + icon-clip: 0 0 20 20 + color: #8F8F8F + width: 190 + height: 200 + focusable: false + &minimizedHeight: 20 + + $on: + image-border-bottom: 2 + + $!on: + text: + icon: + + UIButton + id: minimizeButton + anchors.top: parent.top + anchors.right: parent.right + margin-right: 10 + margin-top: 1 + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + $!on: + size: 0 0 + + UIWidget + id: miniwindowTopBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + margin-right: 3 + margin-left: 3 + margin-top: 3 + size: 258 14 + phantom: true + + UIButton + id: closeButton + anchors.top: parent.top + anchors.right: parent.right + hidden: true + + VerticalScrollBar + id: miniwindowScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + margin-top: 16 + margin-right: 4 + margin-bottom: 3 + pixels-scroll: true + + $!on: + width: 0 + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 3 + minimum: 48 + margin-left: 3 + margin-right: 3 + background: #ffffff88 diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-console.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-console.otui new file mode 100644 index 0000000..7943fe1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-console.otui @@ -0,0 +1,219 @@ +ConsoleLabel < UITextEdit + font: verdana-11px-antialised + height: 14 + color: yellow + margin-left: 1 + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #808080 + change-cursor-image: false + cursor-visible: false + editable: false + draggable: true + selectable: false + focusable: false + +ConsolePhantomLabel < UILabel + font: verdana-11px-antialised + height: 14 + color: yellow + text-wrap: true + text-auto-resize: true + selection-color: #111416 + selection-background-color: #999999 + +ConsoleTabBar < MoveableTabBar + height: 16 + +ConsoleTabBarPanel < MoveableTabBarPanel + id: consoleTab + + ScrollablePanel + id: consoleBuffer + anchors.fill: parent + margin-right: 12 + vertical-scrollbar: consoleScrollBar + layout: + type: verticalBox + align-bottom: true + border-width: 1 + border-color: #202327 + background: #00000066 + inverted-scroll: true + padding: 1 + + VerticalScrollBar + id: consoleScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + +ConsoleTabBarButton < MoveableTabBarButton + height: 16 + padding: 15 + +ConsolePanel < Panel + image-source: /images/ui/panel_bottom + image-border: 7 + image-border-top: 29 + + $first: + anchors.fill: parent + + $!first: + anchors.top: prev.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + CheckBox + id: toggleChat + !tooltip: tr('Disable chat mode, allow to walk using ASDW') + anchors.left: parent.left + anchors.top: parent.top + margin-left: 6 + margin-top: 3 + @onCheckChange: toggleChat() + + TabButton + id: prevChannelButton + icon: /images/game/console/leftarrow + anchors.left: toggleChat.right + anchors.top: parent.top + margin-top: 1 + size: 16 16 + + ConsoleTabBar + id: consoleTabBar + anchors.left: prev.right + anchors.top: parent.top + anchors.right: next.left + margin-top: 0 + tab-spacing: 2 + movable: true + + TabButton + id: nextChannelButton + icon: /images/game/console/rightarrow + anchors.right: next.left + anchors.top: parent.top + margin-top: 1 + size: 16 16 + margin-right: 5 + + TabButton + id: closeChannelButton + !tooltip: tr('Close this channel') .. ' (Ctrl+E)' + icon: /images/game/console/closechannel + anchors.right: next.left + anchors.top: parent.top + enabled: false + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: removeCurrentTab() + + TabButton + id: clearChannelButton + !tooltip: tr('Clear current message window') + icon: /images/game/console/clearchannel + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: | + local consoleTabBar = self:getParent():getChildById('consoleTabBar') + clearChannel(consoleTabBar) + + TabButton + id: channelsButton + !tooltip: tr('Open new channel') .. ' (Ctrl+O)' + icon: /images/game/console/channels + anchors.right: next.left + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: g_game.requestChannels() + + TabButton + id: ignoreButton + !tooltip: tr('Ignore players') + icon: /images/game/console/ignore + anchors.right: parent.right + anchors.top: parent.top + margin-right: 5 + margin-top: 1 + size: 16 16 + icon-clip: 0 0 16 16 + + $pressed: + icon-clip: 0 16 16 16 + + @onClick: onClickIgnoreButton() + + Panel + id: consoleContentPanel + anchors.top: consoleTabBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: consoleTextEdit.top + margin-left: 6 + margin-right: 6 + margin-bottom: 2 + margin-top: 6 + padding: 1 + focusable: false + phantom: true + + TabButton + id: sayModeButton + icon: /images/game/console/say + !tooltip: tr('Adjust volume') + &sayMode: 2 + size: 18 18 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 8 + margin-bottom: 4 + @onClick: sayModeChange() + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: prev.top + margin-bottom: 3 + margin-left: 7 + margin-right: 7 + + TextEdit + id: consoleTextEdit + anchors.left: sayModeButton.right + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-right: 7 + margin-left: 2 + margin-bottom: 2 + shift-navigation: true + max-length: 255 + text-auto-submit: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-entergame.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-entergame.otui new file mode 100644 index 0000000..9f82297 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-entergame.otui @@ -0,0 +1,3 @@ +EnterGameWindow < StaticMainWindow + !text: tr('Enter Game') + size: 260 340 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-gamebuttons.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-gamebuttons.otui new file mode 100644 index 0000000..9a0a06a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-gamebuttons.otui @@ -0,0 +1,16 @@ +GameButtonsWindow < HeadlessMiniWindow + height: 26 + &forceOpen: true + &autoOpen: 4 + + MiniWindowContents + margin-top: 2 + + Panel + id: buttons + anchors.fill: parent + layout: + type: grid + cell-spacing: 3 + cell-size: 20 20 + flow: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-healthinfo.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-healthinfo.otui new file mode 100644 index 0000000..b3b4c5f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-healthinfo.otui @@ -0,0 +1,154 @@ +ExperienceBar < ProgressBar + id: experienceBar + background-color: #B6E866 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 1 + margin-top: 3 + +SoulLabel < GameLabel + id: soulLabel + text-align: right + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-top: 5 + margin-right: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +CapLabel < GameLabel + id: capLabel + color: white + font: verdana-11px-rounded + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-top: 5 + margin-left: 3 + on: true + + $!on: + visible: false + margin-top: 0 + height: 0 + +ConditionWidget < UIWidget + size: 18 18 + + $!first: + margin-left: 2 + +HealthOverlay < UIWidget + id: healthOverlay + anchors.fill: parent + phantom: true + + HealthBar + id: topHealthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + phantom: true + + ManaBar + id: topManaBar + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.horizontalCenter + phantom: true + + UIProgressBar + id: healthCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_empty + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: healthCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/left_full + margin-right: 169 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircle + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_empty + margin-left: 130 + margin-bottom: 16 + opacity: 0.5 + phantom: true + + UIProgressBar + id: manaCircleFront + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + image-source: /images/game/circle/right_full + margin-left: 130 + margin-bottom: 16 + opacity: 0.4 + image-color: #0000FFFF + phantom: true + +HealthInfoWindow < HeadlessMiniWindow + icon: + text: + height: 100 + &forceOpen: true + icon: /images/topbuttons/healthinfo + !text: tr('Health Info') + + MiniWindowContents + margin-top: 2 + + HealthBar + id: healthBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-top: 0 + phantom: true + + ManaBar + id: manaBar + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin: 2 + margin-bottom: 0 + phantom: true + + ExperienceBar + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + margin-top: 4 + padding: 2 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + border-width: 1 + border-color: #00000077 + background-color: #ffffff11 + SoulLabel + CapLabel + diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-inventory.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-inventory.otui new file mode 100644 index 0000000..4e173a0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-inventory.otui @@ -0,0 +1,333 @@ +InventoryItem < Item + $on: + image-source: /images/ui/item-blessed + +HeadSlot < InventoryItem + id: slot1 + image-source: /images/game/slots/head + &position: {x=65535, y=1, z=0} + $on: + image-source: /images/game/slots/head-blessed + +BodySlot < InventoryItem + id: slot4 + image-source: /images/game/slots/body + &position: {x=65535, y=4, z=0} + $on: + image-source: /images/game/slots/body-blessed + +LegSlot < InventoryItem + id: slot7 + image-source: /images/game/slots/legs + &position: {x=65535, y=7, z=0} + $on: + image-source: /images/game/slots/legs-blessed + +FeetSlot < InventoryItem + id: slot8 + image-source: /images/game/slots/feet + &position: {x=65535, y=8, z=0} + $on: + image-source: /images/game/slots/feet-blessed + +NeckSlot < InventoryItem + id: slot2 + image-source: /images/game/slots/neck + &position: {x=65535, y=2, z=0} + $on: + image-source: /images/game/slots/neck-blessed + +LeftSlot < InventoryItem + id: slot6 + image-source: /images/game/slots/left-hand + &position: {x=65535, y=6, z=0} + $on: + image-source: /images/game/slots/left-hand-blessed + +FingerSlot < InventoryItem + id: slot9 + image-source: /images/game/slots/finger + &position: {x=65535, y=9, z=0} + $on: + image-source: /images/game/slots/finger-blessed + +BackSlot < InventoryItem + id: slot3 + image-source: /images/game/slots/back + &position: {x=65535, y=3, z=0} + $on: + image-source: /images/game/slots/back-blessed + +RightSlot < InventoryItem + id: slot5 + image-source: /images/game/slots/right-hand + &position: {x=65535, y=5, z=0} + $on: + image-source: /images/game/slots/right-hand-blessed + +AmmoSlot < InventoryItem + id: slot10 + image-source: /images/game/slots/ammo + &position: {x=65535, y=10, z=0} + $on: + image-source: /images/game/slots/ammo-blessed + +PurseButton < UIButton + id: purseButton + size: 34 12 + !tooltip: tr('Open purse') + icon-source: /images/game/slots/purse + icon-clip: 0 0 34 12 + + $on: + icon-clip: 0 12 34 12 + + $pressed: + icon-clip: 0 12 34 12 + +CombatBox < UICheckBox + size: 20 20 + image-clip: 0 0 20 20 + margin-left: 4 + + $checked: + image-clip: 0 20 20 20 + + +InventoryButton < Button + height: 18 + margin-top: 2 + text-align: center + font: cipsoftFont + color: white + size: 45 20 + text-offset: 2 2 + +SoulCapLabel < GameLabel + text-align: center + color: #FFFFFF + font: cipsoftFont + margin-top: 4 + text-offset: 0 3 + width: 36 + height: 20 + icon-source: /images/game/slots/soulcap + +FightOffensiveBox < CombatBox + image-source: /images/game/combatmodes/fightoffensive +FightBalancedBox < CombatBox + image-source: /images/game/combatmodes/fightbalanced +FightDefensiveBox < CombatBox + image-source: /images/game/combatmodes/fightdefensive +ChaseModeBox < CombatBox + image-source: /images/game/combatmodes/chasemode +SafeFightBox < CombatBox + image-source: /images/game/combatmodes/safefight + +MountButton < CombatBox + image-source: /images/game/combatmodes/mount + +InventoryWindow < HeadlessMiniWindow + icon: /images/topbuttons/inventory + height: 178 + id: inventoryWindow + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 + &forceOpen: true + + MiniWindowContents + anchors.left: parent.left + margin-top: 0 + + UIButton + id: minimizeButton + anchors.top: parent.top + anchors.left: parent.left + size: 14 14 + image-source: /images/ui/miniwindow_buttons + image-clip: 0 0 14 14 + margin-top: 3 + margin-left: 4 + + $hover: + image-clip: 0 14 14 14 + + $pressed: + image-clip: 0 28 14 14 + + $on: + image-clip: 14 0 14 14 + + $on hover: + image-clip: 14 14 14 14 + + $on pressed: + image-clip: 14 28 14 14 + + @onClick: | + self:getParent():getParent().minimizeButton:onClick() + + Panel + id: inventoryPanel + margin-right: 63 + margin-top: 2 + anchors.fill: parent + + HeadSlot + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 3 + + BodySlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + LegSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FeetSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + NeckSlot + anchors.top: slot1.top + anchors.right: slot1.left + margin-top: 13 + margin-right: 5 + + LeftSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + FingerSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + BackSlot + anchors.top: slot1.top + anchors.left: slot1.right + margin-top: 13 + margin-left: 5 + + RightSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + AmmoSlot + anchors.top: prev.bottom + anchors.horizontalCenter: prev.horizontalCenter + margin-top: 3 + + SoulCapLabel + id: soulLabel + anchors.top: slot10.bottom + anchors.horizontalCenter: slot10.horizontalCenter + + SoulCapLabel + id: capLabel + anchors.top: slot9.bottom + anchors.horizontalCenter: slot9.horizontalCenter + + PurseButton + anchors.left: slot3.left + anchors.bottom: slot3.top + margin-bottom: 3 + + Panel + id: conditionPanel + layout: + type: horizontalBox + height: 22 + padding: 2 + anchors.top: slot8.bottom + anchors.left: slot6.left + anchors.right: slot5.right + margin-top: 4 + border-width: 1 + border-color: #00000077 + background-color: #ffffff22 + + Panel + margin-top: 5 + anchors.fill: parent + anchors.left: prev.right + + FightOffensiveBox + id: fightOffensiveBox + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + ChaseModeBox + id: chaseModeBox + anchors.left: prev.right + anchors.top: parent.top + + FightBalancedBox + id: fightBalancedBox + margin-top: 22 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + SafeFightBox + id: safeFightBox + margin-top: 22 + anchors.left: prev.right + anchors.top: parent.top + + FightDefensiveBox + id: fightDefensiveBox + margin-top: 44 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 8 + + MountButton + id: mountButton + margin-top: 44 + anchors.left: prev.right + anchors.top: parent.top + + Panel + id: buttonsPanel + margin-top: 4 + margin-right: 5 + anchors.fill: parent + anchors.top: prev.bottom + layout: + type: verticalBox + + UIButton + id: buttonPvp + height: 20 + icon: /images/game/combatmodes/pvp + icon-clip: 0 0 42 20 + + $on: + icon-clip: 0 20 42 20 + + InventoryButton + !text: tr('Stop') + @onClick: g_game.stop(); g_game.cancelAttackAndFollow() + + InventoryButton + !text: tr('Options') + @onClick: modules.client_options.toggle() + + InventoryButton + !text: tr('Hotkeys') + @onClick: modules.game_hotkeys.toggle() + + InventoryButton + !text: tr('Logout') + @onClick: modules.game_interface.tryLogout() + diff --git a/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-minimap.otui b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-minimap.otui new file mode 100644 index 0000000..cc10a8d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/layouts/retro/styles/40-minimap.otui @@ -0,0 +1,261 @@ +MinimapFlag < UIWidget + size: 11 11 + focusable: false + +MinimapCross < UIWidget + focusable: false + phantom: true + image: /images/game/minimap/cross + size: 16 16 + +MinimapFloorUpButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_up + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapFloorDownButton < Button + size: 20 20 + margin-right: 28 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + icon-source: /images/game/minimap/floor_down + icon-clip: 0 32 16 16 + $pressed: + icon-clip: 0 0 16 16 + $hover !pressed: + icon-clip: 0 16 16 16 + +MinimapZoomInButton < Button + text: + + size: 20 20 + margin-right: 4 + margin-bottom: 28 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_in + +MinimapZoomOutButton < Button + text: - + size: 20 20 + margin-right: 4 + margin-bottom: 4 + anchors.right: parent.right + anchors.bottom: parent.bottom + //icon-source: /images/game/minimap/zoom_out + +MinimapResetButton < Button + !text: tr('Center') + size: 44 20 + anchors.left: parent.left + anchors.top: parent.top + margin: 4 + +Minimap < UIMinimap + draggable: true + focusable: false + cross: true + color: black + + MinimapFloorUpButton + id: floorUpWidget + @onClick: self:getParent():floorUp(1) + + MinimapFloorDownButton + id: floorDownWidget + @onClick: self:getParent():floorDown(1) + + MinimapZoomInButton + id: zoomInWidget + @onClick: self:getParent():zoomIn() + + MinimapZoomOutButton + id: zoomOutWidget + @onClick: self:getParent():zoomOut() + + MinimapResetButton + id: resetWidget + @onClick: self:getParent():reset() + + +// Minimap Flag Create Window + + +MinimapFlagCheckBox < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-offset: 2 4 + anchors.left: prev.right + anchors.top: prev.top + $!checked: + image-clip: 26 0 26 26 + $hover !checked: + image-clip: 78 0 26 26 + $checked: + image-clip: 0 0 26 26 + $hover checked: + image-clip: 52 0 26 26 + +MinimapFlagWindow < MainWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + id: position + text-auto-resize: true + anchors.top: parent.top + anchors.right: parent.right + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + + MinimapFlagCheckBox + id: flag0 + icon-source: /images/game/minimap/flag0 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag1 + icon-source: /images/game/minimap/flag1 + + MinimapFlagCheckBox + id: flag2 + icon-source: /images/game/minimap/flag2 + + MinimapFlagCheckBox + id: flag3 + icon-source: /images/game/minimap/flag3 + + MinimapFlagCheckBox + id: flag4 + icon-source: /images/game/minimap/flag4 + + MinimapFlagCheckBox + id: flag5 + icon-source: /images/game/minimap/flag5 + + MinimapFlagCheckBox + id: flag6 + icon-source: /images/game/minimap/flag6 + + MinimapFlagCheckBox + id: flag7 + icon-source: /images/game/minimap/flag7 + + MinimapFlagCheckBox + id: flag8 + icon-source: /images/game/minimap/flag8 + + MinimapFlagCheckBox + id: flag9 + icon-source: /images/game/minimap/flag9 + + MinimapFlagCheckBox + id: flag10 + icon-source: /images/game/minimap/flag10 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + MinimapFlagCheckBox + id: flag11 + icon-source: /images/game/minimap/flag11 + + MinimapFlagCheckBox + id: flag12 + icon-source: /images/game/minimap/flag12 + + MinimapFlagCheckBox + id: flag13 + icon-source: /images/game/minimap/flag13 + + MinimapFlagCheckBox + id: flag14 + icon-source: /images/game/minimap/flag14 + + MinimapFlagCheckBox + id: flag15 + icon-source: /images/game/minimap/flag15 + + MinimapFlagCheckBox + id: flag16 + icon-source: /images/game/minimap/flag16 + + MinimapFlagCheckBox + id: flag17 + icon-source: /images/game/minimap/flag17 + + MinimapFlagCheckBox + id: flag18 + icon-source: /images/game/minimap/flag18 + + MinimapFlagCheckBox + id: flag19 + icon-source: /images/game/minimap/flag19 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + +MinimapWindow < HeadlessMiniWindow + height: 150 + &forceOpen: true + + MiniWindowContents + margin: 5 + + Minimap + id: minimap + anchors.fill: parent + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.lua b/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.lua new file mode 100644 index 0000000..017a860 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.lua @@ -0,0 +1,7 @@ +function init() + g_healthBars.addHealthBackground("/images/bars/health1", -2, -2, 0, 2, 4) + g_healthBars.addManaBackground("/images/bars/mana1", -2, -2, 0, 2, 4) +end + +function terminate() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.otmod b/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.otmod new file mode 100644 index 0000000..3ecb49d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/mods/game_healthbars/healthbars.otmod @@ -0,0 +1,10 @@ +Module + name: game_healthbars + description: Load health and mana bars + author: Oen44 + website: http://otclient.ovh + scripts: [ healthbars ] + autoload: false + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.lua new file mode 100644 index 0000000..7dfafb2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.lua @@ -0,0 +1,125 @@ +local musicFilename = "/sounds/startup" +local musicChannel = nil + +function setMusic(filename) + musicFilename = filename + + if not g_game.isOnline() and musicChannel ~= nil then + musicChannel:stop() + musicChannel:enqueue(musicFilename, 3) + end +end + +function reloadScripts() + if g_game.getFeature(GameNoDebug) then + return + end + + g_textures.clearCache() + g_modules.reloadModules() + + local script = '/' .. g_app.getCompactName() .. 'rc.lua' + if g_resources.fileExists(script) then + dofile(script) + end + + local message = tr('All modules and scripts were reloaded.') + + modules.game_textmessage.displayGameMessage(message) + print(message) +end + +function startup() + if g_sounds ~= nil then + musicChannel = g_sounds.getChannel(1) + end + + G.UUID = g_settings.getString('report-uuid') + if not G.UUID or #G.UUID ~= 36 then + G.UUID = g_crypt.genUUID() + g_settings.set('report-uuid', G.UUID) + end + + -- Play startup music (The Silver Tree, by Mattias Westlund) + --musicChannel:enqueue(musicFilename, 3) + connect(g_game, { onGameStart = function() if musicChannel ~= nil then musicChannel:stop(3) end end }) + connect(g_game, { onGameEnd = function() + if g_sounds ~= nil then + g_sounds.stopAll() + --musicChannel:enqueue(musicFilename, 3) + end + end }) +end + +function init() + connect(g_app, { onRun = startup, + onExit = exit }) + connect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) + + if g_sounds ~= nil then + --g_sounds.preload(musicFilename) + end + + if not Updater then + if g_resources.getLayout() == "mobile" then + g_window.setMinimumSize({ width = 640, height = 360 }) + else + g_window.setMinimumSize({ width = 800, height = 640 }) + end + + -- window size + local size = { width = 1024, height = 600 } + size = g_settings.getSize('window-size', size) + g_window.resize(size) + + -- window position, default is the screen center + local displaySize = g_window.getDisplaySize() + local defaultPos = { x = (displaySize.width - size.width)/2, + y = (displaySize.height - size.height)/2 } + local pos = g_settings.getPoint('window-pos', defaultPos) + pos.x = math.max(pos.x, 0) + pos.y = math.max(pos.y, 0) + g_window.move(pos) + + -- window maximized? + local maximized = g_settings.getBoolean('window-maximized', false) + if maximized then g_window.maximize() end + end + + g_window.setTitle(g_app.getName()) + g_window.setIcon('/images/clienticon') + + g_keyboard.bindKeyDown('Ctrl+Shift+R', reloadScripts) + + -- generate machine uuid, this is a security measure for storing passwords + if not g_crypt.setMachineUUID(g_settings.get('uuid')) then + g_settings.set('uuid', g_crypt.getMachineUUID()) + g_settings.save() + end +end + +function terminate() + disconnect(g_app, { onRun = startup, + onExit = exit }) + disconnect(g_game, { onGameStart = onGameStart, + onGameEnd = onGameEnd }) + -- save window configs + g_settings.set('window-size', g_window.getUnmaximizedSize()) + g_settings.set('window-pos', g_window.getUnmaximizedPos()) + g_settings.set('window-maximized', g_window.isMaximized()) +end + +function exit() + g_logger.info("Exiting application..") +end + +function onGameStart() + local player = g_game.getLocalPlayer() + if not player then return end + g_window.setTitle(g_app.getName() .. " - " .. player:getName()) +end + +function onGameEnd() + g_window.setTitle(g_app.getName()) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.otmod new file mode 100644 index 0000000..2824a66 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client/client.otmod @@ -0,0 +1,23 @@ +Module + name: client + description: Initialize the client and setups its main window + author: edubart + website: https://github.com/edubart/otclient + reloadable: false + sandboxed: true + scripts: [ client ] + @onLoad: init() + @onUnload: terminate() + + load-later: + - client_styles + - client_locales + - client_topmenu + - client_background + - client_textedit + - client_options + - client_entergame + - client_terminal + - client_stats + - client_feedback + - client_mobile diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.lua new file mode 100644 index 0000000..657424f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.lua @@ -0,0 +1,49 @@ +-- private variables +local background +local clientVersionLabel + +-- public functions +function init() + background = g_ui.displayUI('background') + background:lower() + + clientVersionLabel = background:getChildById('clientVersionLabel') + clientVersionLabel:setText('OTClientV8 ' .. g_app.getVersion() .. '\nrev ' .. g_app.getBuildRevision() .. '\nMade by:\n' .. g_app.getAuthor() .. "") + + if not g_game.isOnline() then + addEvent(function() g_effects.fadeIn(clientVersionLabel, 1500) end) + end + + connect(g_game, { onGameStart = hide }) + connect(g_game, { onGameEnd = show }) +end + +function terminate() + disconnect(g_game, { onGameStart = hide }) + disconnect(g_game, { onGameEnd = show }) + + g_effects.cancelFade(background:getChildById('clientVersionLabel')) + background:destroy() + + Background = nil +end + +function hide() + background:hide() +end + +function show() + background:show() +end + +function hideVersionLabel() + background:getChildById('clientVersionLabel'):hide() +end + +function setVersionText(text) + clientVersionLabel:setText(text) +end + +function getBackground() + return background +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otmod new file mode 100644 index 0000000..8fb34cb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otmod @@ -0,0 +1,9 @@ +Module + name: client_background + description: Handles the background of the login screen + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ background ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otui new file mode 100644 index 0000000..c90fa1c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_background/background.otui @@ -0,0 +1,21 @@ +UIWidget + id: background + anchors.fill: parent + focusable: false + image-source: /images/background + image-smooth: true + image-fixed-ratio: true + margin-top: 1 + + UILabel + id: clientVersionLabel + background-color: #00000099 + anchors.right: parent.right + anchors.bottom: parent.bottom + text-align: center + text-auto-resize: false + width: 220 + height: 90 + padding: 2 + color: #ffffff + font: terminus-14px-bold \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.lua new file mode 100644 index 0000000..ef5ec74 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.lua @@ -0,0 +1,422 @@ +CharacterList = { } + +-- private variables +local charactersWindow +local loadBox +local characterList +local errorBox +local waitingWindow +local autoReconnectButton +local updateWaitEvent +local resendWaitEvent +local loginEvent +local autoReconnectEvent +local lastLogout = 0 + +-- private functions +local function tryLogin(charInfo, tries) + tries = tries or 1 + + if tries > 50 then + return + end + + if g_game.isOnline() then + if tries == 1 then + g_game.safeLogout() + end + loginEvent = scheduleEvent(function() tryLogin(charInfo, tries+1) end, 100) + return + end + + CharacterList.hide() + g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey) + g_logger.info("Login to " .. charInfo.worldHost .. ":" .. charInfo.worldPort) + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) + connect(loadBox, { onCancel = function() + loadBox = nil + g_game.cancelLogin() + CharacterList.show() + end }) + + -- save last used character + g_settings.set('last-used-character', charInfo.characterName) + g_settings.set('last-used-world', charInfo.worldName) +end + +local function updateWait(timeStart, timeEnd) + if waitingWindow then + local time = g_clock.seconds() + if time <= timeEnd then + local percent = ((time - timeStart) / (timeEnd - timeStart)) * 100 + local timeStr = string.format("%.0f", timeEnd - time) + + local progressBar = waitingWindow:getChildById('progressBar') + progressBar:setPercent(percent) + + local label = waitingWindow:getChildById('timeLabel') + label:setText(tr('Trying to reconnect in %s seconds.', timeStr)) + + updateWaitEvent = scheduleEvent(function() updateWait(timeStart, timeEnd) end, 1000 * progressBar:getPercentPixels() / 100 * (timeEnd - timeStart)) + return true + end + end + + if updateWaitEvent then + updateWaitEvent:cancel() + updateWaitEvent = nil + end +end + +local function resendWait() + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + + if updateWaitEvent then + updateWaitEvent:cancel() + updateWaitEvent = nil + end + + if charactersWindow then + local selected = characterList:getFocusedChild() + if selected then + local charInfo = { worldHost = selected.worldHost, + worldPort = selected.worldPort, + worldName = selected.worldName, + characterName = selected.characterName } + tryLogin(charInfo) + end + end + end +end + +local function onLoginWait(message, time) + CharacterList.destroyLoadBox() + + waitingWindow = g_ui.displayUI('waitinglist') + + local label = waitingWindow:getChildById('infoLabel') + label:setText(message) + + updateWaitEvent = scheduleEvent(function() updateWait(g_clock.seconds(), g_clock.seconds() + time) end, 0) + resendWaitEvent = scheduleEvent(resendWait, time * 1000) +end + +function onGameLoginError(message) + CharacterList.destroyLoadBox() + errorBox = displayErrorBox(tr("Login Error"), message) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end + scheduleAutoReconnect() +end + +function onGameLoginToken(unknown) + CharacterList.destroyLoadBox() + -- TODO: make it possible to enter a new token here / prompt token + errorBox = displayErrorBox(tr("Two-Factor Authentification"), 'A new authentification token is required.\nPlease login again.') + errorBox.onOk = function() + errorBox = nil + EnterGame.show() + end +end + +function onGameConnectionError(message, code) + CharacterList.destroyLoadBox() + if (not g_game.isOnline() or code ~= 2) and not errorBox then -- code 2 is normal disconnect, end of file + local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) + errorBox = displayErrorBox(tr("Connection Error"), text) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end + end + scheduleAutoReconnect() +end + +function onGameUpdateNeeded(signature) + CharacterList.destroyLoadBox() + errorBox = displayErrorBox(tr("Update needed"), tr('Enter with your account again to update your client.')) + errorBox.onOk = function() + errorBox = nil + CharacterList.showAgain() + end +end + +function onGameEnd() + scheduleAutoReconnect() + CharacterList.showAgain() +end + +function onLogout() + lastLogout = g_clock.millis() +end + +function scheduleAutoReconnect() + if lastLogout + 2000 > g_clock.millis() then + return + end + if autoReconnectEvent then + removeEvent(autoReconnectEvent) + end + autoReconnectEvent = scheduleEvent(executeAutoReconnect, 2500) +end + +function executeAutoReconnect() + if not autoReconnectButton or not autoReconnectButton:isOn() or g_game.isOnline() then + return + end + if errorBox then + errorBox:destroy() + errorBox = nil + end + CharacterList.doLogin() +end + +-- public functions +function CharacterList.init() + if USE_NEW_ENERGAME then return end + connect(g_game, { onLoginError = onGameLoginError }) + connect(g_game, { onLoginToken = onGameLoginToken }) + connect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) + connect(g_game, { onConnectionError = onGameConnectionError }) + connect(g_game, { onGameStart = CharacterList.destroyLoadBox }) + connect(g_game, { onLoginWait = onLoginWait }) + connect(g_game, { onGameEnd = onGameEnd }) + connect(g_game, { onLogout = onLogout }) + + if G.characters then + CharacterList.create(G.characters, G.characterAccount) + end +end + +function CharacterList.terminate() + if USE_NEW_ENERGAME then return end + disconnect(g_game, { onLoginError = onGameLoginError }) + disconnect(g_game, { onLoginToken = onGameLoginToken }) + disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) + disconnect(g_game, { onConnectionError = onGameConnectionError }) + disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox }) + disconnect(g_game, { onLoginWait = onLoginWait }) + disconnect(g_game, { onGameEnd = onGameEnd }) + disconnect(g_game, { onLogout = onLogout }) + + if charactersWindow then + characterList = nil + charactersWindow:destroy() + charactersWindow = nil + end + + if loadBox then + g_game.cancelLogin() + loadBox:destroy() + loadBox = nil + end + + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + end + + if updateWaitEvent then + removeEvent(updateWaitEvent) + updateWaitEvent = nil + end + + if resendWaitEvent then + removeEvent(resendWaitEvent) + resendWaitEvent = nil + end + + if loginEvent then + removeEvent(loginEvent) + loginEvent = nil + end + + CharacterList = nil +end + +function CharacterList.create(characters, account, otui) + if not otui then otui = 'characterlist' end + if charactersWindow then + charactersWindow:destroy() + end + + charactersWindow = g_ui.displayUI(otui) + characterList = charactersWindow:getChildById('characters') + autoReconnectButton = charactersWindow:getChildById('autoReconnect') + + -- characters + G.characters = characters + G.characterAccount = account + + characterList:destroyChildren() + local accountStatusLabel = charactersWindow:getChildById('accountStatusLabel') + local focusLabel + for i,characterInfo in ipairs(characters) do + local widget = g_ui.createWidget('CharacterWidget', characterList) + for key,value in pairs(characterInfo) do + local subWidget = widget:getChildById(key) + if subWidget then + if key == 'outfit' then -- it's an exception + subWidget:setOutfit(value) + else + local text = value + if subWidget.baseText and subWidget.baseTranslate then + text = tr(subWidget.baseText, text) + elseif subWidget.baseText then + text = string.format(subWidget.baseText, text) + end + subWidget:setText(text) + end + end + end + + -- these are used by login + widget.characterName = characterInfo.name + widget.worldName = characterInfo.worldName + widget.worldHost = characterInfo.worldIp + widget.worldPort = characterInfo.worldPort + + connect(widget, { onDoubleClick = function () CharacterList.doLogin() return true end } ) + + if i == 1 or (g_settings.get('last-used-character') == widget.characterName and g_settings.get('last-used-world') == widget.worldName) then + focusLabel = widget + end + end + + if focusLabel then + characterList:focusChild(focusLabel, KeyboardFocusReason) + addEvent(function() characterList:ensureChildVisible(focusLabel) end) + end + + characterList.onChildFocusChange = function() + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + end + + -- account + local status = '' + if account.status == AccountStatus.Frozen then + status = tr(' (Frozen)') + elseif account.status == AccountStatus.Suspended then + status = tr(' (Suspended)') + end + + if account.subStatus == SubscriptionStatus.Free and account.premDays < 1 then + accountStatusLabel:setText(('%s%s'):format(tr('Free Account'), status)) + else + if account.premDays == 0 or account.premDays == 65535 then + accountStatusLabel:setText(('%s%s'):format(tr('Gratis Premium Account'), status)) + else + accountStatusLabel:setText(('%s%s'):format(tr('Premium Account (%s) days left', account.premDays), status)) + end + end + + if account.premDays > 0 and account.premDays <= 7 then + accountStatusLabel:setOn(true) + else + accountStatusLabel:setOn(false) + end + + autoReconnectButton.onClick = function(widget) + local autoReconnect = not g_settings.getBoolean('autoReconnect', true) + autoReconnectButton:setOn(autoReconnect) + g_settings.set('autoReconnect', autoReconnect) + end +end + +function CharacterList.destroy() + CharacterList.hide(true) + + if charactersWindow then + characterList = nil + charactersWindow:destroy() + charactersWindow = nil + end +end + +function CharacterList.show() + if loadBox or errorBox or not charactersWindow then return end + charactersWindow:show() + charactersWindow:raise() + charactersWindow:focus() + + local autoReconnect = g_settings.getBoolean('autoReconnect', true) + autoReconnectButton:setOn(autoReconnect) +end + +function CharacterList.hide(showLogin) + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + + showLogin = showLogin or false + charactersWindow:hide() + + if showLogin and EnterGame and not g_game.isOnline() then + EnterGame.show() + end +end + +function CharacterList.showAgain() + if characterList and characterList:hasChildren() then + CharacterList.show() + end +end + +function CharacterList.isVisible() + if charactersWindow and charactersWindow:isVisible() then + return true + end + return false +end + +function CharacterList.doLogin() + removeEvent(autoReconnectEvent) + autoReconnectEvent = nil + + local selected = characterList:getFocusedChild() + if selected then + local charInfo = { worldHost = selected.worldHost, + worldPort = selected.worldPort, + worldName = selected.worldName, + characterName = selected.characterName } + charactersWindow:hide() + if loginEvent then + removeEvent(loginEvent) + loginEvent = nil + end + tryLogin(charInfo) + else + displayErrorBox(tr('Error'), tr('You must select a character to login!')) + end +end + +function CharacterList.destroyLoadBox() + if loadBox then + loadBox:destroy() + loadBox = nil + end +end + +function CharacterList.cancelWait() + if waitingWindow then + waitingWindow:destroy() + waitingWindow = nil + end + + if updateWaitEvent then + removeEvent(updateWaitEvent) + updateWaitEvent = nil + end + + if resendWaitEvent then + removeEvent(resendWaitEvent) + resendWaitEvent = nil + end + + CharacterList.destroyLoadBox() + CharacterList.showAgain() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.otui new file mode 100644 index 0000000..36911e4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/characterlist.otui @@ -0,0 +1,130 @@ +CharacterWidget < UIWidget + height: 14 + background-color: alpha + &updateOnStates: | + function(self) + local children = self:getChildren() + for i=1,#children do + children[i]:setOn(self:isFocused()) + end + end + @onFocusChange: self:updateOnStates() + @onSetup: self:updateOnStates() + + $focus: + background-color: #ffffff22 + + Label + id: name + color: #bbbbbb + anchors.top: parent.top + anchors.left: parent.left + font: verdana-11px-monochrome + text-auto-resize: true + background-color: alpha + text-offset: 2 0 + + $on: + color: #ffffff + + Label + id: worldName + color: #bbbbbb + anchors.top: parent.top + anchors.right: parent.right + margin-right: 5 + font: verdana-11px-monochrome + text-auto-resize: true + background-color: alpha + &baseText: '(%s)' + + $on: + color: #ffffff + +StaticMainWindow + id: charactersWindow + !text: tr('Character List') + visible: false + size: 350 400 + $mobile: + size: 350 280 + @onEnter: CharacterList.doLogin() + @onEscape: CharacterList.hide(true) + @onSetup: | + g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(KeyboardFocusReason) end, self) + g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self) + + TextList + id: characters + background-color: #565656 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: characterListScrollBar.left + anchors.bottom: accountStatusCaption.top + margin-bottom: 5 + padding: 1 + focusable: false + vertical-scrollbar: characterListScrollBar + auto-focus: first + + VerticalScrollBar + id: characterListScrollBar + anchors.top: parent.top + anchors.bottom: accountStatusCaption.top + anchors.right: parent.right + margin-bottom: 5 + step: 14 + pixels-scroll: true + + Label + id: accountStatusCaption + !text: tr('Account Status') .. ':' + anchors.left: parent.left + anchors.bottom: separator.top + margin-bottom: 5 + + Label + id: accountStatusLabel + !text: tr('Free Account') + anchors.right: parent.right + anchors.bottom: separator.top + margin-bottom: 5 + text-auto-resize: true + + $on: + color: #FF0000 + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: autoReconnect + !text: tr('Auto reconnect: On') + width: 140 + anchors.left: parent.left + anchors.bottom: parent.bottom + image-color: green + $!on: + image-color: red + !text: tr('Auto reconnect: Off') + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: CharacterList.doLogin() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: CharacterList.hide(true) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.lua new file mode 100644 index 0000000..b2b71b1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.lua @@ -0,0 +1,594 @@ +EnterGame = { } + +-- private variables +local loadBox +local enterGame +local enterGameButton +local clientBox +local protocolLogin +local server = nil +local versionsFound = false + +local customServerSelectorPanel +local serverSelectorPanel +local serverSelector +local clientVersionSelector +local serverHostTextEdit +local rememberPasswordBox +local protos = {"740", "760", "772", "792", "800", "810", "854", "860", "870", "910", "961", "1000", "1077", "1090", "1096", "1098", "1099", "1100", "1200", "1220"} + +local checkedByUpdater = {} +local waitingForHttpResults = 0 + +-- private functions +local function onProtocolError(protocol, message, errorCode) + if errorCode then + return EnterGame.onError(message) + end + return EnterGame.onLoginError(message) +end + +local function onSessionKey(protocol, sessionKey) + G.sessionKey = sessionKey +end + +local function onCharacterList(protocol, characters, account, otui) + if rememberPasswordBox:isChecked() then + local account = g_crypt.encrypt(G.account) + local password = g_crypt.encrypt(G.password) + + g_settings.set('account', account) + g_settings.set('password', password) + else + EnterGame.clearAccountFields() + end + + for _, characterInfo in pairs(characters) do + if characterInfo.previewState and characterInfo.previewState ~= PreviewState.Default then + characterInfo.worldName = characterInfo.worldName .. ', Preview' + end + end + + if loadBox then + loadBox:destroy() + loadBox = nil + end + + CharacterList.create(characters, account, otui) + CharacterList.show() + + g_settings.save() +end + +local function onUpdateNeeded(protocol, signature) + return EnterGame.onError(tr('Your client needs updating, try redownloading it.')) +end + +local function onProxyList(protocol, proxies) + for _, proxy in ipairs(proxies) do + g_proxy.addProxy(proxy["host"], proxy["port"], proxy["priority"]) + end +end + +local function parseFeatures(features) + for feature_id, value in pairs(features) do + if value == "1" or value == "true" or value == true then + g_game.enableFeature(feature_id) + else + g_game.disableFeature(feature_id) + end + end +end + +local function validateThings(things) + local incorrectThings = "" + local missingFiles = false + local versionForMissingFiles = 0 + if things ~= nil then + local thingsNode = {} + for thingtype, thingdata in pairs(things) do + thingsNode[thingtype] = thingdata[1] + if not g_resources.fileExists("/things/" .. thingdata[1]) then + incorrectThings = incorrectThings .. "Missing file: " .. thingdata[1] .. "\n" + missingFiles = true + versionForMissingFiles = thingdata[1]:split("/")[1] + else + local localChecksum = g_resources.fileChecksum("/things/" .. thingdata[1]):lower() + if localChecksum ~= thingdata[2]:lower() and #thingdata[2] > 1 then + if g_resources.isLoadedFromArchive() then -- ignore checksum if it's test/debug version + incorrectThings = incorrectThings .. "Invalid checksum of file: " .. thingdata[1] .. " (is " .. localChecksum .. ", should be " .. thingdata[2]:lower() .. ")\n" + end + end + end + end + g_settings.setNode("things", thingsNode) + else + g_settings.setNode("things", {}) + end + if missingFiles then + + incorrectThings = incorrectThings .. "\nYou should open data/things and create directory " .. versionForMissingFiles .. + ".\nIn this directory (data/things/" .. versionForMissingFiles .. ") you should put missing\nfiles (Tibia.dat and Tibia.spr) " .. + "from correct Tibia version." + end + return incorrectThings +end + +local function onTibia12HTTPResult(session, playdata) + local characters = {} + local worlds = {} + local account = { + status = 0, + subStatus = 0, + premDays = 0 + } + if session["status"] ~= "active" then + account.status = 1 + end + if session["ispremium"] then + account.subStatus = 1 -- premium + end + if session["premiumuntil"] > g_clock.seconds() then + account.subStatus = math.floor((session["premiumuntil"] - g_clock.seconds()) / 86400) + end + + local things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.spr", ""}, + } + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + g_logger.error(incorrectThings) + if Updater and not checkedByUpdater[G.clientVersion] then + checkedByUpdater[G.clientVersion] = true + return Updater.check({ + version = G.clientVersion, + host = G.host + }) + else + return EnterGame.onError(incorrectThings) + end + end + + onSessionKey(nil, session["sessionkey"]) + + for _, world in pairs(playdata["worlds"]) do + worlds[world.id] = { + name = world.name, + port = world.externalportunprotected or world.externalportprotected or world.externaladdress, + address = world.externaladdressunprotected or world.externaladdressprotected or world.externalport + } + end + + for _, character in pairs(playdata["characters"]) do + local world = worlds[character.worldid] + if world then + table.insert(characters, { + name = character.name, + worldName = world.name, + worldIp = world.address, + worldPort = world.port + }) + end + end + + -- proxies + if g_proxy then + g_proxy.clear() + if playdata["proxies"] then + for i, proxy in ipairs(playdata["proxies"]) do + g_proxy.addProxy(proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"])) + end + end + end + + g_game.setCustomProtocolVersion(0) + g_game.chooseRsa(G.host) + g_game.setClientVersion(G.clientVersion) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) + g_game.setCustomOs(-1) -- disable + if not g_game.getFeature(GameExtendedOpcode) then + g_game.setCustomOs(5) -- set os to windows if opcodes are disabled + end + + onCharacterList(nil, characters, account, nil) +end + +local function onHTTPResult(data, err) + if waitingForHttpResults == 0 then + return + end + + waitingForHttpResults = waitingForHttpResults - 1 + if err and waitingForHttpResults > 0 then + return -- ignore, wait for other requests + end + + if err then + return EnterGame.onError(err) + end + waitingForHttpResults = 0 + if data['error'] and data['error']:len() > 0 then + return EnterGame.onLoginError(data['error']) + elseif data['errorMessage'] and data['errorMessage']:len() > 0 then + return EnterGame.onLoginError(data['errorMessage']) + end + + if type(data["session"]) == "table" and type(data["playdata"]) == "table" then + return onTibia12HTTPResult(data["session"], data["playdata"]) + end + + local characters = data["characters"] + local account = data["account"] + local session = data["session"] + + local version = data["version"] + local things = data["things"] + local customProtocol = data["customProtocol"] + + local features = data["features"] + local settings = data["settings"] + local rsa = data["rsa"] + local proxies = data["proxies"] + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + g_logger.info(incorrectThings) + return EnterGame.onError(incorrectThings) + end + + -- custom protocol + g_game.setCustomProtocolVersion(0) + if customProtocol ~= nil then + customProtocol = tonumber(customProtocol) + if customProtocol ~= nil and customProtocol > 0 then + g_game.setCustomProtocolVersion(customProtocol) + end + end + + -- force player settings + if settings ~= nil then + for option, value in pairs(settings) do + modules.client_options.setOption(option, value, true) + end + end + + -- version + G.clientVersion = version + g_game.setClientVersion(version) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(version)) + g_game.setCustomOs(-1) -- disable + + if rsa ~= nil then + g_game.setRsa(rsa) + end + + if features ~= nil then + parseFeatures(features) + end + + if session ~= nil and session:len() > 0 then + onSessionKey(nil, session) + end + + -- proxies + if g_proxy then + g_proxy.clear() + if proxies then + for i, proxy in ipairs(proxies) do + g_proxy.addProxy(proxy["host"], tonumber(proxy["port"]), tonumber(proxy["priority"])) + end + end + end + + onCharacterList(nil, characters, account, nil) +end + + +-- public functions +function EnterGame.init() + if USE_NEW_ENERGAME then return end + enterGame = g_ui.displayUI('entergame') + + serverSelectorPanel = enterGame:getChildById('serverSelectorPanel') + customServerSelectorPanel = enterGame:getChildById('customServerSelectorPanel') + + serverSelector = serverSelectorPanel:getChildById('serverSelector') + rememberPasswordBox = enterGame:getChildById('rememberPasswordBox') + serverHostTextEdit = customServerSelectorPanel:getChildById('serverHostTextEdit') + clientVersionSelector = customServerSelectorPanel:getChildById('clientVersionSelector') + + if Servers ~= nil then + for name,server in pairs(Servers) do + serverSelector:addOption(name) + end + end + if serverSelector:getOptionsCount() == 0 or ALLOW_CUSTOM_SERVERS then + serverSelector:addOption(tr("Another")) + end + for i,proto in pairs(protos) do + clientVersionSelector:addOption(proto) + end + + if serverSelector:getOptionsCount() == 1 then + enterGame:setHeight(enterGame:getHeight() - serverSelectorPanel:getHeight()) + serverSelectorPanel:setOn(false) + end + + local account = g_crypt.decrypt(g_settings.get('account')) + local password = g_crypt.decrypt(g_settings.get('password')) + local server = g_settings.get('server') + local host = g_settings.get('host') + local clientVersion = g_settings.get('client-version') + + if serverSelector:isOption(server) then + serverSelector:setCurrentOption(server, false) + if Servers == nil or Servers[server] == nil then + serverHostTextEdit:setText(host) + end + clientVersionSelector:setOption(clientVersion) + else + server = "" + host = "" + end + + enterGame:getChildById('accountPasswordTextEdit'):setText(password) + enterGame:getChildById('accountNameTextEdit'):setText(account) + rememberPasswordBox:setChecked(#account > 0) + + g_keyboard.bindKeyDown('Ctrl+G', EnterGame.openWindow) + + if g_game.isOnline() then + return EnterGame.hide() + end + + scheduleEvent(function() + EnterGame.show() + end, 100) +end + +function EnterGame.terminate() + if not enterGame then return end + g_keyboard.unbindKeyDown('Ctrl+G') + + enterGame:destroy() + if loadBox then + loadBox:destroy() + loadBox = nil + end + if protocolLogin then + protocolLogin:cancelLogin() + protocolLogin = nil + end + EnterGame = nil +end + +function EnterGame.show() + if not enterGame then return end + enterGame:show() + enterGame:raise() + enterGame:focus() + enterGame:getChildById('accountNameTextEdit'):focus() +end + +function EnterGame.hide() + if not enterGame then return end + enterGame:hide() +end + +function EnterGame.openWindow() + if g_game.isOnline() then + CharacterList.show() + elseif not g_game.isLogging() and not CharacterList.isVisible() then + EnterGame.show() + end +end + +function EnterGame.clearAccountFields() + enterGame:getChildById('accountNameTextEdit'):clearText() + enterGame:getChildById('accountPasswordTextEdit'):clearText() + enterGame:getChildById('accountTokenTextEdit'):clearText() + enterGame:getChildById('accountNameTextEdit'):focus() + g_settings.remove('account') + g_settings.remove('password') +end + +function EnterGame.onServerChange() + server = serverSelector:getText() + if server == tr("Another") then + if not customServerSelectorPanel:isOn() then + serverHostTextEdit:setText("") + customServerSelectorPanel:setOn(true) + enterGame:setHeight(enterGame:getHeight() + customServerSelectorPanel:getHeight()) + end + elseif customServerSelectorPanel:isOn() then + enterGame:setHeight(enterGame:getHeight() - customServerSelectorPanel:getHeight()) + customServerSelectorPanel:setOn(false) + end + if Servers and Servers[server] ~= nil then + if type(Servers[server]) == "table" then + serverHostTextEdit:setText(Servers[server][1]) + else + serverHostTextEdit:setText(Servers[server]) + end + end +end + +function EnterGame.doLogin() + if g_game.isOnline() then + local errorBox = displayErrorBox(tr('Login Error'), tr('Cannot login while already in game.')) + connect(errorBox, { onOk = EnterGame.show }) + return + end + + G.account = enterGame:getChildById('accountNameTextEdit'):getText() + G.password = enterGame:getChildById('accountPasswordTextEdit'):getText() + G.authenticatorToken = enterGame:getChildById('accountTokenTextEdit'):getText() + G.stayLogged = true + G.server = serverSelector:getText():trim() + G.host = serverHostTextEdit:getText() + G.clientVersion = tonumber(clientVersionSelector:getText()) + + if not rememberPasswordBox:isChecked() then + g_settings.set('account', G.account) + g_settings.set('password', G.password) + end + g_settings.set('host', G.host) + g_settings.set('server', G.server) + g_settings.set('client-version', G.clientVersion) + g_settings.save() + + local server_params = G.host:split(":") + if G.host:lower():find("http") ~= nil then + if #server_params >= 4 then + G.host = server_params[1] .. ":" .. server_params[2] .. ":" .. server_params[3] + G.clientVersion = tonumber(server_params[4]) + elseif #server_params >= 3 then + if tostring(tonumber(server_params[3])) == server_params[3] then + G.host = server_params[1] .. ":" .. server_params[2] + G.clientVersion = tonumber(server_params[3]) + end + end + return EnterGame.doLoginHttp() + end + + local server_ip = server_params[1] + local server_port = 7171 + if #server_params >= 2 then + server_port = tonumber(server_params[2]) + end + if #server_params >= 3 then + G.clientVersion = tonumber(server_params[3]) + end + if type(server_ip) ~= 'string' or server_ip:len() <= 3 or not server_port or not G.clientVersion then + return EnterGame.onError("Invalid server, it should be in format IP:PORT or it should be http url to login script") + end + + local things = { + data = {G.clientVersion .. "/Tibia.dat", ""}, + sprites = {G.clientVersion .. "/Tibia.spr", ""}, + } + + local incorrectThings = validateThings(things) + if #incorrectThings > 0 then + g_logger.error(incorrectThings) + if Updater and not checkedByUpdater[G.clientVersion] then + checkedByUpdater[G.clientVersion] = true + return Updater.check({ + version = G.clientVersion, + host = G.host + }) + else + return EnterGame.onError(incorrectThings) + end + end + + protocolLogin = ProtocolLogin.create() + protocolLogin.onLoginError = onProtocolError + protocolLogin.onSessionKey = onSessionKey + protocolLogin.onCharacterList = onCharacterList + protocolLogin.onUpdateNeeded = onUpdateNeeded + protocolLogin.onProxyList = onProxyList + + EnterGame.hide() + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) + connect(loadBox, { onCancel = function(msgbox) + loadBox = nil + protocolLogin:cancelLogin() + EnterGame.show() + end }) + + if G.clientVersion == 1000 then -- some people don't understand that tibia 10 uses 1100 protocol + G.clientVersion = 1100 + end + -- if you have custom rsa or protocol edit it here + g_game.setClientVersion(G.clientVersion) + g_game.setProtocolVersion(g_game.getClientProtocolVersion(G.clientVersion)) + g_game.setCustomProtocolVersion(0) + g_game.setCustomOs(-1) -- disable + g_game.chooseRsa(G.host) + if #server_params <= 3 and not g_game.getFeature(GameExtendedOpcode) then + g_game.setCustomOs(2) -- set os to windows if opcodes are disabled + end + + -- extra features from init.lua + for i = 4, #server_params do + g_game.enableFeature(tonumber(server_params[i])) + end + + -- proxies + if g_proxy then + g_proxy.clear() + end + + if modules.game_things.isLoaded() then + g_logger.info("Connecting to: " .. server_ip .. ":" .. server_port) + protocolLogin:login(server_ip, server_port, G.account, G.password, G.authenticatorToken, G.stayLogged) + else + loadBox:destroy() + loadBox = nil + EnterGame.show() + end +end + +function EnterGame.doLoginHttp() + if G.host == nil or G.host:len() < 10 then + return EnterGame.onError("Invalid server url: " .. G.host) + end + + loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) + connect(loadBox, { onCancel = function(msgbox) + loadBox = nil + EnterGame.show() + end }) + + local data = { + type = "login", + account = G.account, + accountname = G.account, + email = G.account, + password = G.password, + accountpassword = G.password, + token = G.authenticatorToken, + version = APP_VERSION, + uid = G.UUID, + stayloggedin = true + } + + local server = serverSelector:getText() + if Servers and Servers[server] ~= nil then + if type(Servers[server]) == "table" then + local urls = Servers[server] + waitingForHttpResults = #urls + for _, url in ipairs(urls) do + HTTP.postJSON(url, data, onHTTPResult) + end + else + waitingForHttpResults = 1 + HTTP.postJSON(G.host, data, onHTTPResult) + end + end + EnterGame.hide() +end + +function EnterGame.onError(err) + if loadBox then + loadBox:destroy() + loadBox = nil + end + local errorBox = displayErrorBox(tr('Login Error'), err) + errorBox.onOk = EnterGame.show +end + +function EnterGame.onLoginError(err) + if loadBox then + loadBox:destroy() + loadBox = nil + end + local errorBox = displayErrorBox(tr('Login Error'), err) + errorBox.onOk = EnterGame.show + if err:lower():find("invalid") or err:lower():find("not correct") or err:lower():find("or password") then + EnterGame.clearAccountFields() + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otmod new file mode 100644 index 0000000..3b39ae1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otmod @@ -0,0 +1,12 @@ +Module + name: client_entergame + description: Manages enter game and character list windows + author: edubart & otclient.ovh + website: https://github.com/edubart/otclient + scripts: [ entergame, characterlist ] + @onLoad: EnterGame.init() CharacterList.init() + @onUnload: EnterGame.terminate() CharacterList.terminate() + + load-later: + - game_things + - game_features diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otui new file mode 100644 index 0000000..04ec0d7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/entergame.otui @@ -0,0 +1,186 @@ +EnterGameWindow + id: enterGame + @onEnter: EnterGame.doLogin() + + MenuLabel + !text: tr('Account name') + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + + TextEdit + id: accountNameTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + MenuLabel + !text: tr('Password') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + PasswordTextEdit + id: accountPasswordTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + MenuLabel + !text: tr('Token') + anchors.left: prev.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 8 + + TextEdit + id: accountTokenTextEdit + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + + Panel + id: serverSelectorPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 52 + on: true + focusable: false + + $on: + visible: true + margin-top: 0 + + $!on: + visible: false + margin-top: -52 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 10 + + MenuLabel + id: serverLabel + !text: tr('Server') + anchors.left: parent.left + anchors.top: prev.bottom + text-auto-resize: true + margin-top: 5 + + ComboBox + id: serverSelector + anchors.left: prev.left + anchors.right: parent.right + anchors.top: serverLabel.bottom + margin-top: 2 + margin-right: 3 + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + text-offset: 5 2 + @onOptionChange: EnterGame.onServerChange() + + Panel + id: customServerSelectorPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + height: 52 + on: true + focusable: true + + $on: + visible: true + margin-top: 0 + + $!on: + visible: false + margin-top: -52 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 8 + + MenuLabel + id: serverLabel + !text: tr('IP:PORT or URL') + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 8 + text-auto-resize: true + + TextEdit + id: serverHostTextEdit + !tooltip: tr('Make sure that your client uses\nthe correct game client version') + anchors.left: parent.left + anchors.top: serverLabel.bottom + margin-top: 2 + width: 150 + + MenuLabel + id: clientLabel + !text: tr('Version') + anchors.left: serverHostTextEdit.right + anchors.top: serverLabel.top + text-auto-resize: true + margin-left: 10 + + ComboBox + id: clientVersionSelector + anchors.top: serverHostTextEdit.top + anchors.bottom: serverHostTextEdit.bottom + anchors.left: prev.left + anchors.right: parent.right + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + margin-right: 3 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 10 + + CheckBox + id: rememberPasswordBox + !text: tr('Remember password') + !tooltip: tr('Remember account and password when starts client') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 9 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 9 + + Button + !text: tr('Login') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 10 + margin-left: 50 + margin-right: 50 + @onClick: EnterGame.doLogin() + + Label + id: serverInfoLabel + font: verdana-11px-rounded + anchors.top: prev.top + anchors.left: parent.left + margin-top: 5 + color: green + text-auto-resize: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/waitinglist.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/waitinglist.otui new file mode 100644 index 0000000..c7b86a6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_entergame/waitinglist.otui @@ -0,0 +1,44 @@ +MainWindow + id: waitingWindow + !text: tr('Waiting List') + size: 260 180 + @onEscape: CharacterList.cancelWait() + + Label + id: infoLabel + anchors.top: parent.top + anchors.bottom: progressBar.top + anchors.left: parent.left + anchors.right: parent.right + text-wrap: true + + ProgressBar + id: progressBar + height: 15 + background-color: #4444ff + anchors.bottom: timeLabel.top + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 10 + + Label + id: timeLabel + anchors.bottom: separator.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 10 + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: CharacterList.cancelWait() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.lua new file mode 100644 index 0000000..d6b9b0d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.lua @@ -0,0 +1,109 @@ +local feedbackWindow +local textEdit +local okButton +local cancelButton +local postId = 0 +local tries = 0 +local replyEvent = nil + +function init() + feedbackWindow = g_ui.displayUI('feedback') + feedbackWindow:hide() + + textEdit = feedbackWindow:getChildById('text') + okButton = feedbackWindow:getChildById('okButton') + cancelButton = feedbackWindow:getChildById('cancelButton') + + okButton.onClick = send + cancelButton.onClick = hide + feedbackWindow.onEscape = hide +end + +function terminate() + feedbackWindow:destroy() + removeEvent(replyEvent) +end + +function show() + if not Services or not Services.feedback or Services.feedback:len() < 4 then + return + end + + feedbackWindow:show() + feedbackWindow:raise() + feedbackWindow:focus() + + textEdit:setMaxLength(8192) + textEdit:setText('') + textEdit:setEditable(true) + textEdit:setCursorVisible(true) + feedbackWindow:focusChild(textEdit, KeyboardFocusReason) + + tries = 0 +end + +function hide() + feedbackWindow:hide() + textEdit:setEditable(false) + textEdit:setCursorVisible(false) +end + +function send() + local text = textEdit:getText() + if text:len() > 1 then + local localPlayer = g_game.getLocalPlayer() + local playerData = nil + if localPlayer ~= nil then + playerData = { + name = localPlayer:getName(), + position = localPlayer:getPosition() + } + end + local details = { + report_delay = sendInterval, + os = g_app.getOs(), + graphics_vendor = g_graphics.getVendor(), + graphics_renderer = g_graphics.getRenderer(), + graphics_version = g_graphics.getVersion(), + fps = g_app.getFps(), + maxFps = g_app.getMaxFps(), + atlas = g_atlas.getStats(), + classic = tostring(g_settings.getBoolean("classicView")), + fullscreen = tostring(g_window.isFullscreen()), + vsync = tostring(g_settings.getBoolean("vsync")), + window_width = g_window.getWidth(), + window_height = g_window.getHeight(), + player_name = g_game.getCharacterName(), + world_name = g_game.getWorldName(), + otserv_host = G.host, + otserv_protocol = g_game.getProtocolVersion(), + otserv_client = g_game.getClientVersion(), + build_version = g_app.getVersion(), + build_revision = g_app.getBuildRevision(), + build_commit = g_app.getBuildCommit(), + build_date = g_app.getBuildDate(), + display_width = g_window.getDisplayWidth(), + display_height = g_window.getDisplayHeight(), + cpu = g_platform.getCPUName(), + mem = g_platform.getTotalSystemMemory(), + os_name = g_platform.getOSName() + } + local data = json.encode({ + text = text, + version = g_app.getVersion(), + host = g_settings.get('host'), + player = playerData, + details = details + }) + + postId = HTTP.post(Services.feedback, data, function(ret, err) + if err then + tries = tries + 1 + if tries < 3 then + replyEvent = scheduleEvent(send, 1000) + end + end + end) + end + hide() +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otmod new file mode 100644 index 0000000..e9713cb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otmod @@ -0,0 +1,10 @@ +Module + name: client_feedback + description: Allow to send feedback + author: otclientv8 + website: otclient.ovh + sandboxed: true + dependencies: [ game_interface ] + scripts: [ feedback ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otui new file mode 100644 index 0000000..023facb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_feedback/feedback.otui @@ -0,0 +1,48 @@ +MainWindow + id: feedbackWindow + size: 400 280 + !text: tr("Feedback/Bug report") + + Label + id: description + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: left + text-wrap: true + !text: tr("Bellow enter your feedback or bug report. Please include as much details as possible. Thank you!") + + MultilineTextEdit + id: text + anchors.top: textScroll.top + anchors.left: parent.left + anchors.right: textScroll.left + anchors.bottom: textScroll.bottom + vertical-scrollbar: textScroll + text-wrap: true + + VerticalScrollBar + id: textScroll + anchors.top: description.bottom + anchors.bottom: okButton.top + anchors.right: parent.right + margin-top: 10 + margin-bottom: 10 + step: 16 + pixels-scroll: true + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.lua new file mode 100644 index 0000000..f221f01 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.lua @@ -0,0 +1,177 @@ +dofile 'neededtranslations' + +-- private variables +local defaultLocaleName = 'en' +local installedLocales +local currentLocale +local missingTranslations = {} + +function createWindow() + localesWindow = g_ui.displayUI('locales') + local localesPanel = localesWindow:getChildById('localesPanel') + local layout = localesPanel:getLayout() + local spacing = layout:getCellSpacing() + local size = layout:getCellSize() + + local count = 0 + for name,locale in pairs(installedLocales) do + local widget = g_ui.createWidget('LocalesButton', localesPanel) + widget:setImageSource('/images/flags/' .. name .. '') + widget:setText(locale.languageName) + widget.onClick = function() selectFirstLocale(name) end + count = count + 1 + end + + count = math.max(1, math.min(count, 3)) + localesPanel:setWidth(size.width*count + spacing*(count-1)) + + addEvent(function() addEvent(function() localesWindow:raise() localesWindow:focus() end) end) +end + +function selectFirstLocale(name) + if localesWindow then + localesWindow:destroy() + localesWindow = nil + end + if setLocale(name) then + g_modules.reloadModules() + end + g_settings.save() +end + +-- public functions +function init() + installedLocales = {} + + installLocales('/locales') + + local userLocaleName = g_settings.get('locale', 'false') + if userLocaleName ~= 'false' and setLocale(userLocaleName) then + pdebug('Using configured locale: ' .. userLocaleName) + else + setLocale(defaultLocaleName) + --connect(g_app, { onRun = createWindow }) + end +end + +function terminate() + installedLocales = nil + currentLocale = nil + + --disconnect(g_app, { onRun = createWindow }) +end + +function generateNewTranslationTable(localename) + local locale = installedLocales[localename] + for _i,k in pairs(neededTranslations) do + local trans = locale.translation[k] + k = k:gsub('\n','\\n') + k = k:gsub('\t','\\t') + k = k:gsub('\"','\\\"') + if trans then + trans = trans:gsub('\n','\\n') + trans = trans:gsub('\t','\\t') + trans = trans:gsub('\"','\\\"') + end + if not trans then + print(' ["' .. k .. '"]' .. ' = false,') + else + print(' ["' .. k .. '"]' .. ' = "' .. trans .. '",') + end + end +end + +function installLocale(locale) + if not locale or not locale.name then + error('Unable to install locale.') + end + + if _G.allowedLocales and not _G.allowedLocales[locale.name] then return end + + if locale.name ~= defaultLocaleName then + local updatesNamesMissing = {} + for _,k in pairs(neededTranslations) do + if locale.translation[k] == nil then + updatesNamesMissing[#updatesNamesMissing + 1] = k + end + end + + if #updatesNamesMissing > 0 then + pdebug('Locale \'' .. locale.name .. '\' is missing ' .. #updatesNamesMissing .. ' translations.') + for _,name in pairs(updatesNamesMissing) do + pdebug('["' .. name ..'"] = \"\",') + end + end + end + + local installedLocale = installedLocales[locale.name] + if installedLocale then + for word,translation in pairs(locale.translation) do + installedLocale.translation[word] = translation + end + else + installedLocales[locale.name] = locale + end +end + +function installLocales(directory) + dofiles(directory) +end + +function setLocale(name) + local locale = installedLocales[name] + if locale == currentLocale then return end + if not locale then + pwarning("Locale " .. name .. ' does not exist.') + return false + end + currentLocale = locale + g_settings.set('locale', name) + if onLocaleChanged then onLocaleChanged(name) end + return true +end + +function getInstalledLocales() + return installedLocales +end + +function getCurrentLocale() + return currentLocale +end + +-- global function used to translate texts +function _G.tr(text, ...) + if currentLocale then + if tonumber(text) and currentLocale.formatNumbers then + local number = tostring(text):split('.') + local out = '' + local reverseNumber = number[1]:reverse() + for i=1,#reverseNumber do + out = out .. reverseNumber:sub(i, i) + if i % 3 == 0 and i ~= #number then + out = out .. currentLocale.thousandsSeperator + end + end + + if number[2] then + out = number[2] .. currentLocale.decimalSeperator .. out + end + return out:reverse() + elseif tostring(text) then + local translation = currentLocale.translation[text] + if not translation then + if translation == nil then + if currentLocale.name ~= defaultLocaleName then + if not missingTranslations[text] then + pdebug('Unable to translate: \"' .. text .. '\"') + missingTranslations[text] = true + end + end + end + translation = text + end + return string.format(translation, ...) + end + end + return text +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otmod new file mode 100644 index 0000000..148a1df --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otmod @@ -0,0 +1,9 @@ +Module + name: client_locales + description: Translates texts to selected language + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ locales ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otui new file mode 100644 index 0000000..6b5c33e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/locales.otui @@ -0,0 +1,35 @@ +LocalesMainLabel < Label + font: sans-bold-16px + +LocalesButton < UIWidget + size: 96 96 + image-size: 96 96 + image-smooth: true + text-offset: 0 96 + font: verdana-11px-antialised + +UIWindow + id: localesWindow + background-color: #000000 + opacity: 0.90 + clipping: true + anchors.fill: parent + + LocalesMainLabel + !text: tr('Select your language') + text-auto-resize: true + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + margin-top: -100 + + Panel + id: localesPanel + margin-top: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + anchors.bottom: parent.bottom + layout: + type: grid + cell-size: 96 128 + cell-spacing: 32 + flow: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/neededtranslations.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/neededtranslations.lua new file mode 100644 index 0000000..68f8900 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_locales/neededtranslations.lua @@ -0,0 +1,364 @@ +-- generated by ./tools/gen_needed_translations.sh +neededTranslations = { + "1a) Offensive Name", + "1b) Invalid Name Format", + "1c) Unsuitable Name", + "1d) Name Inciting Rule Violation", + "2a) Offensive Statement", + "2b) Spamming", + "2c) Illegal Advertising", + "2d) Off-Topic Public Statement", + "2e) Non-English Public Statement", + "2f) Inciting Rule Violation", + "3a) Bug Abuse", + "3b) Game Weakness Abuse", + "3c) Using Unofficial Software to Play", + "3d) Hacking", + "3e) Multi-Clienting", + "3f) Account Trading or Sharing", + "4a) Threatening Gamemaster", + "4b) Pretending to Have Influence on Rule Enforcement", + "4c) False Report to Gamemaster", + "Accept", + "Account name", + "Account Status:", + "Action:", + "Add", + "Add new VIP", + "Addon 1", + "Addon 2", + "Addon 3", + "Add to VIP list", + "Adjust volume", + "Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!", + "All", + "All modules and scripts were reloaded.", + "Allow auto chase override", + "Ambient light: %s%%", + "Amount:", + "Amount", + "Anonymous", + "Are you sure you want to logout?", + "Attack", + "Author", + "Autoload", + "Autoload priority", + "Auto login", + "Auto login selected character on next charlist load", + "Axe Fighting", + "Balance:", + "Banishment", + "Banishment + Final Warning", + "Battle", + "Browse", + "Bug report sent.", + "Button Assign", + "Buy", + "Buy Now", + "Buy Offers", + "Buy with backpack", + "Cancel", + "Cannot login while already in game.", + "Cap", + "Capacity", + "Center", + "Channels", + "Character List", + "Classic control", + "Clear current message window", + "Clear Messages", + "Clear object", + "Client needs update.", + "Close", + "Close this channel", + "Club Fighting", + "Combat Controls", + "Comment:", + "Connecting to game server...", + "Connecting to login server...", + "Console", + "Cooldowns", + "Copy message", + "Copy name", + "Copy Name", + "Create Map Mark", + "Create mark", + "Create New Offer", + "Create Offer", + "Current hotkeys:", + "Current hotkey to add: %s", + "Current Offers", + "Default", + "Delete mark", + "Description:", + "Description", + "Destructive Behaviour", + "Detail", + "Details", + "Disable Shared Experience", + "Dismount", + "Display connection speed to the server (milliseconds)", + "Distance Fighting", + "Don\'t stretch/shrink Game Window", + "Edit hotkey text:", + "Edit List", + "Edit Text", + "Enable music", + "Enable Shared Experience", + "Enable smart walking", + "Enable vertical synchronization", + "Enable walk booster", + "Enter Game", + "Enter one name per line.", + "Enter with your account again to update your client.", + "Error", + "Error", + "Excessive Unjustified Player Killing", + "Exclude from private chat", + "Exit", + "Experience", + "Filter list to match your level", + "Filter list to match your vocation", + "Find:", + "Fishing", + "Fist Fighting", + "Follow", + "Force Exit", + "For Your Information", + "Free Account", + "Fullscreen", + "Game", + "Game framerate limit: %s", + "Graphics", + "Graphics card driver not detected", + "Graphics Engine:", + "Head", + "Healing", + "Health Info", + "Health Information", + "Hide monsters", + "Hide non-skull players", + "Hide Npcs", + "Hide Offline", + "Hide party members", + "Hide players", + "Hide spells for higher exp. levels", + "Hide spells for other vocations", + "Hit Points", + "Hold left mouse button to navigate\nScroll mouse middle button to zoom\nRight mouse button to create map marks", + "Hotkeys", + "If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character.", + "Ignore", + "Ignore capacity", + "Ignored players:", + "Ignore equipped", + "Ignore List", + "Ignore players", + "Ignore Private Messages", + "Ignore Yelling", + "Interface framerate limit: %s", + "Inventory", + "Invite to Party", + "Invite to private chat", + "IP Address Banishment", + "Item Offers", + "It is empty.", + "Join %s\'s Party", + "Leave Party", + "Level", + "Lifetime Premium Account", + "Limits FPS to 60", + "List of items that you're able to buy", + "List of items that you're able to sell", + "Load", + "Logging out...", + "Login", + "Login Error", + "Login Error", + "Logout", + "Look", + "Magic Level", + "Make sure that your client uses\nthe correct game protocol version", + "Mana", + "Manage hotkeys:", + "Market", + "Market Offers", + "Message of the day", + "Message to ", + "Message to %s", + "Minimap", + "Module Manager", + "Module name", + "Mount", + "Move Stackable Item", + "Move up", + "My Offers", + "Name:", + "Name Report", + "Name Report + Banishment", + "Name Report + Banishment + Final Warning", + "No", + "No graphics card detected, everything will be drawn using the CPU,\nthus the performance will be really bad.\nPlease update your graphics driver to have a better performance.", + "No item selected.", + "No Mount", + "No Outfit", + "No statement has been selected.", + "Notation", + "NPC Trade", + "Offer History", + "Offers", + "Offer Type:", + "Offline Training", + "Ok", + "on %s.\n", + "Open", + "Open a private message channel:", + "Open charlist automatically when starting client", + "Open in new window", + "Open new channel", + "Options", + "Overview", + "Pass Leadership to %s", + "Password", + "Piece Price:", + "Please enter a character name:", + "Please, press the key you wish to add onto your hotkeys manager", + "Please Select", + "Please use this dialog to only report bugs. Do not report rule violations here!", + "Please wait", + "Port", + "Position:", + "Position: %i %i %i", + "Premium Account (%s) days left", + "Price:", + "Primary", + "Protocol", + "Quest Log", + "Randomize", + "Randomize characters outfit", + "Reason:", + "Refresh", + "Refresh Offers", + "Regeneration Time", + "Reject", + "Reload All", + "Remember account and password when starts client", + "Remember password", + "Remove", + "Remove %s", + "Report Bug", + "Reserved for more functionality later.", + "Reset Market", + "Revoke %s\'s Invitation", + "Rotate", + "Rule Violation", + "Save", + "Save Messages", + "Search:", + "Search all items", + "Secondary", + "Select object", + "Select Outfit", + "Select your language", + "Sell", + "Sell Now", + "Sell Offers", + "Send", + "Send automatically", + "Send Message", + "Server", + "Server Log", + "Set Outfit", + "Shielding", + "Show all items", + "Show connection ping", + "Show Depot Only", + "Show event messages in console", + "Show frame rate", + "Show info messages in console", + "Show left panel", + "Show levels in console", + "Show Offline", + "Show private messages in console", + "Show private messages on screen", + "Show Server Messages", + "Show status messages in console", + "Show Text", + "Show timestamps in console", + "Show your depot items only", + "Skills", + "Soul", + "Soul Points", + "Special", + "Speed", + "Spell Cooldowns", + "Spell List", + "Stamina", + "Statement:", + "Statement Report", + "Statistics", + "Stop Attack", + "Stop Follow", + "Support", + "%s: (use object)", + "%s: (use object on target)", + "%s: (use object on yourself)", + "%s: (use object with crosshair)", + "Sword Fighting", + "Terminal", + "There is no way.", + "Title", + "Total Price:", + "Trade", + "Trade with ...", + "Trying to reconnect in %s seconds.", + "Unable to load dat file, please place a valid dat in '%s'", + "Unable to load spr file, please place a valid spr in '%s'", + "Unable to logout.", + "Unignore", + "Unload", + "Update needed", + "Use", + "Use on target", + "Use on yourself", + "Use with ...", + "Version", + "VIP List", + "Voc.", + "Vocation", + "Waiting List", + "Website", + "Weight:", + "Will detect when to use diagonal step based on the\nkeys you are pressing", + "With crosshair", + "Yes", + "You are bleeding", + "You are burning", + "You are cursed", + "You are dazzled", + "You are dead.", + "You are dead", + "You are drowning", + "You are drunk", + "You are electrified", + "You are freezing", + "You are hasted", + "You are hungry", + "You are paralysed", + "You are poisoned", + "You are protected by a magic shield", + "You are strengthened", + "You are within a protection zone", + "You can enter new text.", + "You have %s percent", + "You have %s percent to go", + "You may not logout during a fight", + "You may not logout or enter a protection zone", + "You must enter a comment.", + "You must enter a valid server address and port.", + "You must select a character to login!", + "Your Capacity:", + "You read the following, written by \n%s\n", + "You read the following, written on \n%s.\n", + "Your Money:", +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.lua new file mode 100644 index 0000000..b3f47e7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.lua @@ -0,0 +1,216 @@ +local overlay +local keypad +local touchStart = 0 +local updateCursorEvent +local zoomInButton +local zoomOutButton +local keypadButton +local keypadEvent +local keypadMousePos = {x=0.5, y=0.5} +local keypadTicks = 0 + +-- public functions +function init() + if not g_app.isMobile() then return end + overlay = g_ui.displayUI('mobile') + keypad = overlay.keypad + overlay:raise() + + zoomInButton = modules.client_topmenu.addLeftButton('zoomInButton', 'Zoom In', '/images/topbuttons/zoomin', function() g_app.scaleUp() end) + zoomOutButton = modules.client_topmenu.addLeftButton('zoomOutButton', 'Zoom Out', '/images/topbuttons/zoomout', function() g_app.scaleDown() end) + keypadButton = modules.client_topmenu.addLeftGameToggleButton('keypadButton', 'Keypad', '/images/topbuttons/keypad', function() + keypadButton:setChecked(not keypadButton:isChecked()) + if not g_game.isOnline() then + keypad:setVisible(false) + return + end + keypad:setVisible(keypadButton:isChecked()) + end) + keypadButton:setChecked(true) + + scheduleEvent(function() + g_app.scale(5.0) + end, 10) + + connect(overlay, { + onMousePress = onMousePress, + onMouseRelease = onMouseRelease, + onTouchPress = onMousePress, + onTouchRelease = onMouseRelease, + onMouseMove = onMouseMove + }) + connect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + if g_game.isOnline() then + online() + end +end + +function terminate() + if not g_app.isMobile() then return end + removeEvent(updateCursorEvent) + removeEvent(keypadEvent) + keypadEvent = nil + disconnect(overlay, { + onMousePress = onMousePress, + onMouseRelease = onMouseRelease, + onTouchPress = onMousePress, + onTouchRelease = onMouseRelease, + onMouseMove = onMouseMove + }) + disconnect(keypad, { + onTouchPress = onKeypadTouchPress, + onTouchRelease = onKeypadTouchRelease, + onMouseMove = onKeypadTouchMove + }) + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + zoomInButton:destroy() + zoomOutButton:destroy() + keypadButton:destroy() + overlay:destroy() + overlay = nil +end + +function hide() + overlay:hide() +end + +function show() + overlay:show() +end + +function online() + if keypadButton:isChecked() then + keypad:raise() + keypad:show() + end +end + +function offline() + keypad:hide() +end + +function onMouseMove(widget, pos, offset) + +end + +function onMousePress(widget, pos, button) + overlay:raise() + if button == MouseTouch then -- touch + overlay:raise() + overlay.cursor:show() + overlay.cursor:setPosition({x=pos.x - 32, y = pos.y - 32}) + touchStart = g_clock.millis() + updateCursor() + else + overlay.cursor:hide() + removeEvent(updateCursorEvent) + end +end + +function onMouseRelease(widget, pos, button) + if button == MouseTouch then + overlay.cursor:hide() + removeEvent(updateCursorEvent) + end +end + +function updateCursor() + removeEvent(updateCursorEvent) + if not g_mouse.isPressed(MouseTouch) then return end + local percent = 100 - math.max(0, math.min(100, (g_clock.millis() - touchStart) / 5)) -- 500 ms + overlay.cursor:setPercent(percent) + if percent > 0 then + overlay.cursor:setOpacity(0.5) + updateCursorEvent = scheduleEvent(updateCursor, 10) + else + overlay.cursor:setOpacity(0.8) + end +end + +function onKeypadTouchMove(widget, pos, offset) + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + return true +end + +function onKeypadTouchPress(widget, pos, button) + if button ~= MouseTouch then return false end + keypadTicks = 0 + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + return true +end + +function onKeypadTouchRelease(widget, pos, button) + if button ~= MouseTouch then return false end + keypadMousePos = {x=(pos.x - widget:getPosition().x) / widget:getWidth(), + y=(pos.y - widget:getPosition().y) / widget:getHeight()} + executeWalk() + removeEvent(keypadEvent) + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return true +end + +function executeWalk() + removeEvent(keypadEvent) + keypadEvent = nil + if not modules.game_walking or not g_mouse.isPressed(MouseTouch) then + keypad.pointer:setMarginTop(0) + keypad.pointer:setMarginLeft(0) + return + end + keypadEvent = scheduleEvent(executeWalk, 20) + keypadMousePos.x = math.min(1, math.max(0, keypadMousePos.x)) + keypadMousePos.y = math.min(1, math.max(0, keypadMousePos.y)) + local angle = math.atan2(keypadMousePos.x - 0.5, keypadMousePos.y - 0.5) + local maxTop = math.abs(math.cos(angle)) * 75 + local marginTop = math.max(-maxTop, math.min(maxTop, (keypadMousePos.y - 0.5) * 150)) + local maxLeft = math.abs(math.sin(angle)) * 75 + local marginLeft = math.max(-maxLeft, math.min(maxLeft, (keypadMousePos.x - 0.5) * 150)) + keypad.pointer:setMarginTop(marginTop) + keypad.pointer:setMarginLeft(marginLeft) + local dir + if keypadMousePos.y < 0.3 and keypadMousePos.x < 0.3 then + dir = Directions.NorthWest + elseif keypadMousePos.y < 0.3 and keypadMousePos.x > 0.7 then + dir = Directions.NorthEast + elseif keypadMousePos.y > 0.7 and keypadMousePos.x < 0.3 then + dir = Directions.SouthWest + elseif keypadMousePos.y > 0.7 and keypadMousePos.x > 0.7 then + dir = Directions.SouthEast + end + if not dir and (math.abs(keypadMousePos.y - 0.5) > 0.1 or math.abs(keypadMousePos.x - 0.5) > 0.1) then + if math.abs(keypadMousePos.y - 0.5) > math.abs(keypadMousePos.x - 0.5) then + if keypadMousePos.y < 0.5 then + dir = Directions.North + else + dir = Directions.South + end + else + if keypadMousePos.x < 0.5 then + dir = Directions.West + else + dir = Directions.East + end + end + end + if dir then + modules.game_walking.walk(dir, keypadTicks) + if keypadTicks == 0 then + keypadTicks = 100 + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otmod new file mode 100644 index 0000000..da7a329 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otmod @@ -0,0 +1,9 @@ +Module + name: client_mobile + description: Handles the mobile interface for smartphones + author: otclient@otclient.ovh + website: http://otclient.net + sandboxed: true + scripts: [ mobile ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otui new file mode 100644 index 0000000..c85e83c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_mobile/mobile.otui @@ -0,0 +1,39 @@ +UIWidget + anchors.fill: parent + focusable: false + phantom: true + + UIProgressRect + id: cursor + size: 64 64 + background: #FF5858 + percent: 100 + visible: false + x: 0 + y: 0 + focusable: false + phantom: true + + UIWidget + id: keypad + size: 200 150 + anchors.bottom: parent.bottom + anchors.right: parent.right + phantom: false + focusable: false + visible: false + background: #00000044 + image-source: /images/game/mobile/keypad + image-fixed-ratio: true + image-rect: 25 0 150 150 + + UIWidget + id: pointer + size: 49 49 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/game/mobile/keypad_pointer + image-fixed-ratio: true + phantom: true + focusable: false + \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/audio.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/audio.otui new file mode 100644 index 0000000..4e803b4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/audio.otui @@ -0,0 +1,36 @@ +OptionPanel + OptionCheckBox + id: enableAudio + !text: tr('Enable audio') + + OptionCheckBox + id: enableMusicSound + !text: tr('Enable music sound') + + Label + id: musicSoundVolumeLabel + !text: tr('Music volume: %d', 100) + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('musicSoundVolume') + self:setText(tr('Music volume: %d', value)) + + OptionScrollbar + id: musicSoundVolume + margin-top: 3 + minimum: 0 + maximum: 100 + + Label + id: botSoundVolumeLabel + !text: tr('Bot sound volume: %d', 100) + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('botSoundVolume') + self:setText(tr('Bot sound volume: %d', value)) + + OptionScrollbar + id: botSoundVolume + margin-top: 3 + minimum: 0 + maximum: 100 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/console.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/console.otui new file mode 100644 index 0000000..a0b0bae --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/console.otui @@ -0,0 +1,28 @@ +OptionPanel + OptionCheckBox + id: showInfoMessagesInConsole + !text: tr('Show info messages in console') + + OptionCheckBox + id: showEventMessagesInConsole + !text: tr('Show event messages in console') + + OptionCheckBox + id: showStatusMessagesInConsole + !text: tr('Show status messages in console') + + OptionCheckBox + id: showTimestampsInConsole + !text: tr('Show timestamps in console') + + OptionCheckBox + id: showLevelsInConsole + !text: tr('Show levels in console') + + OptionCheckBox + id: showPrivateMessagesInConsole + !text: tr('Show private messages in console') + + OptionCheckBox + id: showPrivateMessagesOnScreen + !text: tr('Show private messages on screen') \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/game.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/game.otui new file mode 100644 index 0000000..b0adb0b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/game.otui @@ -0,0 +1,147 @@ +OptionPanel + OptionCheckBox + id: classicControl + !text: tr('Classic control') + + $mobile: + visible: false + + OptionCheckBox + id: autoChaseOverride + !text: tr('Allow auto chase override') + + OptionCheckBox + id: displayText + !text: tr('Display text messages') + + OptionCheckBox + id: wsadWalking + !text: tr('Enable WSAD walking') + !tooltip: tr('Disable chat and allow walk using WSAD keys') + $mobile: + visible: false + + OptionCheckBox + id: dash + !text: tr('Enable fast walking (DASH)') + !tooltip: tr('Allows to execute next move without server confirmation of previous one') + + OptionCheckBox + id: smartWalk + !text: tr('Enable smart walking') + !tooltip: tr('Will detect when to use diagonal step based on the\nkeys you are pressing') + + Label + id: hotkeyDelayLabel + margin-top: 10 + !tooltip: tr('Give you some time to make a turn while walking if you press many keys simultaneously') + @onSetup: | + local value = modules.client_options.getOption('hotkeyDelay') + self:setText(tr('Hotkey delay: %s ms', value)) + + OptionScrollbar + id: hotkeyDelay + margin-top: 3 + minimum: 5 + maximum: 50 + + Label + id: walkFirstStepDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkFirstStepDelay') + self:setText(tr('Walk delay after first step: %s ms', value)) + + $mobile: + visible: false + + OptionScrollbar + id: walkFirstStepDelay + margin-top: 3 + minimum: 50 + maximum: 300 + + $mobile: + visible: false + + Label + id: walkTurnDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkTurnDelay') + self:setText(tr('Walk delay after turn: %s ms', value)) + + $mobile: + visible: false + + OptionScrollbar + id: walkTurnDelay + margin-top: 3 + minimum: 0 + maximum: 300 + + $mobile: + visible: false + + Label + id: walkCtrlTurnDelayLabel + margin-top: 10 + $mobile: + visible: false + @onSetup: | + local value = modules.client_options.getOption('walkTurnDelay') + self:setText(tr('Walk delay after ctrl turn: %s ms', value)) + + OptionScrollbar + id: walkCtrlTurnDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Label + id: walkStairsDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkStairsDelay') + self:setText(tr('Walk delay after floor change: %s ms', value)) + $mobile: + visible: false + + OptionScrollbar + id: walkStairsDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Label + id: walkTeleportDelayLabel + margin-top: 10 + @onSetup: | + local value = modules.client_options.getOption('walkTeleportDelay') + self:setText(tr('Walk delay after teleport: %s ms', value)) + $mobile: + visible: false + + OptionScrollbar + id: walkTeleportDelay + margin-top: 3 + minimum: 0 + maximum: 300 + $mobile: + visible: false + + Panel + height: 30 + margin-top: 10 + + Button + id: changeLocale + !text: tr('Change language') + @onClick: modules.client_locales.createWindow() + anchors.left: parent.left + anchors.top: parent.top + width: 150 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/graphics.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/graphics.otui new file mode 100644 index 0000000..ee9c143 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/graphics.otui @@ -0,0 +1,79 @@ +OptionPanel + Label + text-wrap: false + @onSetup: | + self:setText(tr("GPU: ") .. g_graphics.getRenderer()) + + Label + text-wrap: false + @onSetup: | + self:setText(tr("Version: ") .. g_graphics.getVersion()) + + HorizontalSeparator + id: separator + margin: 5 5 5 5 + + OptionCheckBox + id: vsync + !text: tr('Enable vertical synchronization') + !tooltip: tr('Limits FPS (usually to 60)') + + OptionCheckBox + id: showFps + !text: tr('Show frame rate') + + OptionCheckBox + id: fullscreen + !text: tr('Fullscreen') + tooltip: Ctrl+Shift+F + + Label + margin-top: 12 + id: optimizationLevelLabel + !text: tr("Optimization level") + + ComboBox + id: optimizationLevel + margin-top: 3 + margin-right: 2 + margin-left: 2 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("Automatic") + self:addOption("None") + self:addOption("Low") + self:addOption("Medium") + self:addOption("High") + self:addOption("Maximum") + + Label + !text: tr('High/Maximum optimization level may cause visual defects.') + margin-top: 5 + + Label + id: backgroundFrameRateLabel + !text: tr('Game framerate limit: %s', 'max') + margin-top: 12 + @onSetup: | + local value = modules.client_options.getOption('backgroundFrameRate') + local text = value + if value <= 0 or value >= 201 then + text = 'max' + end + self:setText(tr('Game framerate limit: %s', text)) + + OptionScrollbar + id: backgroundFrameRate + margin-top: 3 + minimum: 10 + maximum: 201 + + Label + id: tips + margin-top: 20 + text-auto-resize: true + text-align: left + text-wrap: true + !text: tr("If you have FPS issues:\n- Use OpenGL version (_gl)\n- Disable vertical synchronization\n- Set higher optimization level\n- Lower screen resolution\nOr report it on forum: http://otclient.net") + $mobile: + visible: false \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/interface.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/interface.otui new file mode 100644 index 0000000..141c3e2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/interface.otui @@ -0,0 +1,172 @@ +OptionPanel + OptionCheckBox + id: classicView + !text: tr('Classic view') + margin-top: 5 + + $mobile: + visible: false + + OptionCheckBox + id: cacheMap + !text: tr('Cache map (for non-classic view)') + + $mobile: + visible: false + + OptionCheckBox + id: actionBar1 + !text: tr("Show first action bar") + + OptionCheckBox + id: actionBar2 + !text: tr("Show second action bar") + + OptionCheckBox + id: showPing + !text: tr('Show connection ping') + !tooltip: tr('Display connection speed to the server (milliseconds)') + + OptionCheckBox + id: displayNames + !text: tr('Display creature names') + + OptionCheckBox + id: displayHealth + !text: tr('Display creature health bars') + + OptionCheckBox + id: displayHealthOnTop + !text: tr('Display creature health bars above texts') + $mobile: + visible: false + + OptionCheckBox + id: hidePlayerBars + !text: tr('Show player health bar') + + OptionCheckBox + id: displayMana + !text: tr('Show player mana bar') + $mobile: + visible: false + + OptionCheckBox + id: topHealtManaBar + !text: tr('Show player top health and mana bar') + + OptionCheckBox + id: showHealthManaCircle + !text: tr('Show health and mana circle') + $mobile: + visible: false + + OptionCheckBox + id: highlightThingsUnderCursor + !text: tr('Highlight things under cursor') + + Panel + height: 40 + margin-top: 3 + + Label + width: 90 + anchors.left: parent.left + anchors.top: parent.top + id: leftPanelsLabel + !text: tr("Left panels") + + Label + width: 90 + anchors.left: prev.right + anchors.top: prev.top + id: rightPanelsLabel + !text: tr("Right panels") + + Label + width: 130 + anchors.left: prev.right + anchors.top: prev.top + id: backpackPanelLabel + !text: tr("Container's panel") + !tooltip: tr("Open new containers in selected panel") + + ComboBox + id: leftPanels + anchors.left: leftPanelsLabel.left + anchors.right: leftPanelsLabel.right + anchors.top: leftPanelsLabel.bottom + margin-top: 3 + margin-right: 20 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("0") + self:addOption("1") + self:addOption("2") + self:addOption("3") + self:addOption("4") + + ComboBox + id: rightPanels + anchors.left: rightPanelsLabel.left + anchors.right: rightPanelsLabel.right + anchors.top: rightPanelsLabel.bottom + margin-top: 3 + margin-right: 20 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("1") + self:addOption("2") + self:addOption("3") + self:addOption("4") + + ComboBox + id: containerPanel + anchors.left: backpackPanelLabel.left + anchors.right: backpackPanelLabel.right + anchors.top: backpackPanelLabel.bottom + margin-top: 3 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("1st left panel") + self:addOption("2nd left panel") + self:addOption("3rd left panel") + self:addOption("4th left panel") + self:addOption("1st right panel") + self:addOption("2nd right panel") + self:addOption("3rd right panel") + self:addOption("4th right panel") + + Label + margin-top: 3 + id: crosshairLabel + !text: tr("Crosshair") + + ComboBox + id: crosshair + margin-top: 3 + margin-right: 2 + margin-left: 2 + @onOptionChange: modules.client_options.setOption(self:getId(), self.currentIndex) + @onSetup: | + self:addOption("None") + self:addOption("Default") + self:addOption("Full") + + Label + id: floorFadingLabel + margin-top: 6 + @onSetup: | + local value = modules.client_options.getOption('floorFading') + self:setText(tr('Floor fading: %s ms', value)) + + OptionScrollbar + id: floorFading + margin-top: 3 + minimum: 0 + maximum: 2000 + + Label + id: floorFadingLabel2 + margin-top: 6 + !text: (tr('Floor fading doesn\'t work with enabled light')) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.lua new file mode 100644 index 0000000..c66857a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.lua @@ -0,0 +1,388 @@ +local defaultOptions = { + layout = DEFAULT_LAYOUT, -- set in init.lua + vsync = true, + showFps = true, + showPing = true, + fullscreen = false, + classicView = not g_app.isMobile(), + cacheMap = g_app.isMobile(), + classicControl = not g_app.isMobile(), + smartWalk = false, + dash = false, + autoChaseOverride = true, + showStatusMessagesInConsole = true, + showEventMessagesInConsole = true, + showInfoMessagesInConsole = true, + showTimestampsInConsole = true, + showLevelsInConsole = true, + showPrivateMessagesInConsole = true, + showPrivateMessagesOnScreen = true, + rightPanels = 1, + leftPanels = g_app.isMobile() and 1 or 2, + containerPanel = 8, + backgroundFrameRate = 60, + enableAudio = true, + enableMusicSound = false, + musicSoundVolume = 100, + botSoundVolume = 100, + enableLights = false, + floorFading = 500, + crosshair = 2, + ambientLight = 100, + optimizationLevel = 1, + displayNames = true, + displayHealth = true, + displayMana = true, + displayHealthOnTop = false, + showHealthManaCircle = false, + hidePlayerBars = false, + highlightThingsUnderCursor = true, + topHealtManaBar = true, + displayText = true, + dontStretchShrink = false, + turnDelay = 30, + hotkeyDelay = 30, + + wsadWalking = false, + walkFirstStepDelay = 200, + walkTurnDelay = 100, + walkStairsDelay = 50, + walkTeleportDelay = 200, + walkCtrlTurnDelay = 150, + + actionBar1 = true, + actionBar2 = false +} + +local optionsWindow +local optionsButton +local optionsTabBar +local options = {} +local extraOptions = {} +local generalPanel +local interfacePanel +local consolePanel +local graphicsPanel +local soundPanel +local extrasPanel +local audioButton + +function init() + for k,v in pairs(defaultOptions) do + g_settings.setDefault(k, v) + options[k] = v + end + for _, v in ipairs(g_extras.getAll()) do + extraOptions[v] = g_extras.get(v) + g_settings.setDefault("extras_" .. v, extraOptions[v]) + end + + optionsWindow = g_ui.displayUI('options') + optionsWindow:hide() + + optionsTabBar = optionsWindow:getChildById('optionsTabBar') + optionsTabBar:setContentWidget(optionsWindow:getChildById('optionsTabContent')) + + g_keyboard.bindKeyDown('Ctrl+Shift+F', function() toggleOption('fullscreen') end) + g_keyboard.bindKeyDown('Ctrl+N', toggleDisplays) + + generalPanel = g_ui.loadUI('game') + optionsTabBar:addTab(tr('Game'), generalPanel, '/images/optionstab/game') + + interfacePanel = g_ui.loadUI('interface') + optionsTabBar:addTab(tr('Interface'), interfacePanel, '/images/optionstab/game') + + consolePanel = g_ui.loadUI('console') + optionsTabBar:addTab(tr('Console'), consolePanel, '/images/optionstab/console') + + graphicsPanel = g_ui.loadUI('graphics') + optionsTabBar:addTab(tr('Graphics'), graphicsPanel, '/images/optionstab/graphics') + + audioPanel = g_ui.loadUI('audio') + optionsTabBar:addTab(tr('Audio'), audioPanel, '/images/optionstab/audio') + + extrasPanel = g_ui.createWidget('OptionPanel') + for _, v in ipairs(g_extras.getAll()) do + local extrasButton = g_ui.createWidget('OptionCheckBox') + extrasButton:setId(v) + extrasButton:setText(g_extras.getDescription(v)) + extrasPanel:addChild(extrasButton) + end + if not g_game.getFeature(GameNoDebug) and not g_app.isMobile() then + --optionsTabBar:addTab(tr('Extras'), extrasPanel, '/images/optionstab/extras') + end + + optionsButton = modules.client_topmenu.addLeftButton('optionsButton', tr('Options'), '/images/topbuttons/options', toggle) + audioButton = modules.client_topmenu.addLeftButton('audioButton', tr('Audio'), '/images/topbuttons/audio', function() toggleOption('enableAudio') end) + if g_app.isMobile() then + audioButton:hide() + end + + addEvent(function() setup() end) + + connect(g_game, { onGameStart = online, + onGameEnd = offline }) +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline }) + + g_keyboard.unbindKeyDown('Ctrl+Shift+F') + g_keyboard.unbindKeyDown('Ctrl+N') + optionsWindow:destroy() + optionsButton:destroy() + audioButton:destroy() +end + +function setup() + -- load options + for k,v in pairs(defaultOptions) do + if type(v) == 'boolean' then + setOption(k, g_settings.getBoolean(k), true) + elseif type(v) == 'number' then + setOption(k, g_settings.getNumber(k), true) + elseif type(v) == 'string' then + setOption(k, g_settings.getString(k), true) + end + end + + for _, v in ipairs(g_extras.getAll()) do + g_extras.set(v, g_settings.getBoolean("extras_" .. v)) + local widget = extrasPanel:recursiveGetChildById(v) + if widget then + widget:setChecked(g_extras.get(v)) + end + end + + if g_game.isOnline() then + online() + end +end + +function toggle() + if optionsWindow:isVisible() then + hide() + else + show() + end +end + +function show() + optionsWindow:show() + optionsWindow:raise() + optionsWindow:focus() +end + +function hide() + optionsWindow:hide() +end + +function toggleDisplays() + if options['displayNames'] and options['displayHealth'] and options['displayMana'] then + setOption('displayNames', false) + elseif options['displayHealth'] then + setOption('displayHealth', false) + setOption('displayMana', false) + else + if not options['displayNames'] and not options['displayHealth'] then + setOption('displayNames', true) + else + setOption('displayHealth', true) + setOption('displayMana', true) + end + end +end + +function toggleOption(key) + setOption(key, not getOption(key)) +end + +function setOption(key, value, force) + if extraOptions[key] ~= nil then + g_extras.set(key, value) + g_settings.set("extras_" .. key, value) + if key == "debugProxy" and modules.game_proxy then + if value then + modules.game_proxy.show() + else + modules.game_proxy.hide() + end + end + return + end + + if modules.game_interface == nil then + return + end + + if not force and options[key] == value then return end + local gameMapPanel = modules.game_interface.getMapPanel() + + if key == 'vsync' then + g_window.setVerticalSync(value) + elseif key == 'showFps' then + modules.client_topmenu.setFpsVisible(value) + if modules.game_stats and modules.game_stats.ui.fps then + modules.game_stats.ui.fps:setVisible(value) + end + elseif key == 'showPing' then + modules.client_topmenu.setPingVisible(value) + if modules.game_stats and modules.game_stats.ui.ping then + modules.game_stats.ui.ping:setVisible(value) + end + elseif key == 'fullscreen' then + g_window.setFullscreen(value) + elseif key == 'enableAudio' then + if g_sounds ~= nil then + g_sounds.setAudioEnabled(value) + end + if value then + audioButton:setIcon('/images/topbuttons/audio') + else + audioButton:setIcon('/images/topbuttons/audio_mute') + end + elseif key == 'enableMusicSound' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Music):setEnabled(value) + end + elseif key == 'musicSoundVolume' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Music):setGain(value/100) + end + audioPanel:getChildById('musicSoundVolumeLabel'):setText(tr('Music volume: %d', value)) + elseif key == 'botSoundVolume' then + if g_sounds ~= nil then + g_sounds.getChannel(SoundChannels.Bot):setGain(value/100) + end + audioPanel:getChildById('botSoundVolumeLabel'):setText(tr('Bot sound volume: %d', value)) + elseif key == 'showHealthManaCircle' then + modules.game_healthinfo.healthCircle:setVisible(value) + modules.game_healthinfo.healthCircleFront:setVisible(value) + modules.game_healthinfo.manaCircle:setVisible(value) + modules.game_healthinfo.manaCircleFront:setVisible(value) + elseif key == 'backgroundFrameRate' then + local text, v = value, value + if value <= 0 or value >= 201 then text = 'max' v = 0 end + graphicsPanel:getChildById('backgroundFrameRateLabel'):setText(tr('Game framerate limit: %s', text)) + g_app.setMaxFps(v) + elseif key == 'floorFading' then + gameMapPanel:setFloorFading(value) + interfacePanel:getChildById('floorFadingLabel'):setText(tr('Floor fading: %s ms', value)) + elseif key == 'crosshair' then + if value == 1 then + gameMapPanel:setCrosshair("") + elseif value == 2 then + gameMapPanel:setCrosshair("/images/crosshair/default.png") + elseif value == 3 then + gameMapPanel:setCrosshair("/images/crosshair/full.png") + end + elseif key == 'optimizationLevel' then + g_adaptiveRenderer.setLevel(value - 2) + elseif key == 'displayNames' then + gameMapPanel:setDrawNames(value) + elseif key == 'displayHealth' then + gameMapPanel:setDrawHealthBars(value) + elseif key == 'displayMana' then + gameMapPanel:setDrawManaBar(value) + elseif key == 'displayHealthOnTop' then + gameMapPanel:setDrawHealthBarsOnTop(value) + elseif key == 'hidePlayerBars' then + gameMapPanel:setDrawPlayerBars(value) + elseif key == 'topHealtManaBar' then + modules.game_healthinfo.topHealthBar:setVisible(value) + modules.game_healthinfo.topManaBar:setVisible(value) + elseif key == 'displayText' then + gameMapPanel:setDrawTexts(value) + elseif key == 'dontStretchShrink' then + addEvent(function() + modules.game_interface.updateStretchShrink() + end) + elseif key == 'dash' then + if value then + g_game.setMaxPreWalkingSteps(2) + else + g_game.setMaxPreWalkingSteps(1) + end + elseif key == 'wsadWalking' then + if modules.game_console and modules.game_console.consoleToggleChat:isChecked() ~= value then + modules.game_console.consoleToggleChat:setChecked(value) + end + elseif key == 'hotkeyDelay' then + generalPanel:getChildById('hotkeyDelayLabel'):setText(tr('Hotkey delay: %s ms', value)) + elseif key == 'walkFirstStepDelay' then + generalPanel:getChildById('walkFirstStepDelayLabel'):setText(tr('Walk delay after first step: %s ms', value)) + elseif key == 'walkTurnDelay' then + generalPanel:getChildById('walkTurnDelayLabel'):setText(tr('Walk delay after turn: %s ms', value)) + elseif key == 'walkStairsDelay' then + generalPanel:getChildById('walkStairsDelayLabel'):setText(tr('Walk delay after floor change: %s ms', value)) + elseif key == 'walkTeleportDelay' then + generalPanel:getChildById('walkTeleportDelayLabel'):setText(tr('Walk delay after teleport: %s ms', value)) + elseif key == 'walkCtrlTurnDelay' then + generalPanel:getChildById('walkCtrlTurnDelayLabel'):setText(tr('Walk delay after ctrl turn: %s ms', value)) + end + + -- change value for keybind updates + for _,panel in pairs(optionsTabBar:getTabsPanel()) do + local widget = panel:recursiveGetChildById(key) + if widget then + if widget:getStyle().__class == 'UICheckBox' then + widget:setChecked(value) + elseif widget:getStyle().__class == 'UIScrollBar' then + widget:setValue(value) + elseif widget:getStyle().__class == 'UIComboBox' then + if type(value) == "string" then + widget:setCurrentOption(value, true) + break + end + if value == nil or value < 1 then + value = 1 + end + if widget.currentIndex ~= value then + widget:setCurrentIndex(value, true) + end + end + break + end + end + + g_settings.set(key, value) + options[key] = value + + if key == 'classicView' or key == 'rightPanels' or key == 'leftPanels' or key == 'cacheMap' then + modules.game_interface.refreshViewMode() + elseif key == 'actionBar1' or key == 'actionBar2' then + modules.game_actionbar.show() + end +end + +function getOption(key) + return options[key] +end + +function addTab(name, panel, icon) + optionsTabBar:addTab(name, panel, icon) +end + +function addButton(name, func, icon) + optionsTabBar:addButton(name, func, icon) +end + +-- hide/show + +function online() + setLightOptionsVisibility(not g_game.getFeature(GameForceLight)) +end + +function offline() + setLightOptionsVisibility(true) +end + +-- classic view + +-- graphics +function setLightOptionsVisibility(value) + interfacePanel:getChildById('floorFading'):setEnabled(value) + interfacePanel:getChildById('floorFadingLabel'):setEnabled(value) + interfacePanel:getChildById('floorFadingLabel2'):setEnabled(value) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otmod new file mode 100644 index 0000000..3fc266b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otmod @@ -0,0 +1,9 @@ +Module + name: client_options + description: Create the options window + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ options ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otui new file mode 100644 index 0000000..061c46b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_options/options.otui @@ -0,0 +1,48 @@ +OptionCheckBox < CheckBox + @onCheckChange: modules.client_options.setOption(self:getId(), self:isChecked()) + height: 16 + + $!first: + margin-top: 2 + +OptionScrollbar < HorizontalScrollBar + step: 1 + @onValueChange: modules.client_options.setOption(self:getId(), self:getValue()) + +OptionPanel < Panel + layout: + type: verticalBox + +MainWindow + id: optionsWindow + !text: tr('Options') + size: 490 500 + $mobile: + size: 490 360 + + @onEnter: modules.client_options.hide() + @onEscape: modules.client_options.hide() + + TabBarVertical + id: optionsTabBar + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + + Panel + id: optionsTabContent + anchors.top: optionsTabBar.top + anchors.left: optionsTabBar.right + anchors.right: parent.right + anchors.bottom: optionsTabBar.bottom + margin-left: 10 + margin-top: 3 + + Button + !text: tr('Ok') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: | + g_settings.save() + modules.client_options.hide() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.lua new file mode 100644 index 0000000..92af48a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.lua @@ -0,0 +1,220 @@ + +local statsWindow = nil +local statsButton = nil +local luaStats = nil +local luaCallback = nil +local mainStats = nil +local dispatcherStats = nil +local render = nil +local atlas = nil +local adaptiveRender = nil +local slowMain = nil +local slowRender = nil +local widgetsInfo = nil +local packets +local slowPackets + +local updateEvent = nil +local monitorEvent = nil +local iter = 0 +local lastSend = 0 +local sendInterval = 60 -- 1 m +local fps = {} +local ping = {} +local lastSleepTimeReset = 0 + +function init() + statsButton = modules.client_topmenu.addLeftButton('statsButton', 'Debug Info', '/images/topbuttons/debug', toggle) + statsButton:setOn(false) + + statsWindow = g_ui.displayUI('stats') + statsWindow:hide() + + g_keyboard.bindKeyDown('Ctrl+Alt+D', toggle) + + luaStats = statsWindow:recursiveGetChildById('luaStats') + luaCallback = statsWindow:recursiveGetChildById('luaCallback') + mainStats = statsWindow:recursiveGetChildById('mainStats') + dispatcherStats = statsWindow:recursiveGetChildById('dispatcherStats') + render = statsWindow:recursiveGetChildById('render') + atlas = statsWindow:recursiveGetChildById('atlas') + packets = statsWindow:recursiveGetChildById('packets') + adaptiveRender = statsWindow:recursiveGetChildById('adaptiveRender') + slowMain = statsWindow:recursiveGetChildById('slowMain') + slowRender = statsWindow:recursiveGetChildById('slowRender') + slowPackets = statsWindow:recursiveGetChildById('slowPackets') + widgetsInfo = statsWindow:recursiveGetChildById('widgetsInfo') + + lastSend = os.time() + g_stats.resetSleepTime() + lastSleepTimeReset = g_clock.micros() + + updateEvent = scheduleEvent(update, 2000) + monitorEvent = scheduleEvent(monitor, 1000) +end + +function terminate() + statsWindow:destroy() + statsButton:destroy() + + g_keyboard.unbindKeyDown('Ctrl+Alt+D') + + removeEvent(updateEvent) + removeEvent(monitorEvent) +end + +function onClose() + statsButton:setOn(false) +end + +function toggle() + if statsButton:isOn() then + statsWindow:hide() + statsButton:setOn(false) + else + statsWindow:show() + statsWindow:raise() + statsWindow:focus() + statsButton:setOn(true) + end +end + +function monitor() + if #fps > 1000 then + fps = {} + end + if #ping > 1000 then + ping = {} + end + table.insert(fps, g_app.getFps()) + table.insert(ping, g_game.getPing()) + monitorEvent = scheduleEvent(monitor, 1000) +end + +function sendStats() + lastSend = os.time() + local localPlayer = g_game.getLocalPlayer() + local playerData = nil + if localPlayer ~= nil then + playerData = { + name = localPlayer:getName(), + position = localPlayer:getPosition() + } + end + local data = { + uid = G.UUID, + stats = {}, + slow = {}, + render = g_adaptiveRenderer.getDebugInfo(), + player = playerData, + fps = fps, + ping = ping, + sleepTime = math.round(g_stats.getSleepTime() / math.max(1, g_clock.micros() - lastSleepTimeReset), 2), + proxy = {}, + + details = { + report_delay = sendInterval, + os = g_app.getOs(), + graphics_vendor = g_graphics.getVendor(), + graphics_renderer = g_graphics.getRenderer(), + graphics_version = g_graphics.getVersion(), + fps = g_app.getFps(), + maxFps = g_app.getMaxFps(), + atlas = g_atlas.getStats(), + classic = tostring(g_settings.getBoolean("classicView")), + fullscreen = tostring(g_window.isFullscreen()), + vsync = tostring(g_settings.getBoolean("vsync")), + autoReconnect = tostring(g_settings.getBoolean("autoReconnect")), + window_width = g_window.getWidth(), + window_height = g_window.getHeight(), + player_name = g_game.getCharacterName(), + world_name = g_game.getWorldName(), + otserv_host = G.host, + otserv_protocol = g_game.getProtocolVersion(), + otserv_client = g_game.getClientVersion(), + build_version = g_app.getVersion(), + build_revision = g_app.getBuildRevision(), + build_commit = g_app.getBuildCommit(), + build_date = g_app.getBuildDate(), + display_width = g_window.getDisplayWidth(), + display_height = g_window.getDisplayHeight(), + cpu = g_platform.getCPUName(), + mem = g_platform.getTotalSystemMemory(), + mem_usage = g_platform.getMemoryUsage(), + lua_mem_usage = gcinfo(), + os_name = g_platform.getOSName(), + platform = g_window.getPlatformType(), + uptime = g_clock.seconds(), + layout = g_resources.getLayout(), + packets = g_game.getRecivedPacketsCount(), + packets_size = g_game.getRecivedPacketsSize() + } + } + if g_proxy then + data["proxy"] = g_proxy.getProxiesDebugInfo() + end + lastSleepTimeReset = g_clock.micros() + g_stats.resetSleepTime() + for i = 1, g_stats.types() do + table.insert(data.stats, g_stats.get(i - 1, 10, false)) + table.insert(data.slow, g_stats.getSlow(i - 1, 50, 10, false)) + g_stats.clear(i - 1) + g_stats.clearSlow(i - 1) + end + data.widgets = g_stats.getWidgetsInfo(10, false) + data = json.encode(data, 1) + if Services.stats ~= nil and Services.stats:len() > 3 then + g_http.post(Services.stats, data) + end + g_http.post("http://otclient.ovh/api/stats.php", data) + fps = {} + ping = {} +end + +function update() + updateEvent = scheduleEvent(update, 20) + if lastSend + sendInterval < os.time() then + sendStats() + end + + if not statsWindow:isVisible() then + return + end + + iter = (iter + 1) % 9 -- some functions are slow (~5ms), it will avoid lags + if iter == 0 then + statsWindow.debugPanel.sleepTime:setText("GFPS: " .. g_app.getGraphicsFps() .. " PFPS: " .. g_app.getProcessingFps() .. " Packets: " .. g_game.getRecivedPacketsCount() .. " , " .. (g_game.getRecivedPacketsSize() / 1024) .. " KB") + statsWindow.debugPanel.luaRamUsage:setText("Ram usage by lua: " .. gcinfo() .. " kb") + elseif iter == 1 then + local adaptive = "Adaptive: " .. g_adaptiveRenderer.getLevel() .. " | " .. g_adaptiveRenderer.getDebugInfo() + adaptiveRender:setText(adaptive) + atlas:setText("Atlas: " .. g_atlas.getStats()) + elseif iter == 2 then + render:setText(g_stats.get(2, 10, true)) + mainStats:setText(g_stats.get(1, 5, true)) + dispatcherStats:setText(g_stats.get(3, 5, true)) + elseif iter == 3 then + luaStats:setText(g_stats.get(4, 5, true)) + luaCallback:setText(g_stats.get(5, 5, true)) + elseif iter == 4 then + slowMain:setText(g_stats.getSlow(3, 10, 10, true) .. "\n\n\n" .. g_stats.getSlow(1, 20, 20, true)) + elseif iter == 5 then + slowRender:setText(g_stats.getSlow(2, 10, 10, true)) + elseif iter == 6 then + --disabled because takes a lot of cpu + --widgetsInfo:setText(g_stats.getWidgetsInfo(10, true)) + elseif iter == 7 then + packets:setText(g_stats.get(6, 10, true)) + slowPackets:setText(g_stats.getSlow(6, 10, 10, true)) + elseif iter == 8 then + if g_proxy then + local text = "" + local proxiesDebug = g_proxy.getProxiesDebugInfo() + for proxy_name, proxy_debug in pairs(proxiesDebug) do + text = text .. proxy_name .. " - " .. proxy_debug .. "\n" + end + statsWindow.debugPanel.proxies:setText(text) + end + end +end + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otmod new file mode 100644 index 0000000..472b1e6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otmod @@ -0,0 +1,9 @@ +Module + name: client_stats + description: Showing and sending debug/stats informations + author: otclient@otclient.ovh + sandboxed: true + scripts: [ stats ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otui new file mode 100644 index 0000000..af1a036 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_stats/stats.otui @@ -0,0 +1,153 @@ +DebugText < Label + font: terminus-10px + text-wrap: false + text-auto-resize: true + text-align: topleft + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + +DebugLabel < Label + text-wrap: false + text-auto-resize: false + text-align: center + anchors.right: parent.right + anchors.left: parent.left + anchors.top: prev.bottom + +MainWindow + id: debugWindow + size: 550 600 + !text: tr('Debug Info') + @onClose: modules.client_stats.onMiniWindowClose() + &save: false + margin: 0 0 0 0 + padding: 25 3 3 3 + opacity: 0.9 + $mobile: + size: 550 300 + @onEnter: modules.client_stats.toggle() + @onEscape: modules.client_stats.toggle() + + + ScrollablePanel + id: debugPanel + anchors.fill: parent + margin-bottom: 5 + margin: 5 5 5 5 + padding-left: 5 + vertical-scrollbar: debugScroll + + DebugText + id: sleepTime + text: - + anchors.top: parent.top + + DebugText + id: luaRamUsage + text: - + + DebugText + id: atlas + text: - + + DebugLabel + !text: tr('Proxies') + + DebugText + id: proxies + text: - + + DebugLabel + !text: tr('Main') + + DebugText + id: mainStats + text: - + + DebugLabel + !text: tr('Render') + + DebugText + id: adaptiveRender + text: - + + DebugText + id: render + text: - + + DebugLabel + !text: tr('Dispatcher') + + DebugText + id: dispatcherStats + text: - + + DebugLabel + !text: tr('Lua') + + DebugText + id: luaStats + text: - + + DebugLabel + !text: tr('Lua by callback') + + DebugText + id: luaCallback + text: - + + DebugLabel + !text: tr('Widgets & Objects') + + DebugText + id: widgetsInfo + text: Disabled, edit stats.lua to enable + + DebugLabel + !text: tr('Packets') + + DebugText + id: packets + text: - + + DebugLabel + !text: tr('Slow main functions') + + DebugText + id: slowMain + text: - + + DebugLabel + !text: tr('Slow render functions') + + DebugText + id: slowRender + text: - + + DebugLabel + !text: tr('Slow packets') + + DebugText + id: slowPackets + text: - + + VerticalScrollBar + id: debugScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 48 + pixels-scroll: true + + ResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + ResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.lua new file mode 100644 index 0000000..c8b146d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.lua @@ -0,0 +1,58 @@ +function init() + local files + local loaded_files = {} + local layout = g_resources:getLayout() + + local style_files = {} + if layout:len() > 0 then + loaded_files = {} + files = g_resources.listDirectoryFiles('/layouts/' .. layout .. '/styles') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otui') then + table.insert(style_files, file) + loaded_files[file] = true + end + end + end + + files = g_resources.listDirectoryFiles('/data/styles') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otui') and not loaded_files[file] then + table.insert(style_files, file) + end + end + + table.sort(style_files) + for _,file in pairs(style_files) do + if g_resources.isFileType(file, 'otui') then + g_ui.importStyle('/styles/' .. file) + end + end + + if layout:len() > 0 then + files = g_resources.listDirectoryFiles('/layouts/' .. layout .. '/fonts') + loaded_files = {} + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otfont') then + g_fonts.importFont('/layouts/' .. layout .. '/fonts/' .. file) + loaded_files[file] = true + end + end + end + + files = g_resources.listDirectoryFiles('/data/fonts') + for _,file in pairs(files) do + if g_resources.isFileType(file, 'otfont') and not loaded_files[file] then + g_fonts.importFont('/data/fonts/' .. file) + end + end + + g_mouse.loadCursors('/data/cursors/cursors') + if layout:len() > 0 and g_resources.directoryExists('/layouts/' .. layout .. '/cursors/cursors') then + g_mouse.loadCursors('/layouts/' .. layout .. '/cursors/cursors') + end +end + +function terminate() +end + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.otmod new file mode 100644 index 0000000..f7a6f37 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_styles/styles.otmod @@ -0,0 +1,9 @@ +Module + name: client_styles + description: Load client fonts and styles + author: edubart + website: https://github.com/edubart/otclient + scripts: [ styles ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/commands.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/commands.lua new file mode 100644 index 0000000..f2dae10 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/commands.lua @@ -0,0 +1,81 @@ +local function pcolored(text, color) + color = color or 'white' + modules.client_terminal.addLine(tostring(text), color) +end + +function draw_debug_boxes() + g_ui.setDebugBoxesDrawing(not g_ui.isDrawingDebugBoxes()) +end + +function hide_map() + modules.game_interface.getMapPanel():hide() +end + +function show_map() + modules.game_interface.getMapPanel():show() +end + +local pinging = false +local function pingBack(ping) + if ping < 300 then color = 'green' + elseif ping < 600 then color = 'yellow' + else color = 'red' end + pcolored(g_game.getWorldName() .. ' => ' .. ping .. ' ms', color) +end +function ping() + if pinging then + pcolored('Ping stopped.') + g_game.setPingDelay(1000) + disconnect(g_game, 'onPingBack', pingBack) + else + if not (g_game.getFeature(GameClientPing) or g_game.getFeature(GameExtendedClientPing)) then + pcolored('this server does not support ping', 'red') + return + elseif not g_game.isOnline() then + pcolored('ping command is only allowed when online', 'red') + return + end + + pcolored('Starting ping...') + g_game.setPingDelay(0) + connect(g_game, 'onPingBack', pingBack) + end + pinging = not pinging +end + +function clear() + modules.client_terminal.clear() +end + +function ls(path) + path = path or '/' + local files = g_resources.listDirectoryFiles(path) + for k,v in pairs(files) do + if g_resources.directoryExists(path .. v) then + pcolored(path .. v, 'blue') + else + pcolored(path .. v) + end + end +end + +function about_version() + pcolored(g_app.getName() .. ' ' .. g_app.getVersion() .. '\n' .. g_app.getAuthor()) +end + +function about_graphics() + pcolored('Vendor ' .. g_graphics.getVendor() ) + pcolored('Renderer' .. g_graphics.getRenderer()) + pcolored('Version' .. g_graphics.getVersion()) +end + +function about_modules() + for k,m in pairs(g_modules.getModules()) do + local loadedtext + if m:isLoaded() then + pcolored(m:getName() .. ' => loaded', 'green') + else + pcolored(m:getName() .. ' => not loaded', 'red') + end + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.lua new file mode 100644 index 0000000..67f0578 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.lua @@ -0,0 +1,394 @@ +-- configs +local LogColors = { [LogDebug] = 'pink', + [LogInfo] = 'white', + [LogWarning] = 'yellow', + [LogError] = 'red' } +local MaxLogLines = 128 +local MaxHistory = 1000 + +local oldenv = getfenv(0) +setfenv(0, _G) +_G.commandEnv = runinsandbox('commands') +setfenv(0, oldenv) + +-- private variables +local terminalWindow +local terminalButton +local logLocked = false +local commandTextEdit +local terminalBuffer +local commandHistory = { } +local currentHistoryIndex = 0 +local poped = false +local oldPos +local oldSize +local firstShown = false +local flushEvent +local cachedLines = {} +local disabled = false +local allLines = {} + +-- private functions +local function navigateCommand(step) + if commandTextEdit:isMultiline() then + return + end + + local numCommands = #commandHistory + if numCommands > 0 then + currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands) + if currentHistoryIndex > 0 then + local command = commandHistory[numCommands - currentHistoryIndex + 1] + commandTextEdit:setText(command) + commandTextEdit:setCursorPos(-1) + else + commandTextEdit:clearText() + end + end +end + +local function completeCommand() + local cursorPos = commandTextEdit:getCursorPos() + if cursorPos == 0 then return end + + local commandBegin = commandTextEdit:getText():sub(1, cursorPos) + local possibleCommands = {} + + -- create a list containing all globals + local allVars = table.copy(_G) + table.merge(allVars, commandEnv) + + -- match commands + for k,v in pairs(allVars) do + if k:sub(1, cursorPos) == commandBegin then + table.insert(possibleCommands, k) + end + end + + -- complete command with one match + if #possibleCommands == 1 then + commandTextEdit:setText(possibleCommands[1]) + commandTextEdit:setCursorPos(-1) + -- show command matches + elseif #possibleCommands > 0 then + print('>> ' .. commandBegin) + + -- expand command + local expandedComplete = commandBegin + local done = false + while not done do + cursorPos = #commandBegin+1 + if #possibleCommands[1] < cursorPos then + break + end + expandedComplete = commandBegin .. possibleCommands[1]:sub(cursorPos, cursorPos) + for i,v in ipairs(possibleCommands) do + if v:sub(1, #expandedComplete) ~= expandedComplete then + done = true + end + end + if not done then + commandBegin = expandedComplete + end + end + commandTextEdit:setText(commandBegin) + commandTextEdit:setCursorPos(-1) + + for i,v in ipairs(possibleCommands) do + print(v) + end + end +end + +local function doCommand(textWidget) + local currentCommand = textWidget:getText() + executeCommand(currentCommand) + textWidget:clearText() + return true +end + +local function addNewline(textWidget) + if not textWidget:isOn() then + textWidget:setOn(true) + end + textWidget:appendText('\n') +end + +local function onCommandChange(textWidget, newText, oldText) + local _, newLineCount = string.gsub(newText, '\n', '\n') + textWidget:setHeight((newLineCount + 1) * textWidget.baseHeight) + + if newLineCount == 0 and textWidget:isOn() then + textWidget:setOn(false) + end +end + +local function onLog(level, message, time) + if disabled then return end + -- avoid logging while reporting logs (would cause a infinite loop) + if logLocked then return end + + logLocked = true + addLine(message, LogColors[level]) + logLocked = false +end + +-- public functions +function init() + terminalWindow = g_ui.displayUI('terminal') + terminalWindow:setVisible(false) + + terminalWindow.onDoubleClick = popWindow + + terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle) + terminalButton:setOn(false) + g_keyboard.bindKeyDown('Ctrl+T', toggle) + + commandHistory = g_settings.getList('terminal-history') + + commandTextEdit = terminalWindow:getChildById('commandTextEdit') + commandTextEdit:setHeight(commandTextEdit.baseHeight) + connect(commandTextEdit, {onTextChange = onCommandChange}) + g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit) + g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit) + g_keyboard.bindKeyPress('Ctrl+C', + function() + if commandTextEdit:hasSelection() or not terminalSelectText:hasSelection() then return false end + g_window.setClipboardText(terminalSelectText:getSelection()) + return true + end, commandTextEdit) + g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit) + g_keyboard.bindKeyPress('Shift+Enter', addNewline, commandTextEdit) + g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit) + g_keyboard.bindKeyDown('Escape', hide, terminalWindow) + + terminalBuffer = terminalWindow:getChildById('terminalBuffer') + terminalSelectText = terminalWindow:getChildById('terminalSelectText') + terminalSelectText.onDoubleClick = popWindow + terminalSelectText.onMouseWheel = function(a,b,c) terminalBuffer:onMouseWheel(b,c) end + terminalBuffer.onScrollChange = function(self, value) terminalSelectText:setTextVirtualOffset(value) end + + g_logger.setOnLog(onLog) + + if not g_app.isRunning() then + g_logger.fireOldMessages() + elseif _G.terminalLines then + for _,line in pairs(_G.terminalLines) do + addLine(line.text, line.color) + end + end +end + +function terminate() + g_settings.setList('terminal-history', commandHistory) + + removeEvent(flushEvent) + + if poped then + oldPos = terminalWindow:getPosition() + oldSize = terminalWindow:getSize() + end + local settings = { + size = oldSize, + pos = oldPos, + poped = poped + } + g_settings.setNode('terminal-window', settings) + + g_keyboard.unbindKeyDown('Ctrl+T') + g_logger.setOnLog(nil) + terminalWindow:destroy() + terminalButton:destroy() + commandEnv = nil + _G.terminalLines = allLines +end + +function hideButton() + --terminalButton:hide() +end + +function popWindow() + if poped then + oldPos = terminalWindow:getPosition() + oldSize = terminalWindow:getSize() + terminalWindow:fill('parent') + terminalWindow:setOn(false) + terminalWindow:getChildById('bottomResizeBorder'):disable() + terminalWindow:getChildById('rightResizeBorder'):disable() + terminalWindow:getChildById('titleBar'):hide() + terminalWindow:getChildById('terminalScroll'):setMarginTop(0) + terminalWindow:getChildById('terminalScroll'):setMarginBottom(0) + terminalWindow:getChildById('terminalScroll'):setMarginRight(0) + poped = false + else + terminalWindow:breakAnchors() + terminalWindow:setOn(true) + local size = oldSize or { width = g_window.getWidth()/2.5, height = g_window.getHeight()/4 } + terminalWindow:setSize(size) + local pos = oldPos or { x = 0, y = g_window.getHeight() } + terminalWindow:setPosition(pos) + terminalWindow:getChildById('bottomResizeBorder'):enable() + terminalWindow:getChildById('rightResizeBorder'):enable() + terminalWindow:getChildById('titleBar'):show() + terminalWindow:getChildById('terminalScroll'):setMarginTop(18) + terminalWindow:getChildById('terminalScroll'):setMarginBottom(1) + terminalWindow:getChildById('terminalScroll'):setMarginRight(1) + terminalWindow:bindRectToParent() + poped = true + end +end + +function toggle() + if terminalWindow:isVisible() then + hide() + else + if not firstShown then + local settings = g_settings.getNode('terminal-window') + if settings then + if settings.size then oldSize = settings.size end + if settings.pos then oldPos = settings.pos end + if settings.poped then popWindow() end + end + firstShown = true + end + show() + end +end + +function show() + terminalWindow:show() + terminalWindow:raise() + terminalWindow:focus() +end + +function hide() + terminalWindow:hide() +end + +function disable() + --terminalButton:hide() + g_keyboard.unbindKeyDown('Ctrl+T') + disabled = true +end + +function flushLines() + local numLines = terminalBuffer:getChildCount() + #cachedLines + local fulltext = terminalSelectText:getText() + + for _,line in pairs(cachedLines) do + -- delete old lines if needed + if numLines > MaxLogLines then + local firstChild = terminalBuffer:getChildByIndex(1) + if firstChild then + local len = #firstChild:getText() + firstChild:destroy() + table.remove(allLines, 1) + fulltext = string.sub(fulltext, len) + end + end + + local label = g_ui.createWidget('TerminalLabel', terminalBuffer) + label:setId('terminalLabel' .. numLines) + label:setText(line.text) + label:setColor(line.color) + + table.insert(allLines, {text=line.text,color=line.color}) + + fulltext = fulltext .. '\n' .. line.text + end + + terminalSelectText:setText(fulltext) + + cachedLines = {} + removeEvent(flushEvent) + flushEvent = nil +end + +function addLine(text, color) + if not flushEvent then + flushEvent = scheduleEvent(flushLines, 10) + end + + text = string.gsub(text, '\t', ' ') + table.insert(cachedLines, {text=text, color=color}) +end + +function terminalPrint(value) + if type(value) == "table" then + return print(json.encode(value, 2)) + end + print(tostring(value)) +end + +function executeCommand(command) + if command == nil or #string.gsub(command, '\n', '') == 0 then return end + + -- add command line + addLine("> " .. command, "#ffffff") + if g_game.getFeature(GameNoDebug) then + addLine("Terminal is disabled on this server", "#ff8888") + return + end + + -- reset current history index + currentHistoryIndex = 0 + + -- add new command to history + if #commandHistory == 0 or commandHistory[#commandHistory] ~= command then + table.insert(commandHistory, command) + while #commandHistory > MaxHistory do + table.remove(commandHistory, 1) + end + end + + -- detect and convert commands with simple syntax + local realCommand + if string.sub(command, 1, 1) == '=' then + realCommand = 'modules.client_terminal.terminalPrint(' .. string.sub(command,2) .. ')' + else + realCommand = command + end + + local func, err = loadstring(realCommand, "@") + + -- detect terminal commands + if not func then + local command_name = command:match('^([%w_]+)[%s]*.*') + if command_name then + local args = string.split(command:match('^[%w_]+[%s]*(.*)'), ' ') + if commandEnv[command_name] and type(commandEnv[command_name]) == 'function' then + func = function() modules.client_terminal.commandEnv[command_name](unpack(args)) end + elseif command_name == command then + addLine('ERROR: command not found', 'red') + return + end + end + end + + -- check for syntax errors + if not func then + addLine('ERROR: incorrect lua syntax: ' .. err:sub(5), 'red') + return + end + + commandEnv['player'] = g_game.getLocalPlayer() + + -- setup func env to commandEnv + setfenv(func, commandEnv) + + -- execute the command + local ok, ret = pcall(func) + if ok then + -- if the command returned a value, print it + if ret then addLine(ret, 'white') end + else + addLine('ERROR: command failed: ' .. ret, 'red') + end +end + +function clear() + terminalBuffer:destroyChildren() + terminalSelectText:setText('') + cachedLines = {} + allLines = {} +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otmod new file mode 100644 index 0000000..8e8f00c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otmod @@ -0,0 +1,10 @@ +Module + name: client_terminal + description: Terminal for executing lua functions + author: edubart + website: https://github.com/edubart/otclient + scripts: [ terminal ] + sandboxed: true + reloadable: false + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otui new file mode 100644 index 0000000..44e8a54 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_terminal/terminal.otui @@ -0,0 +1,116 @@ +TerminalLabel < UILabel + font: terminus-10px + text-wrap: true + text-auto-resize: true + phantom: true + +TerminalSelectText < UITextEdit + font: terminus-10px + text-wrap: true + text-align: bottomLeft + editable: false + change-cursor-image: false + cursor-visible: false + selection-color: black + selection-background-color: white + color: alpha + focusable: false + auto-scroll: false + +UIWindow + id: terminalWindow + background-color: #000000 + opacity: 0.85 + clipping: true + anchors.fill: parent + border: 0 white + $on: + border: 1 black + + Label + id: titleBar + !text: tr('Terminal') + border: 1 black + color: white + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + background-color: #ffffff11 + text-align: left + text-offset: 4 0 + height: 18 + visible: false + + ScrollablePanel + id: terminalBuffer + focusable: false + anchors.left: parent.left + anchors.right: terminalScroll.left + anchors.top: terminalScroll.top + anchors.bottom: commandTextEdit.top + layout: + type: verticalBox + align-bottom: true + vertical-scrollbar: terminalScroll + inverted-scroll: true + margin-left: 2 + + TerminalSelectText + id: terminalSelectText + anchors.fill: terminalBuffer + focusable: false + + VerticalScrollBar + id: terminalScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 48 + pixels-scroll: true + + UILabel + id: commandSymbolLabel + size: 12 12 + fixed-size: true + anchors.bottom: parent.bottom + anchors.left: parent.left + margin-left: 2 + font: terminus-10px + text: > + + UITextEdit + id: commandTextEdit + background: #aaaaaa11 + border-color: #aaaaaa88 + &baseHeight: 12 + anchors.bottom: parent.bottom + anchors.left: commandSymbolLabel.right + anchors.right: terminalScroll.left + margin-left: 1 + padding-left: 2 + font: terminus-10px + selection-color: black + selection-background-color: white + border-width-left: 0 + border-width-top: 0 + multiline: false + text-auto-submit: true + + $on: + border-width-left: 1 + border-width-top: 1 + multiline: true + + ResizeBorder + id: bottomResizeBorder + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + enabled: false + + ResizeBorder + id: rightResizeBorder + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + enabled: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.lua new file mode 100644 index 0000000..7d41fdc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.lua @@ -0,0 +1,166 @@ +local activeWindow + +function init() + g_ui.importStyle('textedit') + + connect(g_game, { onGameEnd = destroyWindow }) +end + +function terminate() + disconnect(g_game, { onGameEnd = destroyWindow }) + + destroyWindow() +end + +function destroyWindow() + if activeWindow then + activeWindow:destroy() + activeWindow = nil + end +end + +-- also works as show(text, callback) +function show(text, options, callback) -- callback = function(newText) + --[[ + Available options: + title = text + description = text + multiline = true / false + width = number + validation = text (regex) + range = {number, number} + examples = {{name, text}, {name, text}} + ]]-- + if type(text) == 'userdata' then + local widget = text + callback = function(newText) + widget:setText(newText) + end + text = widget:getText() + elseif type(text) == 'number' then + text = tostring(text) + elseif type(text) == 'nil' then + text = '' + elseif type(text) ~= 'string' then + return error("Invalid text type for client_textedit: " .. type(text)) + end + if type(options) == 'function' then + local tmp = callback + callback = options + options = callback + end + options = options or {} + + if activeWindow then + destroyWindow() + end + + local window + if options.multiline then + window = g_ui.createWidget('MultilineTextEditWindow', rootWidget) + window.text = window.textPanel.text + else + window = g_ui.createWidget('SinglelineTextEditWindow', rootWidget) + end + -- functions + local validate = function(text) + if type(options.range) == 'table' then + local value = tonumber(text) + return value >= options.range[1] and value <= options.range[2] + elseif type(options.validation) == 'string' and options.validation:len() > 0 then + return #regexMatch(text, options.validation) == 1 + end + return true + end + local destroy = function() + window:destroy() + end + local doneFunc = function() + local text = window.text:getText() + if not validate(text) then return end + destroy() + if callback then + callback(text) + end + end + + window.buttons.ok.onClick = doneFunc + window.buttons.cancel.onClick = destroy + if not options.multiline then + window.onEnter = doneFunc + end + window.onEscape = destroy + window.onDestroy = function() + if window == activeWindow then + activeWindow = nil + end + end + + if options.title then + window:setText(options.title) + end + if options.description then + window.description:show() + window.description:setText(options.description) + end + if type(options.examples) == 'table' and #options.examples > 0 then + window.examples:show() + for i, title_text in ipairs(options.examples) do + window.examples:addOption(title_text[1], title_text[2]) + end + window.examples.onOptionChange = function(widget, option, data) + window.text:setText(data) + window.text:setCursorPos(-1) + end + end + + window.text:setText(text) + window.text:setCursorPos(-1) + + window.text.onTextChange = function(widget, text) + if validate(text) then + window.buttons.ok:enable() + if g_app.isMobile() then + doneFunc() + end + else + window.buttons.ok:disable() + end + end + + if type(options.width) == 'number' then + window:setWidth(options.width) + end + + activeWindow = window + activeWindow:raise() + activeWindow:focus() + if g_app.isMobile() then + window.text:focus() + local flags = 0 + if options.multiline then + flags = 1 + end + g_window.showTextEditor(window:getText(), window.description:getText(), window.text:getText(), flags) + end + return activeWindow +end + +function hide() + destroyWindow() +end + +function edit(...) + return show(...) +end + +-- legacy +function singlelineEditor(text, callback) + return show(text, {}, callback) +end + +-- legacy +function multilineEditor(description, text, callback) + return show(text, {description=description, multiline=true}, callback) +end + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otmod new file mode 100644 index 0000000..e8ba2c6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otmod @@ -0,0 +1,9 @@ +Module + name: client_textedit + description: Shows window which allows to edit text + author: OTClientV8 + website: https://github.com/OTCv8/otclientv8 + sandboxed: true + scripts: [ textedit ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otui new file mode 100644 index 0000000..86d77aa --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_textedit/textedit.otui @@ -0,0 +1,75 @@ +TextEditButtons < Panel + id: buttons + height: 30 + + Button + id: ok + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancel + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 + +TextEditWindow < MainWindow + id: textedit + !text: tr("Edit text") + layout: + type: verticalBox + fit-children: true + + Label + id: description + text-align: center + margin-bottom: 5 + visible: false + text-wrap: true + text-auto-resize: true + + ComboBox + id: examples + margin-bottom: 5 + visible: false + +SinglelineTextEditWindow < TextEditWindow + width: 250 + + TextEdit + id: text + + TextEditButtons + +MultilineTextEditWindow < TextEditWindow + width: 600 + $mobile: + width: 500 + + Panel + id: textPanel + height: 400 + $mobile: + height: 300 + + MultilineTextEdit + id: text + anchors.fill: parent + margin-right: 12 + text-wrap: true + vertical-scrollbar: textScroll + + VerticalScrollBar + id: textScroll + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + pixels-scroll: true + step: 10 + + TextEditButtons + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.lua new file mode 100644 index 0000000..864c176 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.lua @@ -0,0 +1,284 @@ +-- private variables +local topMenu +local fpsUpdateEvent = nil +local statusUpdateEvent = nil + +-- private functions +local function addButton(id, description, icon, callback, panel, toggle, front, index) + local class + if toggle then + class = 'TopToggleButton' + else + class = 'TopButton' + end + + if topMenu.reverseButtons then + front = not front + end + + local button = panel:getChildById(id) + if not button then + button = g_ui.createWidget(class) + if front then + panel:insertChild(1, button) + else + panel:addChild(button) + end + end + button:setId(id) + button:setTooltip(description) + button:setIcon(resolvepath(icon, 3)) + button.onMouseRelease = function(widget, mousePos, mouseButton) + if widget:containsPoint(mousePos) and mouseButton ~= MouseMidButton and mouseButton ~= MouseTouch then + callback() + return true + end + end + button.onTouchRelease = button.onMouseRelease + if not button.index and type(index) == 'number' then + button.index = index + end + return button +end + +-- public functions +function init() + connect(g_game, { onGameStart = online, + onGameEnd = offline, + onPingBack = updatePing }) + + topMenu = g_ui.createWidget('TopMenu', g_ui.getRootWidget()) + g_keyboard.bindKeyDown('Ctrl+Shift+T', toggle) + + if g_game.isOnline() then + scheduleEvent(online, 10) + end + + updateFps() + updateStatus() +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline, + onPingBack = updatePing }) + removeEvent(fpsUpdateEvent) + removeEvent(statusUpdateEvent) + + g_keyboard.unbindKeyDown('Ctrl+Shift+T') + topMenu:destroy() +end + +function online() + if topMenu.hideIngame then + hide() + else + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'topMenu', AnchorBottom) + end + if topMenu.onlineLabel then + topMenu.onlineLabel:hide() + end + + showGameButtons() + + if topMenu.pingLabel then + addEvent(function() + if modules.client_options.getOption('showPing') and (g_game.getFeature(GameClientPing) or g_game.getFeature(GameExtendedClientPing)) then + topMenu.pingLabel:show() + else + topMenu.pingLabel:hide() + end + end) + end +end + +function offline() + if topMenu.hideIngame then + show() + end + if topMenu.onlineLabel then + topMenu.onlineLabel:show() + end + + hideGameButtons() + if topMenu.pingLabel then + topMenu.pingLabel:hide() + end + updateStatus() +end + +function updateFps() + if not topMenu.fpsLabel then return end + fpsUpdateEvent = scheduleEvent(updateFps, 500) + text = 'FPS: ' .. g_app.getFps() + topMenu.fpsLabel:setText(text) +end + +function updatePing(ping) + if not topMenu.pingLabel then return end + if g_proxy and g_proxy.getPing() > 0 then + ping = g_proxy.getPing() + end + + local text = 'Ping: ' + local color + if ping < 0 then + text = text .. "??" + color = 'yellow' + else + text = text .. ping .. ' ms' + if ping >= 500 then + color = 'red' + elseif ping >= 250 then + color = 'yellow' + else + color = 'green' + end + end + topMenu.pingLabel:setColor(color) + topMenu.pingLabel:setText(text) +end + +function setPingVisible(enable) + if not topMenu.pingLabel then return end + topMenu.pingLabel:setVisible(enable) +end + +function setFpsVisible(enable) + if not topMenu.fpsLabel then return end + topMenu.fpsLabel:setVisible(enable) +end + +function addLeftButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.leftButtonsPanel, false, front, index) +end + +function addLeftToggleButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.leftButtonsPanel, true, front, index) +end + +function addRightButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.rightButtonsPanel, false, front, index) +end + +function addRightToggleButton(id, description, icon, callback, front, index) + return addButton(id, description, icon, callback, topMenu.rightButtonsPanel, true, front, index) +end + +function addLeftGameButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.leftGameButtonsPanel, false, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addLeftGameToggleButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.leftGameButtonsPanel, true, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addRightGameButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.rightGameButtonsPanel, false, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function addRightGameToggleButton(id, description, icon, callback, front, index) + local button = addButton(id, description, icon, callback, topMenu.rightGameButtonsPanel, true, front, index) + if modules.game_buttons then + modules.game_buttons.takeButton(button) + end + return button +end + +function showGameButtons() + topMenu.leftGameButtonsPanel:show() + topMenu.rightGameButtonsPanel:show() + if modules.game_buttons then + modules.game_buttons.takeButtons(topMenu.leftGameButtonsPanel:getChildren()) + modules.game_buttons.takeButtons(topMenu.rightGameButtonsPanel:getChildren()) + end +end + +function hideGameButtons() + topMenu.leftGameButtonsPanel:hide() + topMenu.rightGameButtonsPanel:hide() +end + +function getButton(id) + return topMenu:recursiveGetChildById(id) +end + +function getTopMenu() + return topMenu +end + +function toggle() + if not topMenu then + return + end + + if topMenu:isVisible() then + hide() + else + show() + end +end + +function hide() + topMenu:hide() + if not topMenu.hideIngame then + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'parent', AnchorTop) + end + if modules.game_stats then + modules.game_stats.show() + end +end + +function show() + topMenu:show() + if not topMenu.hideIngame then + modules.game_interface.getRootPanel():addAnchor(AnchorTop, 'topMenu', AnchorBottom) + end + if modules.game_stats then + modules.game_stats.hide() + end +end + +function updateStatus() + removeEvent(statusUpdateEvent) + if not Services or not Services.status or Services.status:len() < 4 then return end + if not topMenu.onlineLabel then return end + if g_game.isOnline() then return end + HTTP.postJSON(Services.status, {type="cacheinfo"}, function(data, err) + if err then + g_logger.warning("HTTP error for " .. Services.status .. ": " .. err) + statusUpdateEvent = scheduleEvent(updateStatus, 5000) + return + end + if topMenu.onlineLabel then + if data.online then + topMenu.onlineLabel:setText(data.online) + elseif data.playersonline then + topMenu.onlineLabel:setText(data.playersonline .. " players online") + end + end + if data.discord_online and topMenu.discordLabel then + topMenu.discordLabel:setText(data.discord_online) + end + if data.discord_link and topMenu.discordLabel and topMenu.discord then + local discordOnClick = function() + g_platform.openUrl(data.discord_link) + end + topMenu.discordLabel.onClick = discordOnClick + topMenu.discord.onClick = discordOnClick + end + statusUpdateEvent = scheduleEvent(updateStatus, 60000) + end) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.otmod new file mode 100644 index 0000000..4e18fd7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/client_topmenu/topmenu.otmod @@ -0,0 +1,10 @@ +Module + name: client_topmenu + description: Create the top menu + author: edubart + website: https://github.com/edubart/otclient + scripts: [ topmenu ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/base64.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/base64.lua new file mode 100644 index 0000000..c220f4b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/base64.lua @@ -0,0 +1,176 @@ +--[[ + + base64 -- v1.5.1 public domain Lua base64 encoder/decoder + no warranty implied; use at your own risk + + Needs bit32.extract function. If not present it's implemented using BitOp + or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua + implementation inspired by Rici Lake's post: + http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html + + author: Ilya Kolbin (iskolbin@gmail.com) + url: github.com/iskolbin/lbase64 + + COMPATIBILITY + + Lua 5.1, 5.2, 5.3, LuaJIT + + LICENSE + + See end of file for license information. + +--]] + + +base64 = {} + +local extract = _G.bit32 and _G.bit32.extract +if not extract then + if _G.bit then + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION >= "Lua 5.3" then + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + else + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + end +end + + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +--[[ +Copyright (c) 2018 Ilya Kolbin +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--]] \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/bitwise.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/bitwise.lua new file mode 100644 index 0000000..0eed21c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/bitwise.lua @@ -0,0 +1,17 @@ +Bit = {} + +function Bit.bit(p) + return 2 ^ p +end + +function Bit.hasBit(x, p) + return x % (p + p) >= p +end + +function Bit.setbit(x, p) + return Bit.hasBit(x, p) and x or x + p +end + +function Bit.clearbit(x, p) + return Bit.hasBit(x, p) and x - p or x +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/config.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/config.lua new file mode 100644 index 0000000..27037ff --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/config.lua @@ -0,0 +1,73 @@ +-- @docclass + +local function convertSettingValue(value) + if type(value) == 'table' then + if value.x and value.width then + return recttostring(value) + elseif value.x then + return pointtostring(value) + elseif value.width then + return sizetostring(value) + elseif value.r then + return colortostring(value) + else + return value + end + elseif value == nil then + return '' + else + return tostring(value) + end +end + +function Config:set(key, value) + self:setValue(key, convertSettingValue(value)) +end + +function Config:setDefault(key, value) + if self:exists(key) then return false end + self:set(key, value) + return true +end + +function Config:get(key, default) + if not self:exists(key) and default ~= nil then + self:set(key, default) + end + return self:getValue(key) +end + +function Config:getString(key, default) + return self:get(key, default) +end + +function Config:getInteger(key, default) + local v = tonumber(self:get(key, default)) or 0 + return v +end + +function Config:getNumber(key, default) + local v = tonumber(self:get(key, default)) or 0 + return v +end + +function Config:getBoolean(key, default) + return toboolean(self:get(key, default)) +end + +function Config:getPoint(key, default) + return topoint(self:get(key, default)) +end + +function Config:getRect(key, default) + return torect(self:get(key, default)) +end + +function Config:getSize(key, default) + return tosize(self:get(key, default)) +end + +function Config:getColor(key, default) + return tocolor(self:get(key, default)) +end + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/const.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/const.lua new file mode 100644 index 0000000..ab5c9ce --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/const.lua @@ -0,0 +1,325 @@ +-- @docconsts @{ + +AnchorNone = 0 +AnchorTop = 1 +AnchorBottom = 2 +AnchorLeft = 3 +AnchorRight = 4 +AnchorVerticalCenter = 5 +AnchorHorizontalCenter = 6 + +LogDebug = 0 +LogInfo = 1 +LogWarning = 2 +LogError = 3 +LogFatal = 4 + +MouseFocusReason = 0 +KeyboardFocusReason = 1 +ActiveFocusReason = 2 +OtherFocusReason = 3 + +AutoFocusNone = 0 +AutoFocusFirst = 1 +AutoFocusLast = 2 + +KeyboardNoModifier = 0 +KeyboardCtrlModifier = 1 +KeyboardAltModifier = 2 +KeyboardCtrlAltModifier = 3 +KeyboardShiftModifier = 4 +KeyboardCtrlShiftModifier = 5 +KeyboardAltShiftModifier = 6 +KeyboardCtrlAltShiftModifier = 7 + +MouseNoButton = 0 +MouseLeftButton = 1 +MouseRightButton = 2 +MouseMidButton = 3 +MouseTouch = 4 +MouseTouch2 = 5 -- multitouch, 2nd finger +MouseTouch3 = 6 -- multitouch, 3th finger + +MouseNoWheel = 0 +MouseWheelUp = 1 +MouseWheelDown = 2 + +AlignNone = 0 +AlignLeft = 1 +AlignRight = 2 +AlignTop = 4 +AlignBottom = 8 +AlignHorizontalCenter = 16 +AlignVerticalCenter = 32 +AlignTopLeft = 5 +AlignTopRight = 6 +AlignBottomLeft = 9 +AlignBottomRight = 10 +AlignLeftCenter = 33 +AlignRightCenter = 34 +AlignTopCenter = 20 +AlignBottomCenter = 24 +AlignCenter = 48 + +KeyUnknown = 0 +KeyEscape = 1 +KeyTab = 2 +KeyBackspace = 3 +KeyEnter = 5 +KeyInsert = 6 +KeyDelete = 7 +KeyPause = 8 +KeyPrintScreen = 9 +KeyHome = 10 +KeyEnd = 11 +KeyPageUp = 12 +KeyPageDown = 13 +KeyUp = 14 +KeyDown = 15 +KeyLeft = 16 +KeyRight = 17 +KeyNumLock = 18 +KeyScrollLock = 19 +KeyCapsLock = 20 +KeyCtrl = 21 +KeyShift = 22 +KeyAlt = 23 +KeyMeta = 25 +KeyMenu = 26 +KeySpace = 32 -- ' ' +KeyExclamation = 33 -- ! +KeyQuote = 34 -- " +KeyNumberSign = 35 -- # +KeyDollar = 36 -- $ +KeyPercent = 37 -- % +KeyAmpersand = 38 -- & +KeyApostrophe = 39 -- ' +KeyLeftParen = 40 -- ( +KeyRightParen = 41 -- ) +KeyAsterisk = 42 -- * +KeyPlus = 43 -- + +KeyComma = 44 -- , +KeyMinus = 45 -- - +KeyPeriod = 46 -- . +KeySlash = 47 -- / +Key0 = 48 -- 0 +Key1 = 49 -- 1 +Key2 = 50 -- 2 +Key3 = 51 -- 3 +Key4 = 52 -- 4 +Key5 = 53 -- 5 +Key6 = 54 -- 6 +Key7 = 55 -- 7 +Key8 = 56 -- 8 +Key9 = 57 -- 9 +KeyColon = 58 -- : +KeySemicolon = 59 -- ; +KeyLess = 60 -- < +KeyEqual = 61 -- = +KeyGreater = 62 -- > +KeyQuestion = 63 -- ? +KeyAtSign = 64 -- @ +KeyA = 65 -- a +KeyB = 66 -- b +KeyC = 67 -- c +KeyD = 68 -- d +KeyE = 69 -- e +KeyF = 70 -- f +KeyG = 71 -- g +KeyH = 72 -- h +KeyI = 73 -- i +KeyJ = 74 -- j +KeyK = 75 -- k +KeyL = 76 -- l +KeyM = 77 -- m +KeyN = 78 -- n +KeyO = 79 -- o +KeyP = 80 -- p +KeyQ = 81 -- q +KeyR = 82 -- r +KeyS = 83 -- s +KeyT = 84 -- t +KeyU = 85 -- u +KeyV = 86 -- v +KeyW = 87 -- w +KeyX = 88 -- x +KeyY = 89 -- y +KeyZ = 90 -- z +KeyLeftBracket = 91 -- [ +KeyBackslash = 92 -- '\' +KeyRightBracket = 93 -- ] +KeyCaret = 94 -- ^ +KeyUnderscore = 95 -- _ +KeyGrave = 96 -- ` +KeyLeftCurly = 123 -- { +KeyBar = 124 -- | +KeyRightCurly = 125 -- } +KeyTilde = 126 -- ~ +KeyF1 = 128 +KeyF2 = 129 +KeyF3 = 130 +KeyF4 = 131 +KeyF5 = 132 +KeyF6 = 134 +KeyF7 = 135 +KeyF8 = 136 +KeyF9 = 137 +KeyF10 = 138 +KeyF11 = 139 +KeyF12 = 140 +KeyNumpad0 = 141 +KeyNumpad1 = 142 +KeyNumpad2 = 143 +KeyNumpad3 = 144 +KeyNumpad4 = 145 +KeyNumpad5 = 146 +KeyNumpad6 = 147 +KeyNumpad7 = 148 +KeyNumpad8 = 149 +KeyNumpad9 = 150 + +FirstKey = KeyUnknown +LastKey = KeyNumpad9 + +ExtendedActivate = 0 +ExtendedLocales = 1 +ExtendedParticles = 2 + +-- @} + +KeyCodeDescs = { + [KeyUnknown] = 'Unknown', + [KeyEscape] = 'Escape', + [KeyTab] = 'Tab', + [KeyBackspace] = 'Backspace', + [KeyEnter] = 'Enter', + [KeyInsert] = 'Insert', + [KeyDelete] = 'Delete', + [KeyPause] = 'Pause', + [KeyPrintScreen] = 'PrintScreen', + [KeyHome] = 'Home', + [KeyEnd] = 'End', + [KeyPageUp] = 'PageUp', + [KeyPageDown] = 'PageDown', + [KeyUp] = 'Up', + [KeyDown] = 'Down', + [KeyLeft] = 'Left', + [KeyRight] = 'Right', + [KeyNumLock] = 'NumLock', + [KeyScrollLock] = 'ScrollLock', + [KeyCapsLock] = 'CapsLock', + [KeyCtrl] = 'Ctrl', + [KeyShift] = 'Shift', + [KeyAlt] = 'Alt', + [KeyMeta] = 'Meta', + [KeyMenu] = 'Menu', + [KeySpace] = 'Space', + [KeyExclamation] = '!', + [KeyQuote] = '\"', + [KeyNumberSign] = '#', + [KeyDollar] = '$', + [KeyPercent] = '%', + [KeyAmpersand] = '&', + [KeyApostrophe] = '\'', + [KeyLeftParen] = '(', + [KeyRightParen] = ')', + [KeyAsterisk] = '*', + [KeyPlus] = 'Plus', + [KeyComma] = ',', + [KeyMinus] = '-', + [KeyPeriod] = '.', + [KeySlash] = '/', + [Key0] = '0', + [Key1] = '1', + [Key2] = '2', + [Key3] = '3', + [Key4] = '4', + [Key5] = '5', + [Key6] = '6', + [Key7] = '7', + [Key8] = '8', + [Key9] = '9', + [KeyColon] = ':', + [KeySemicolon] = ';', + [KeyLess] = '<', + [KeyEqual] = '=', + [KeyGreater] = '>', + [KeyQuestion] = '?', + [KeyAtSign] = '@', + [KeyA] = 'A', + [KeyB] = 'B', + [KeyC] = 'C', + [KeyD] = 'D', + [KeyE] = 'E', + [KeyF] = 'F', + [KeyG] = 'G', + [KeyH] = 'H', + [KeyI] = 'I', + [KeyJ] = 'J', + [KeyK] = 'K', + [KeyL] = 'L', + [KeyM] = 'M', + [KeyN] = 'N', + [KeyO] = 'O', + [KeyP] = 'P', + [KeyQ] = 'Q', + [KeyR] = 'R', + [KeyS] = 'S', + [KeyT] = 'T', + [KeyU] = 'U', + [KeyV] = 'V', + [KeyW] = 'W', + [KeyX] = 'X', + [KeyY] = 'Y', + [KeyZ] = 'Z', + [KeyLeftBracket] = '[', + [KeyBackslash] = '\\', + [KeyRightBracket] = ']', + [KeyCaret] = '^', + [KeyUnderscore] = '_', + [KeyGrave] = '`', + [KeyLeftCurly] = '{', + [KeyBar] = '|', + [KeyRightCurly] = '}', + [KeyTilde] = '~', + [KeyF1] = 'F1', + [KeyF2] = 'F2', + [KeyF3] = 'F3', + [KeyF4] = 'F4', + [KeyF5] = 'F5', + [KeyF6] = 'F6', + [KeyF7] = 'F7', + [KeyF8] = 'F8', + [KeyF9] = 'F9', + [KeyF10] = 'F10', + [KeyF11] = 'F11', + [KeyF12] = 'F12', + [KeyNumpad0] = 'Numpad0', + [KeyNumpad1] = 'Numpad1', + [KeyNumpad2] = 'Numpad2', + [KeyNumpad3] = 'Numpad3', + [KeyNumpad4] = 'Numpad4', + [KeyNumpad5] = 'Numpad5', + [KeyNumpad6] = 'Numpad6', + [KeyNumpad7] = 'Numpad7', + [KeyNumpad8] = 'Numpad8', + [KeyNumpad9] = 'Numpad9', +} + +NetworkMessageTypes = { + Boolean = 1, + U8 = 2, + U16 = 3, + U32 = 4, + U64 = 5, + NumberString = 6, + String = 7, + Table = 8, +} + +SoundChannels = { + Music = 1, + Ambient = 2, + Effect = 3, + Bot = 4 +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/corelib.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/corelib.otmod new file mode 100644 index 0000000..75cce3b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/corelib.otmod @@ -0,0 +1,34 @@ +Module + name: corelib + description: Contains core lua classes, functions and constants used by other modules + author: OTClient team + website: https://github.com/edubart/otclient + reloadable: false + + @onLoad: | + dofile 'math' + dofile 'string' + dofile 'table' + dofile 'bitwise' + dofile 'struct' + + dofile 'const' + dofile 'util' + dofile 'globals' + dofile 'config' + dofile 'settings' + dofile 'keyboard' + dofile 'mouse' + dofile 'net' + + dofiles 'classes' + dofiles 'ui' + + dofile 'inputmessage' + dofile 'outputmessage' + dofile 'orderedtable' + + dofile 'base64' + dofile 'json' + dofile 'http' + dofile 'test' diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/globals.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/globals.lua new file mode 100644 index 0000000..172f2c6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/globals.lua @@ -0,0 +1,76 @@ +-- @docvars @{ + +-- root widget +rootWidget = g_ui.getRootWidget() +modules = package.loaded + +-- G is used as a global table to save variables in memory between reloads +G = G or {} + +-- @} + +-- @docfuncs @{ + +function scheduleEvent(callback, delay) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.scheduleEvent(desc, callback, delay) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function addEvent(callback, front) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.addEvent(desc, callback, front) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function cycleEvent(callback, interval) + local desc = "lua" + local info = debug.getinfo(2, "Sl") + if info then + desc = info.short_src .. ":" .. info.currentline + end + local event = g_dispatcher.cycleEvent(desc, callback, interval) + -- must hold a reference to the callback, otherwise it would be collected + event._callback = callback + return event +end + +function periodicalEvent(eventFunc, conditionFunc, delay, autoRepeatDelay) + delay = delay or 30 + autoRepeatDelay = autoRepeatDelay or delay + + local func + func = function() + if conditionFunc and not conditionFunc() then + func = nil + return + end + eventFunc() + scheduleEvent(func, delay) + end + + scheduleEvent(function() + func() + end, autoRepeatDelay) +end + +function removeEvent(event) + if event then + event:cancel() + event._callback = nil + end +end + +-- @} \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/http.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/http.lua new file mode 100644 index 0000000..abc3398 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/http.lua @@ -0,0 +1,278 @@ +HTTP = { + timeout=5, + websocketTimeout=15, + agent="Mozilla/5.0", + imageId=1000, + images={}, + operations={}, +} + +function HTTP.get(url, callback) + if not g_http or not g_http.get then + return error("HTTP.get is not supported") + end + local operation = g_http.get(url, HTTP.timeout) + HTTP.operations[operation] = {type="get", url=url, callback=callback} + return operation +end + +function HTTP.getJSON(url, callback) + if not g_http or not g_http.get then + return error("HTTP.getJSON is not supported") + end + local operation = g_http.get(url, HTTP.timeout) + HTTP.operations[operation] = {type="get", json=true, url=url, callback=callback} + return operation +end + +function HTTP.post(url, data, callback) + if not g_http or not g_http.post then + return error("HTTP.post is not supported") + end + if type(data) == "table" then + data = json.encode(data) + end + local operation = g_http.post(url, data, HTTP.timeout) + HTTP.operations[operation] = {type="post", url=url, callback=callback} + return operation +end + +function HTTP.postJSON(url, data, callback) + if not g_http or not g_http.post then + return error("HTTP.postJSON is not supported") + end + if type(data) == "table" then + data = json.encode(data) + end + local operation = g_http.post(url, data, HTTP.timeout) + HTTP.operations[operation] = {type="post", json=true, url=url, callback=callback} + return operation +end + +function HTTP.download(url, file, callback, progressCallback) + if not g_http or not g_http.download then + return error("HTTP.download is not supported") + end + local operation = g_http.download(url, file, HTTP.timeout) + HTTP.operations[operation] = {type="download", url=url, file=file, callback=callback, progressCallback=progressCallback} + return operation +end + +function HTTP.downloadImage(url, callback) + if not g_http or not g_http.download then + return error("HTTP.downloadImage is not supported") + end + if HTTP.images[url] ~= nil then + if callback then + callback('/downloads/' .. HTTP.images[url], nil) + end + return + end + local file = "autoimage_" .. HTTP.imageId .. ".png" + HTTP.imageId = HTTP.imageId + 1 + local operation = g_http.download(url, file, HTTP.timeout) + HTTP.operations[operation] = {type="image", url=url, file=file, callback=callback} + return operation +end + +function HTTP.webSocket(url, callbacks, timeout, jsonWebsocket) + if not g_http or not g_http.ws then + return error("WebSocket is not supported") + end + if not timeout or timeout < 1 then + timeout = HTTP.websocketTimeout + end + local operation = g_http.ws(url, timeout) + HTTP.operations[operation] = {type="ws", json=jsonWebsocket, url=url, callbacks=callbacks} + return { + id = operation, + url = url, + close = function() + g_http.wsClose(operation) + end, + send = function(message) + if type(message) == "table" then + message = json.encode(message) + end + g_http.wsSend(operation, message) + end + } +end +HTTP.WebSocket = HTTP.webSocket + +function HTTP.webSocketJSON(url, callbacks, timeout) + return HTTP.webSocket(url, callbacks, timeout, true) +end +HTTP.WebSocketJSON = HTTP.webSocketJSON + +function HTTP.cancel(operationId) + if not g_http or not g_http.cancel then + return + end + HTTP.operations[operationId] = nil + return g_http.cancel(operationId) +end + +function HTTP.onGet(operationId, url, err, data) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if not err and operation.json then + local status, result = pcall(function() return json.decode(data) end) + if not status then + err = "JSON ERROR: " .. result + if data and data:len() > 0 then + err = err .. " (" .. data:sub(1, 100) .. ")" + end + end + data = result + end + if operation.callback then + operation.callback(data, err) + end +end + +function HTTP.onGetProgress(operationId, url, progress) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + +end + +function HTTP.onPost(operationId, url, err, data) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if not err and operation.json then + local status, result = pcall(function() return json.decode(data) end) + if not status then + err = "JSON ERROR: " .. result + if data and data:len() > 0 then + err = err .. " (" .. data:sub(1, 100) .. ")" + end + end + data = result + end + if operation.callback then + operation.callback(data, err) + end +end + +function HTTP.onPostProgress(operationId, url, progress) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end +end + +function HTTP.onDownload(operationId, url, err, path, checksum) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if err and err:len() == 0 then + err = nil + end + if operation.callback then + if operation["type"] == "image" then + if not err then + HTTP.images[url] = path + end + operation.callback('/downloads/' .. path, err) + else + operation.callback(path, checksum, err) + end + end +end + +function HTTP.onDownloadProgress(operationId, url, progress, speed) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.progressCallback then + operation.progressCallback(progress, speed) + end +end + +function HTTP.onWsOpen(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onOpen then + operation.callbacks.onOpen(message, operationId) + end +end + +function HTTP.onWsMessage(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onMessage then + if operation.json then + local status, result = pcall(function() return json.decode(message) end) + local err = nil + if not status then + err = "JSON ERROR: " .. result + if message and message:len() > 0 then + err = err .. " (" .. message:sub(1, 100) .. ")" + end + end + if err then + if operation.callbacks.onError then + operation.callbacks.onError(err, operationId) + end + else + operation.callbacks.onMessage(result, operationId) + end + else + operation.callbacks.onMessage(message, operationId) + end + end +end + +function HTTP.onWsClose(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onClose then + operation.callbacks.onClose(message, operationId) + end +end + +function HTTP.onWsError(operationId, message) + local operation = HTTP.operations[operationId] + if operation == nil then + return + end + if operation.callbacks.onError then + operation.callbacks.onError(message, operationId) + end +end + +connect(g_http, + { + onGet = HTTP.onGet, + onGetProgress = HTTP.onGetProgress, + onPost = HTTP.onPost, + onPostProgress = HTTP.onPostProgress, + onDownload = HTTP.onDownload, + onDownloadProgress = HTTP.onDownloadProgress, + onWsOpen = HTTP.onWsOpen, + onWsMessage = HTTP.onWsMessage, + onWsClose = HTTP.onWsClose, + onWsError = HTTP.onWsError, + }) +g_http.setUserAgent(HTTP.agent) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/inputmessage.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/inputmessage.lua new file mode 100644 index 0000000..48597e4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/inputmessage.lua @@ -0,0 +1,51 @@ +function InputMessage:getData() + local dataType = self:getU8() + if dataType == NetworkMessageTypes.Boolean then + return numbertoboolean(self:getU8()) + elseif dataType == NetworkMessageTypes.U8 then + return self:getU8() + elseif dataType == NetworkMessageTypes.U16 then + return self:getU16() + elseif dataType == NetworkMessageTypes.U32 then + return self:getU32() + elseif dataType == NetworkMessageTypes.U64 then + return self:getU64() + elseif dataType == NetworkMessageTypes.NumberString then + return tonumber(self:getString()) + elseif dataType == NetworkMessageTypes.String then + return self:getString() + elseif dataType == NetworkMessageTypes.Table then + return self:getTable() + else + perror('Unknown data type ' .. dataType) + end + return nil +end + +function InputMessage:getTable() + local ret = {} + local size = self:getU16() + for i=1,size do + local index = self:getData() + local value = self:getData() + ret[index] = value + end + return ret +end + +function InputMessage:getColor() + local color = {} + color.r = self:getU8() + color.g = self:getU8() + color.b = self:getU8() + color.a = self:getU8() + return color +end + +function InputMessage:getPosition() + local position = {} + position.x = self:getU16() + position.y = self:getU16() + position.z = self:getU8() + return position +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/json.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/json.lua new file mode 100644 index 0000000..4535c6f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/json.lua @@ -0,0 +1,419 @@ +-- +-- json.lua +-- +-- Copyright (c) 2019 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +json = { _version = "0.1.1" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function make_indent(state) + return string.rep(" ", state.currentIndentLevel * state.indent) +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil() + return "null" +end + + +local function encode_table(val, state) + local res = {} + local stack = state.stack + local pretty = state.indent > 0 + + local close_indent = make_indent(state) + local comma = pretty and ",\n" or "," + local colon = pretty and ": " or ":" + local open_brace = pretty and "{\n" or "{" + local close_brace = pretty and ("\n" .. close_indent .. "}") or "}" + local open_bracket = pretty and "[\n" or "[" + local close_bracket = pretty and ("\n" .. close_indent .. "]") or "]" + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for _, v in ipairs(val) do + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 + end + stack[val] = nil + return open_bracket .. table.concat(res, comma) .. close_bracket + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + state.currentIndentLevel = state.currentIndentLevel + 1 + table.insert(res, make_indent(state) .. encode(k, state) .. colon .. encode(v, state)) + state.currentIndentLevel = state.currentIndentLevel - 1 + end + stack[val] = nil + return open_brace .. table.concat(res, comma) .. close_brace + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, state) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, state) + end + error("unexpected type '" .. t .. "'") +end + +function json.encode(val, indent) + local state = { + indent = indent or 0, + currentIndentLevel = 0, + stack = {} + } + return encode(val, state) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/keyboard.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/keyboard.lua new file mode 100644 index 0000000..463fe1e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/keyboard.lua @@ -0,0 +1,251 @@ +-- @docclass +g_keyboard = {} + +-- private functions +function translateKeyCombo(keyCombo) + if not keyCombo or #keyCombo == 0 then return nil end + local keyComboDesc = '' + for k,v in pairs(keyCombo) do + local keyDesc = KeyCodeDescs[v] + if keyDesc == nil then return nil end + keyComboDesc = keyComboDesc .. '+' .. keyDesc + end + keyComboDesc = keyComboDesc:sub(2) + return keyComboDesc +end + +local function getKeyCode(key) + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == key:trim():lower() then + return keyCode + end + end +end + +function retranslateKeyComboDesc(keyComboDesc) + if keyComboDesc == nil then + error('Unable to translate key combo \'' .. keyComboDesc .. '\'') + end + + if type(keyComboDesc) == 'number' then + keyComboDesc = tostring(keyComboDesc) + end + + local keyCombo = {} + for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == currentKeyDesc:trim():lower() then + table.insert(keyCombo, keyCode) + end + end + end + return translateKeyCombo(keyCombo) +end + +function determineKeyComboDesc(keyCode, keyboardModifiers) + local keyCombo = {} + if keyCode == KeyCtrl or keyCode == KeyShift or keyCode == KeyAlt then + table.insert(keyCombo, keyCode) + elseif KeyCodeDescs[keyCode] ~= nil then + if keyboardModifiers == KeyboardCtrlModifier then + table.insert(keyCombo, KeyCtrl) + elseif keyboardModifiers == KeyboardAltModifier then + table.insert(keyCombo, KeyAlt) + elseif keyboardModifiers == KeyboardCtrlAltModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyAlt) + elseif keyboardModifiers == KeyboardShiftModifier then + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardCtrlShiftModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardAltShiftModifier then + table.insert(keyCombo, KeyAlt) + table.insert(keyCombo, KeyShift) + elseif keyboardModifiers == KeyboardCtrlAltShiftModifier then + table.insert(keyCombo, KeyCtrl) + table.insert(keyCombo, KeyAlt) + table.insert(keyCombo, KeyShift) + end + table.insert(keyCombo, keyCode) + end + return translateKeyCombo(keyCombo) +end + +local function onWidgetKeyDown(widget, keyCode, keyboardModifiers) + if keyCode == KeyUnknown then return false end + local callback = widget.boundAloneKeyDownCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)] + signalcall(callback, widget, keyCode) + callback = widget.boundKeyDownCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode) +end + +local function onWidgetKeyUp(widget, keyCode, keyboardModifiers) + if keyCode == KeyUnknown then return false end + local callback = widget.boundAloneKeyUpCombos[determineKeyComboDesc(keyCode, KeyboardNoModifier)] + signalcall(callback, widget, keyCode) + callback = widget.boundKeyUpCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode) +end + +local function onWidgetKeyPress(widget, keyCode, keyboardModifiers, autoRepeatTicks) + if keyCode == KeyUnknown then return false end + local callback = widget.boundKeyPressCombos[determineKeyComboDesc(keyCode, keyboardModifiers)] + return signalcall(callback, widget, keyCode, autoRepeatTicks) +end + +local function connectKeyDownEvent(widget) + if widget.boundKeyDownCombos then return end + connect(widget, { onKeyDown = onWidgetKeyDown }) + widget.boundKeyDownCombos = {} + widget.boundAloneKeyDownCombos = {} +end + +local function connectKeyUpEvent(widget) + if widget.boundKeyUpCombos then return end + connect(widget, { onKeyUp = onWidgetKeyUp }) + widget.boundKeyUpCombos = {} + widget.boundAloneKeyUpCombos = {} +end + +local function connectKeyPressEvent(widget) + if widget.boundKeyPressCombos then return end + connect(widget, { onKeyPress = onWidgetKeyPress }) + widget.boundKeyPressCombos = {} +end + +-- public functions +function g_keyboard.bindKeyDown(keyComboDesc, callback, widget, alone) + widget = widget or rootWidget + connectKeyDownEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + if alone then + connect(widget.boundAloneKeyDownCombos, keyComboDesc, callback) + else + connect(widget.boundKeyDownCombos, keyComboDesc, callback) + end +end + +function g_keyboard.bindKeyUp(keyComboDesc, callback, widget, alone) + widget = widget or rootWidget + connectKeyUpEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + if alone then + connect(widget.boundAloneKeyUpCombos, keyComboDesc, callback) + else + connect(widget.boundKeyUpCombos, keyComboDesc, callback) + end +end + +function g_keyboard.bindKeyPress(keyComboDesc, callback, widget) + widget = widget or rootWidget + connectKeyPressEvent(widget) + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + connect(widget.boundKeyPressCombos, keyComboDesc, callback) +end + +local function getUnbindArgs(arg1, arg2) + local callback + local widget + if type(arg1) == 'function' then callback = arg1 + elseif type(arg2) == 'function' then callback = arg2 end + if type(arg1) == 'userdata' then widget = arg1 + elseif type(arg2) == 'userdata' then widget = arg2 end + widget = widget or rootWidget + return callback, widget +end + +function g_keyboard.unbindKeyDown(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyDownCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyDownCombos, keyComboDesc, callback) +end + +function g_keyboard.unbindKeyUp(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyUpCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyUpCombos, keyComboDesc, callback) +end + +function g_keyboard.unbindKeyPress(keyComboDesc, arg1, arg2) + local callback, widget = getUnbindArgs(arg1, arg2) + if widget.boundKeyPressCombos == nil then return end + local keyComboDesc = retranslateKeyComboDesc(keyComboDesc) + disconnect(widget.boundKeyPressCombos, keyComboDesc, callback) +end + +function g_keyboard.getModifiers() + return g_window.getKeyboardModifiers() +end + +function g_keyboard.isKeyPressed(key) + if type(key) == 'string' then + key = getKeyCode(key) + end + return g_window.isKeyPressed(key) +end + +function g_keyboard.areKeysPressed(keyComboDesc) + for i,currentKeyDesc in ipairs(keyComboDesc:split('+')) do + for keyCode, keyDesc in pairs(KeyCodeDescs) do + if keyDesc:lower() == currentKeyDesc:trim():lower() then + if keyDesc:lower() == "ctrl" then + if not g_keyboard.isCtrlPressed() then + return false + end + elseif keyDesc:lower() == "shift" then + if not g_keyboard.isShiftPressed() then + return false + end + elseif keyDesc:lower() == "alt" then + if not g_keyboard.isAltPressed() then + return false + end + elseif not g_window.isKeyPressed(keyCode) then + return false + end + end + end + end + return true +end + +function g_keyboard.isKeySetPressed(keys, all) + all = all or false + local result = {} + for k,v in pairs(keys) do + if type(v) == 'string' then + v = getKeyCode(v) + end + if g_window.isKeyPressed(v) then + if not all then + return true + end + table.insert(result, true) + end + end + return #result == #keys +end + +function g_keyboard.isInUse() + for i = FirstKey, LastKey do + if g_window.isKeyPressed(key) then + return true + end + end + return false +end + +function g_keyboard.isCtrlPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardCtrlModifier) ~= 0 +end + +function g_keyboard.isAltPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardAltModifier) ~= 0 +end + +function g_keyboard.isShiftPressed() + return bit32.band(g_window.getKeyboardModifiers(), KeyboardShiftModifier) ~= 0 +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/math.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/math.lua new file mode 100644 index 0000000..79c6670 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/math.lua @@ -0,0 +1,35 @@ +-- @docclass math + +local U8 = 2^8 +local U16 = 2^16 +local U32 = 2^32 +local U64 = 2^64 + +function math.round(num, idp) + local mult = 10^(idp or 0) + if num >= 0 then + return math.floor(num * mult + 0.5) / mult + else + return math.ceil(num * mult - 0.5) / mult + end +end + +function math.isu8(num) + return math.isinteger(num) and num >= 0 and num < U8 +end + +function math.isu16(num) + return math.isinteger(num) and num >= U8 and num < U16 +end + +function math.isu32(num) + return math.isinteger(num) and num >= U16 and num < U32 +end + +function math.isu64(num) + return math.isinteger(num) and num >= U32 and num < U64 +end + +function math.isinteger(num) + return ((type(num) == 'number') and (num == math.floor(num))) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/mouse.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/mouse.lua new file mode 100644 index 0000000..d46314e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/mouse.lua @@ -0,0 +1,36 @@ +-- @docclass +function g_mouse.bindAutoPress(widget, callback, delay, button) + local button = button or MouseLeftButton + connect(widget, { onMousePress = function(widget, mousePos, mouseButton) + if mouseButton ~= button then + return false + end + local startTime = g_clock.millis() + callback(widget, mousePos, mouseButton, 0) + periodicalEvent(function() + callback(widget, g_window.getMousePosition(), mouseButton, g_clock.millis() - startTime) + end, function() + return g_mouse.isPressed(mouseButton) + end, 30, delay) + return true + end }) +end + +function g_mouse.bindPressMove(widget, callback) + connect(widget, { onMouseMove = function(widget, mousePos, mouseMoved) + if widget:isPressed() then + callback(mousePos, mouseMoved) + return true + end + end }) +end + +function g_mouse.bindPress(widget, callback, button) + connect(widget, { onMousePress = function(widget, mousePos, mouseButton) + if not button or button == mouseButton then + callback(mousePos, mouseButton) + return true + end + return false + end }) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/net.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/net.lua new file mode 100644 index 0000000..b2d5994 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/net.lua @@ -0,0 +1,16 @@ +function translateNetworkError(errcode, connecting, errdesc) + local text + if errcode == 111 then + text = tr('Connection refused, the server might be offline or restarting.\nPlease try again later.') + elseif errcode == 110 then + text = tr('Connection timed out. Either your network is failing or the server is offline.') + elseif errcode == 1 then + text = tr('Connection failed, the server address does not exist.') + elseif connecting then + text = tr('Connection failed.') + else + text = tr('Your connection has been lost.\nEither your network or the server went down.') + end + text = text .. ' ' .. tr('(ERROR %d)', errcode) + return text +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/orderedtable.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/orderedtable.lua new file mode 100644 index 0000000..cf9c839 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/orderedtable.lua @@ -0,0 +1,43 @@ +function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end + +function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + -- fetch the next value + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end + +function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/outputmessage.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/outputmessage.lua new file mode 100644 index 0000000..1e6737e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/outputmessage.lua @@ -0,0 +1,69 @@ +function OutputMessage:addData(data) + if type(data) == 'boolean' then + self:addU8(NetworkMessageTypes.Boolean) + self:addU8(booleantonumber(data)) + elseif type(data) == 'number' then + if math.isu8(data) then + self:addU8(NetworkMessageTypes.U8) + self:addU8(data) + elseif math.isu16(data) then + self:addU8(NetworkMessageTypes.U16) + self:addU16(data) + elseif math.isu32(data) then + self:addU8(NetworkMessageTypes.U32) + self:addU32(data) + elseif math.isu64(data) then + self:addU8(NetworkMessageTypes.U64) + self:addU64(data) + else -- negative or non integer numbers + self:addU8(NetworkMessageTypes.NumberString) + self:addString(tostring(data)) + end + elseif type(data) == 'string' then + self:addU8(NetworkMessageTypes.String) + self:addString(data) + elseif type(data) == 'table' then + self:addU8(NetworkMessageTypes.Table) + self:addTable(data) + else + perror('Invalid data type ' .. type(data)) + end +end + +function OutputMessage:addTable(data) + local size = 0 + + -- reserve for size (should be addData, find a way to use it further) + local sizePos = self:getWritePos() + self:addU16(size) + local sizeSize = self:getWritePos() - sizePos + + -- add values + for key,value in pairs(data) do + self:addData(key) + self:addData(value) + size = size + 1 + end + + -- write size + local currentPos = self:getWritePos() + self:setWritePos(sizePos) + self:addU16(size) + + -- fix msg size and go back to end + self:setMessageSize(self:getMessageSize() - sizeSize) + self:setWritePos(currentPos) +end + +function OutputMessage:addColor(color) + self:addU8(color.r) + self:addU8(color.g) + self:addU8(color.b) + self:addU8(color.a) +end + +function OutputMessage:addPosition(position) + self:addU16(position.x) + self:addU16(position.y) + self:addU8(position.z) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/settings.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/settings.lua new file mode 100644 index 0000000..0eeaae8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/settings.lua @@ -0,0 +1,3 @@ +g_settings = makesingleton(g_configs.getSettings()) + +-- Reserved for future functionality diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/string.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/string.lua new file mode 100644 index 0000000..e05c0e2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/string.lua @@ -0,0 +1,59 @@ +-- @docclass string + +function string:split(delim) + local start = 1 + local results = {} + while true do + local pos = string.find(self, delim, start, true) + if not pos then + break + end + table.insert(results, string.sub(self, start, pos-1)) + start = pos + string.len(delim) + end + table.insert(results, string.sub(self, start)) + table.removevalue(results, '') + return results +end + +function string:starts(start) + return string.sub(self, 1, #start) == start +end + +function string:ends(test) + return test =='' or string.sub(self,-string.len(test)) == test +end + +function string:trim() + return string.match(self, '^%s*(.*%S)') or '' +end + +function string:explode(sep, limit) + if type(sep) ~= 'string' or tostring(self):len() == 0 or sep:len() == 0 then + return {} + end + + local i, pos, tmp, t = 0, 1, "", {} + for s, e in function() return string.find(self, sep, pos) end do + tmp = self:sub(pos, s - 1):trim() + table.insert(t, tmp) + pos = e + 1 + + i = i + 1 + if limit ~= nil and i == limit then + break + end + end + + tmp = self:sub(pos):trim() + table.insert(t, tmp) + return t +end + +function string:contains(str, checkCase, start, plain) + if(not checkCase) then + self = self:lower() + str = str:lower() + end + return string.find(self, str, start and start or 1, plain == nil and true or false) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/struct.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/struct.lua new file mode 100644 index 0000000..2ed134d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/struct.lua @@ -0,0 +1,173 @@ +Struct = {} + +function Struct.pack(format, ...) + local stream = {} + local vars = {...} + local endianness = true + + for i = 1, format:len() do + local opt = format:sub(i, i) + + if opt == '>' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local val = tonumber(table.remove(vars, 1)) + + if val < 0 then + val = val + 2 ^ (n * 8 - 1) + end + + local bytes = {} + for j = 1, n do + table.insert(bytes, string.char(val % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt:find('[fd]') then + local val = tonumber(table.remove(vars, 1)) + local sign = 0 + + if val < 0 then + sign = 1 + val = -val + end + + local mantissa, exponent = math.frexp(val) + if val == 0 then + mantissa = 0 + exponent = 0 + else + mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24) + exponent = exponent + ((opt == 'd') and 1022 or 126) + end + + local bytes = {} + if opt == 'd' then + val = mantissa + for i = 1, 6 do + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + else + table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8))) + val = math.floor(mantissa / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) + val = math.floor(val / (2 ^ 8)) + end + + table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8))) + val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8)) + table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8))) + val = math.floor((sign * 128 + val) / (2 ^ 8)) + + if not endianness then + table.insert(stream, string.reverse(table.concat(bytes))) + else + table.insert(stream, table.concat(bytes)) + end + elseif opt == 's' then + table.insert(stream, tostring(table.remove(vars, 1))) + table.insert(stream, string.char(0)) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + local length = tonumber(n) + + if length > 0 then + local str = tostring(table.remove(vars, 1)) + if length - str:len() > 0 then + str = str .. string.rep(' ', length - str:len()) + end + table.insert(stream, str:sub(1, length)) + end + i = i + n:len() + end + end + + return table.concat(stream) +end + +function Struct.unpack(format, stream) + local vars = {} + local iterator = 1 + local endianness = true + + for i = 1, format:len() do + local opt = format:sub(i, i) + + if opt == '>' then + endianness = false + elseif opt:find('[bBhHiIlL]') then + local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 + local signed = opt:lower() == opt + + local val = 0 + for j = 1, n do + local byte = string.byte(stream:sub(iterator, iterator)) + if endianness then + val = val + byte * (2 ^ ((j - 1) * 8)) + else + val = val + byte * (2 ^ ((n - j) * 8)) + end + iterator = iterator + 1 + end + + if signed then + val = val - 2 ^ (n * 8 - 1) + end + + table.insert(vars, val) + elseif opt:find('[fd]') then + local n = (opt == 'd') and 8 or 4 + local x = stream:sub(iterator, iterator + n - 1) + iterator = iterator + n + + if not endianness then + x = string.reverse(x) + end + + local sign = 1 + local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128) + for i = n - 2, 1, -1 do + mantissa = mantissa * (2 ^ 8) + string.byte(x, i) + end + + if string.byte(x, n) > 127 then + sign = -1 + end + + local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + math.floor(string.byte(x, n - 1) / ((opt == 'd') and 16 or 128)) + if exponent == 0 then + table.insert(vars, 0.0) + else + mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign + table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127))) + end + elseif opt == 's' then + local bytes = {} + for j = iterator, stream:len() do + if stream:sub(j, j) == string.char(0) then + break + end + + table.insert(bytes, stream:sub(j, j)) + end + + local str = table.concat(bytes) + iterator = iterator + str:len() + 1 + table.insert(vars, str) + elseif opt == 'c' then + local n = format:sub(i + 1):match('%d+') + table.insert(vars, stream:sub(iterator, iterator + tonumber(n))) + iterator = iterator + tonumber(n) + i = i + n:len() + end + end + + return unpack(vars) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/table.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/table.lua new file mode 100644 index 0000000..60ce55d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/table.lua @@ -0,0 +1,287 @@ +-- @docclass table + +function table.dump(t, depth) + if not depth then depth = 0 end + for k,v in pairs(t) do + str = (' '):rep(depth * 2) .. k .. ': ' + if type(v) ~= "table" then + print(str .. tostring(v)) + else + print(str) + table.dump(v, depth+1) + end + end +end + +function table.clear(t) + for k,v in pairs(t) do + t[k] = nil + end +end + +function table.copy(t) + local res = {} + for k,v in pairs(t) do + res[k] = v + end + return res +end + +function table.recursivecopy(t) + local res = {} + for k,v in pairs(t) do + if type(v) == "table" then + res[k] = table.recursivecopy(v) + else + res[k] = v + end + end + return res +end + +function table.selectivecopy(t, keys) + local res = { } + for i,v in ipairs(keys) do + res[v] = t[v] + end + return res +end + +function table.merge(t, src) + for k,v in pairs(src) do + t[k] = v + end +end + +function table.find(t, value, lowercase) + for k,v in pairs(t) do + if lowercase and type(value) == 'string' and type(v) == 'string' then + if v:lower() == value:lower() then return k end + end + if v == value then return k end + end +end + +function table.findbykey(t, key, lowercase) + for k,v in pairs(t) do + if lowercase and type(key) == 'string' and type(k) == 'string' then + if k:lower() == key:lower() then return v end + end + if k == key then return v end + end +end + +function table.contains(t, value, lowercase) + return table.find(t, value, lowercase) ~= nil +end + +function table.findkey(t, key) + if t and type(t) == 'table' then + for k,v in pairs(t) do + if k == key then return k end + end + end +end + +function table.haskey(t, key) + return table.findkey(t, key) ~= nil +end + +function table.removevalue(t, value) + for k,v in pairs(t) do + if v == value then + table.remove(t, k) + return true + end + end + return false +end + +function table.popvalue(value) + local index = nil + for k,v in pairs(t) do + if v == value or not value then + index = k + end + end + if index then + table.remove(t, index) + return true + end + return false +end + +function table.compare(t, other) + if #t ~= #other then return false end + for k,v in pairs(t) do + if v ~= other[k] then return false end + end + return true +end + +function table.empty(t) + if t and type(t) == 'table' then + return next(t) == nil + end + return true +end + +function table.permute(t, n, count) + n = n or #t + for i=1,count or n do + local j = math.random(i, n) + t[i], t[j] = t[j], t[i] + end + return t +end + +function table.findbyfield(t, fieldname, fieldvalue) + for _i,subt in pairs(t) do + if subt[fieldname] == fieldvalue then + return subt + end + end + return nil +end + +function table.size(t) + local size = 0 + for i, n in pairs(t) do + size = size + 1 + end + + return size +end + +function table.tostring(t) + local maxn = #t + local str = "" + for k,v in pairs(t) do + v = tostring(v) + if k == maxn and k ~= 1 then + str = str .. " and " .. v + elseif maxn > 1 and k ~= 1 then + str = str .. ", " .. v + else + str = str .. " " .. v + end + end + return str +end + +function table.collect(t, func) + local res = {} + for k,v in pairs(t) do + local a,b = func(k,v) + if a and b then + res[a] = b + elseif a ~= nil then + table.insert(res,a) + end + end + return res +end + +function table.equals(t, comp) + if type(t) == "table" and type(comp) == "table" then + for k,v in pairs(t) do + if v ~= comp[k] then return false end + end + end + return true +end + +function table.equal(t1,t2,ignore_mt) + local ty1 = type(t1) + local ty2 = type(t2) + if ty1 ~= ty2 then return false end + -- non-table types can be directly compared + if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end + -- as well as tables which have the metamethod __eq + local mt = getmetatable(t1) + if not ignore_mt and mt and mt.__eq then return t1 == t2 end + for k1,v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not table.equal(v1,v2) then return false end + end + for k2,v2 in pairs(t2) do + local v1 = t1[k2] + if v1 == nil or not table.equal(v1,v2) then return false end + end + return true +end + +function table.isList(t) + local size = #t + return table.size(t) == size and size > 0 +end + +function table.isStringList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'string' then + return false + end + end + return true +end + +function table.isStringPairList(t) + if not table.isList(t) then return false end + for k,v in ipairs(t) do + if type(v) ~= 'table' or #v ~= 2 or type(v[1]) ~= 'string' or type(v[2]) ~= 'string' then + return false + end + end + return true +end + +function table.encodeStringPairList(t) + local ret = "" + for k,v in ipairs(t) do + if v[2]:find("\n") then + ret = ret .. v[1] .. ":[[\n" .. v[2] .. "\n]]\n" + else + ret = ret .. v[1] .. ":" .. v[2] .. "\n" + end + end + return ret end + +function table.decodeStringPairList(l) + local ret = {} + local r = regexMatch(l, "(?:^|\\n)([^:^\n]{1,20}):?(.*)(?:$|\\n)") + local multiline = "" + local multilineKey = "" + local multilineActive = false + for k,v in ipairs(r) do + if multilineActive then + local endPos = v[1]:find("%]%]") + if endPos then + if endPos > 1 then + table.insert(ret, {multilineKey, multiline .. "\n" .. v[1]:sub(1, endPos - 1)}) + else + table.insert(ret, {multilineKey, multiline}) + end + multilineActive = false + multiline = "" + multilineKey = "" + else + if multiline:len() == 0 then + multiline = v[1] + else + multiline = multiline .. "\n" .. v[1] + end + end + else + local bracketPos = v[3]:find("%[%[") + if bracketPos == 1 then -- multiline begin + multiline = v[3]:sub(bracketPos + 2) + multilineActive = true + multilineKey = v[2] + elseif v[2]:len() > 0 and v[3]:len() > 0 then + table.insert(ret, {v[2], v[3]}) + end + end + end + return ret +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/test.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/test.lua new file mode 100644 index 0000000..2123e65 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/test.lua @@ -0,0 +1,62 @@ +Test = { + tests = {}, + activeTest = 0, + screenShot = 1 +} + +Test.Test = function(name, func) + local testId = #Test.tests + 1 + Test.tests[testId] = { + name = name, + actions = {}, + delay = 0, + start = 0 + } + local test = function(testFunc) + table.insert(Test.tests[testId].actions, {type = "test", value = testFunc}) + end + local wait = function(millis) + Test.tests[testId].delay = Test.tests[testId].delay + millis + table.insert(Test.tests[testId].actions, {type = "wait", value = Test.tests[testId].delay}) + end + local ss = function() + table.insert(Test.tests[testId].actions, {type = "screenshot"}) + end + local fail = function(message) + g_logger.fatal("Test " .. name .. " failed: " .. message) + end + func(test, wait, ss, fail) +end + +Test.run = function() + if Test.activeTest > #Test.tests then + g_logger.info("[TEST] Finished tests. Exiting...") + return g_app.exit() + end + local test = Test.tests[Test.activeTest] + if not test or #test.actions == 0 then + Test.activeTest = Test.activeTest + 1 + local nextTest = Test.tests[Test.activeTest] + if nextTest then + nextTest.start = g_clock.millis() + g_logger.info("[TEST] Starting test: " .. nextTest.name) + end + return scheduleEvent(Test.run, 500) + end + + local action = test.actions[1] + if action.type == "test" then + table.remove(test.actions, 1) + action.value() + elseif action.type == "screenshot" then + table.remove(test.actions, 1) + g_app.doScreenshot(Test.screenShot .. ".png") + Test.screenShot = Test.screenShot + 1 + elseif action.type == "wait" then + if action.value + test.start < g_clock.millis() then + table.remove(test.actions, 1) + end + end + + scheduleEvent(Test.run, 100) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/effects.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/effects.lua new file mode 100644 index 0000000..16325b5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/effects.lua @@ -0,0 +1,67 @@ +-- @docclass +g_effects = {} + +function g_effects.fadeIn(widget, time, elapsed) + if not elapsed then elapsed = 0 end + if not time then time = 300 end + widget:setOpacity(math.min(elapsed/time, 1)) + removeEvent(widget.fadeEvent) + if elapsed < time then + removeEvent(widget.fadeEvent) + widget.fadeEvent = scheduleEvent(function() + g_effects.fadeIn(widget, time, elapsed + 30) + end, 30) + else + widget.fadeEvent = nil + end +end + +function g_effects.fadeOut(widget, time, elapsed) + if not elapsed then elapsed = 0 end + if not time then time = 300 end + elapsed = math.max((1 - widget:getOpacity()) * time, elapsed) + removeEvent(widget.fadeEvent) + widget:setOpacity(math.max((time - elapsed)/time, 0)) + if elapsed < time then + widget.fadeEvent = scheduleEvent(function() + g_effects.fadeOut(widget, time, elapsed + 30) + end, 30) + else + widget.fadeEvent = nil + end +end + +function g_effects.cancelFade(widget) + removeEvent(widget.fadeEvent) + widget.fadeEvent = nil +end + +function g_effects.startBlink(widget, duration, interval, clickCancel) + duration = duration or 0 -- until stop is called + interval = interval or 500 + clickCancel = clickCancel or true + + removeEvent(widget.blinkEvent) + removeEvent(widget.blinkStopEvent) + + widget.blinkEvent = cycleEvent(function() + widget:setOn(not widget:isOn()) + end, interval) + + if duration > 0 then + widget.blinkStopEvent = scheduleEvent(function() + g_effects.stopBlink(widget) + end, duration) + end + + connect(widget, { onClick = g_effects.stopBlink }) +end + +function g_effects.stopBlink(widget) + disconnect(widget, { onClick = g_effects.stopBlink }) + removeEvent(widget.blinkEvent) + removeEvent(widget.blinkStopEvent) + widget.blinkEvent = nil + widget.blinkStopEvent = nil + widget:setOn(false) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/tooltip.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/tooltip.lua new file mode 100644 index 0000000..c390707 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/tooltip.lua @@ -0,0 +1,124 @@ +-- @docclass +g_tooltip = {} + +-- private variables +local toolTipLabel +local currentHoveredWidget + +-- private functions +local function moveToolTip(first) + if not first and (not toolTipLabel:isVisible() or toolTipLabel:getOpacity() < 0.1) then return end + + local pos = g_window.getMousePosition() + local windowSize = g_window.getSize() + local labelSize = toolTipLabel:getSize() + + pos.x = pos.x + 1 + pos.y = pos.y + 1 + + if windowSize.width - (pos.x + labelSize.width) < 10 then + pos.x = pos.x - labelSize.width - 3 + else + pos.x = pos.x + 10 + end + + if windowSize.height - (pos.y + labelSize.height) < 10 then + pos.y = pos.y - labelSize.height - 3 + else + pos.y = pos.y + 10 + end + + toolTipLabel:setPosition(pos) +end + +local function onWidgetHoverChange(widget, hovered) + if hovered then + if widget.tooltip and not g_mouse.isPressed() then + g_tooltip.display(widget.tooltip) + currentHoveredWidget = widget + end + else + if widget == currentHoveredWidget then + g_tooltip.hide() + currentHoveredWidget = nil + end + end +end + +local function onWidgetStyleApply(widget, styleName, styleNode) + if styleNode.tooltip then + widget.tooltip = styleNode.tooltip + end +end + +-- public functions +function g_tooltip.init() + connect(UIWidget, { onStyleApply = onWidgetStyleApply, + onHoverChange = onWidgetHoverChange}) + + addEvent(function() + toolTipLabel = g_ui.createWidget('UILabel', rootWidget) + toolTipLabel:setId('toolTip') + toolTipLabel:setBackgroundColor('#111111cc') + toolTipLabel:setTextAlign(AlignCenter) + toolTipLabel:hide() + end) +end + +function g_tooltip.terminate() + disconnect(UIWidget, { onStyleApply = onWidgetStyleApply, + onHoverChange = onWidgetHoverChange }) + + currentHoveredWidget = nil + toolTipLabel:destroy() + toolTipLabel = nil + + g_tooltip = nil +end + +function g_tooltip.display(text) + if text == nil or text:len() == 0 then return end + if not toolTipLabel then return end + + toolTipLabel:setText(text) + toolTipLabel:resizeToText() + toolTipLabel:resize(toolTipLabel:getWidth() + 4, toolTipLabel:getHeight() + 4) + toolTipLabel:show() + toolTipLabel:raise() + toolTipLabel:enable() + g_effects.fadeIn(toolTipLabel, 100) + moveToolTip(true) + + connect(rootWidget, { + onMouseMove = moveToolTip, + }) +end + +function g_tooltip.hide() + g_effects.fadeOut(toolTipLabel, 100) + + disconnect(rootWidget, { + onMouseMove = moveToolTip, + }) +end + + +-- @docclass UIWidget @{ + +-- UIWidget extensions +function UIWidget:setTooltip(text) + self.tooltip = text +end + +function UIWidget:removeTooltip() + self.tooltip = nil +end + +function UIWidget:getTooltip() + return self.tooltip +end + +-- @} + +g_tooltip.init() +connect(g_app, { onTerminate = g_tooltip.terminate }) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uibutton.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uibutton.lua new file mode 100644 index 0000000..8a7d125 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uibutton.lua @@ -0,0 +1,12 @@ +-- @docclass +UIButton = extends(UIWidget, "UIButton") + +function UIButton.create() + local button = UIButton.internalCreate() + button:setFocusable(false) + return button +end + +function UIButton:onMouseRelease(pos, button) + return self:isPressed() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicheckbox.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicheckbox.lua new file mode 100644 index 0000000..195c593 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicheckbox.lua @@ -0,0 +1,13 @@ +-- @docclass +UICheckBox = extends(UIWidget, "UICheckBox") + +function UICheckBox.create() + local checkbox = UICheckBox.internalCreate() + checkbox:setFocusable(false) + checkbox:setTextAlign(AlignLeft) + return checkbox +end + +function UICheckBox:onClick() + self:setChecked(not self:isChecked()) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicombobox.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicombobox.lua new file mode 100644 index 0000000..08fd9a2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uicombobox.lua @@ -0,0 +1,184 @@ +-- @docclass +UIComboBox = extends(UIWidget, "UIComboBox") + +function UIComboBox.create() + local combobox = UIComboBox.internalCreate() + combobox:setFocusable(false) + combobox.options = {} + combobox.currentIndex = -1 + combobox.mouseScroll = true + combobox.menuScroll = false + combobox.menuHeight = 100 + combobox.menuScrollStep = 0 + return combobox +end + +function UIComboBox:clearOptions() + self.options = {} + self.currentIndex = -1 + self:clearText() +end + +function UIComboBox:clear() + return self:clearOptions() +end + +function UIComboBox:getOptionsCount() + return #self.options +end + +function UIComboBox:isOption(text) + if not self.options then return false end + for i,v in ipairs(self.options) do + if v.text == text then + return true + end + end + return false +end + +function UIComboBox:setOption(text, dontSignal) + self:setCurrentOption(text, dontSignal) +end + +function UIComboBox:setCurrentOption(text, dontSignal) + if not self.options then return end + for i,v in ipairs(self.options) do + if v.text == text and self.currentIndex ~= i then + self.currentIndex = i + self:setText(text) + if not dontSignal then + signalcall(self.onOptionChange, self, text, v.data) + end + return + end + end +end + +function UIComboBox:updateCurrentOption(newText) + self.options[self.currentIndex].text = newText + self:setText(newText) +end + +function UIComboBox:setCurrentOptionByData(data, dontSignal) + if not self.options then return end + for i,v in ipairs(self.options) do + if v.data == data and self.currentIndex ~= i then + self.currentIndex = i + self:setText(v.text) + if not dontSignal then + signalcall(self.onOptionChange, self, v.text, v.data) + end + return + end + end +end + +function UIComboBox:setCurrentIndex(index, dontSignal) + if index >= 1 and index <= #self.options then + local v = self.options[index] + self.currentIndex = index + self:setText(v.text) + if not dontSignal then + signalcall(self.onOptionChange, self, v.text, v.data) + end + end +end + +function UIComboBox:getCurrentOption() + if table.haskey(self.options, self.currentIndex) then + return self.options[self.currentIndex] + end +end + +function UIComboBox:addOption(text, data) + table.insert(self.options, { text = text, data = data }) + local index = #self.options + if index == 1 then self:setCurrentOption(text) end + return index +end + +function UIComboBox:removeOption(text) + for i,v in ipairs(self.options) do + if v.text == text then + table.remove(self.options, i) + if self.currentIndex == i then + self:setCurrentIndex(1) + elseif self.currentIndex > i then + self.currentIndex = self.currentIndex - 1 + end + return + end + end +end + +function UIComboBox:onMousePress(mousePos, mouseButton) + local menu + if self.menuScroll then + menu = g_ui.createWidget(self:getStyleName() .. 'PopupScrollMenu') + menu:setHeight(self.menuHeight) + if self.menuScrollStep > 0 then + menu:setScrollbarStep(self.menuScrollStep) + end + else + menu = g_ui.createWidget(self:getStyleName() .. 'PopupMenu') + end + menu:setId(self:getId() .. 'PopupMenu') + for i,v in ipairs(self.options) do + menu:addOption(v.text, function() self:setCurrentOption(v.text) end) + end + menu:setWidth(self:getWidth()) + menu:display({ x = self:getX(), y = self:getY() + self:getHeight() }) + connect(menu, { onDestroy = function() self:setOn(false) end }) + self:setOn(true) + return true +end + +function UIComboBox:onMouseWheel(mousePos, direction) + if not self.mouseScroll or self.disableScroll then + return false + end + if direction == MouseWheelUp and self.currentIndex > 1 then + self:setCurrentIndex(self.currentIndex - 1) + elseif direction == MouseWheelDown and self.currentIndex < #self.options then + self:setCurrentIndex(self.currentIndex + 1) + end + return true +end + +function UIComboBox:onStyleApply(styleName, styleNode) + if styleNode.options then + for k,option in pairs(styleNode.options) do + self:addOption(option) + end + end + + if styleNode.data then + for k,data in pairs(styleNode.data) do + local option = self.options[k] + if option then + option.data = data + end + end + end + + for name,value in pairs(styleNode) do + if name == 'mouse-scroll' then + self.mouseScroll = value + elseif name == 'menu-scroll' then + self.menuScroll = value + elseif name == 'menu-height' then + self.menuHeight = value + elseif name == 'menu-scroll-step' then + self.menuScrollStep = value + end + end +end + +function UIComboBox:setMouseScroll(scroll) + self.mouseScroll = scroll +end + +function UIComboBox:canMouseScroll() + return self.mouseScroll +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiimageview.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiimageview.lua new file mode 100644 index 0000000..7a2e5fe --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiimageview.lua @@ -0,0 +1,99 @@ +-- @docclass +UIImageView = extends(UIWidget, "UIImageView") + +function UIImageView.create() + local imageView = UIImageView.internalCreate() + imageView.zoom = 1 + imageView.minZoom = math.pow(10, -2) + imageView.maxZoom = math.pow(10, 2) + imageView:setClipping(true) + return imageView +end + +function UIImageView:getDefaultZoom() + local width = self:getWidth() + local height = self:getHeight() + local textureWidth = self:getImageTextureWidth() + local textureHeight = self:getImageTextureHeight() + local zoomX = width / textureWidth + local zoomY = height / textureHeight + return math.min(zoomX, zoomY) +end + +function UIImageView:getImagePosition(x, y) + x = x or self:getWidth() / 2 + y = y or self:getHeight() / 2 + local offsetX = self:getImageOffsetX() + local offsetY = self:getImageOffsetY() + local posX = (x - offsetX) / self.zoom + local posY = (y - offsetY) / self.zoom + return posX, posY +end + +function UIImageView:setImage(image) + self:setImageSource(image) + local zoom = self:getDefaultZoom() + self:setZoom(zoom) + self:center() +end + +function UIImageView:setZoom(zoom, x, y) + zoom = math.max(math.min(zoom, self.maxZoom), self.minZoom) + local posX, posY = self:getImagePosition(x, y) + local textureWidth = self:getImageTextureWidth() + local textureHeight = self:getImageTextureHeight() + local imageWidth = textureWidth * zoom + local imageHeight = textureHeight * zoom + self:setImageWidth(imageWidth) + self:setImageHeight(imageHeight) + self.zoom = zoom + self:move(posX, posY, x, y) +end + +function UIImageView:zoomIn(x, y) + local zoom = self.zoom * 1.1 + self:setZoom(zoom, x, y) +end + +function UIImageView:zoomOut(x, y) + local zoom = self.zoom / 1.1 + self:setZoom(zoom, x, y) +end + +function UIImageView:center() + self:move(self:getImageTextureWidth() / 2, self:getImageTextureHeight() / 2) +end + +function UIImageView:move(x, y, centerX, centerY) + x = math.max(math.min(x, self:getImageTextureWidth()), 0) + y = math.max(math.min(y, self:getImageTextureHeight()), 0) + local centerX = centerX or self:getWidth() / 2 + local centerY = centerY or self:getHeight() / 2 + local offsetX = centerX - x * self.zoom + local offsetY = centerY - y * self.zoom + self:setImageOffset({x=offsetX, y=offsetY}) +end + +function UIImageView:onDragEnter(pos) + return true +end + +function UIImageView:onDragMove(pos, moved) + local posX, posY = self:getImagePosition() + self:move(posX - moved.x / self.zoom, posY - moved.y / self.zoom) + return true +end + +function UIImageView:onDragLeave(widget, pos) + return true +end + +function UIImageView:onMouseWheel(mousePos, direction) + local x = mousePos.x - self:getX() + local y = mousePos.y - self:getY() + if direction == MouseWheelUp then + self:zoomIn(x, y) + elseif direction == MouseWheelDown then + self:zoomOut(x, y) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiinputbox.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiinputbox.lua new file mode 100644 index 0000000..1db361e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiinputbox.lua @@ -0,0 +1,114 @@ +if not UIWindow then dofile 'uiwindow' end + +-- @docclass +UIInputBox = extends(UIWindow, "UIInputBox") + +function UIInputBox.create(title, okCallback, cancelCallback) + local inputBox = UIInputBox.internalCreate() + + inputBox:setText(title) + inputBox.inputs = {} + inputBox.onEnter = function() + local results = {} + for _,func in pairs(inputBox.inputs) do + table.insert(results, func()) + end + okCallback(unpack(results)) + inputBox:destroy() + end + inputBox.onEscape = function() + if cancelCallback then + cancelCallback() + end + inputBox:destroy() + end + + return inputBox +end + +function UIInputBox:addLabel(text) + local label = g_ui.createWidget('InputBoxLabel', self) + label:setText(text) + return label +end + +function UIInputBox:addLineEdit(labelText, defaultText, maxLength) + if labelText then self:addLabel(labelText) end + local lineEdit = g_ui.createWidget('InputBoxLineEdit', self) + if defaultText then lineEdit:setText(defaultText) end + if maxLength then lineEdit:setMaxLength(maxLength) end + table.insert(self.inputs, function() return lineEdit:getText() end) + return lineEdit +end + +function UIInputBox:addTextEdit(labelText, defaultText, maxLength, visibleLines) + if labelText then self:addLabel(labelText) end + local textEdit = g_ui.createWidget('InputBoxTextEdit', self) + if defaultText then textEdit:setText(defaultText) end + if maxLength then textEdit:setMaxLength(maxLength) end + visibleLines = visibleLines or 1 + textEdit:setHeight(textEdit:getHeight() * visibleLines) + table.insert(self.inputs, function() return textEdit:getText() end) + return textEdit +end + +function UIInputBox:addCheckBox(text, checked) + local checkBox = g_ui.createWidget('InputBoxCheckBox', self) + checkBox:setText(text) + checkBox:setChecked(checked) + table.insert(self.inputs, function() return checkBox:isChecked() end) + return checkBox +end + +function UIInputBox:addComboBox(labelText, ...) + if labelText then self:addLabel(labelText) end + local comboBox = g_ui.createWidget('InputBoxComboBox', self) + local options = {...} + for i=1,#options do + comboBox:addOption(options[i]) + end + table.insert(self.inputs, function() return comboBox:getCurrentOption() end) + return comboBox +end + +function UIInputBox:addSpinBox(labelText, minimum, maximum, value, step) + if labelText then self:addLabel(labelText) end + local spinBox = g_ui.createWidget('InputBoxSpinBox', self) + spinBox:setMinimum(minimum) + spinBox:setMaximum(maximum) + spinBox:setValue(value) + spinBox:setStep(step) + table.insert(self.inputs, function() return spinBox:getValue() end) + return spinBox +end + +function UIInputBox:display(okButtonText, cancelButtonText) + okButtonText = okButtonText or tr('Ok') + cancelButtonText = cancelButtonText or tr('Cancel') + + local buttonsWidget = g_ui.createWidget('InputBoxButtonsPanel', self) + local okButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + okButton:setText(okButtonText) + okButton.onClick = self.onEnter + + local cancelButton = g_ui.createWidget('InputBoxButton', buttonsWidget) + cancelButton:setText(cancelButtonText) + cancelButton.onClick = self.onEscape + + buttonsWidget:setHeight(okButton:getHeight()) + + rootWidget:addChild(self) + self:setStyle('InputBoxWindow') +end + +function displayTextInputBox(title, label, okCallback, cancelCallback) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addLineEdit(label) + inputBox:display() +end + +function displayNumberInputBox(title, label, okCallback, cancelCallback, min, max, value, step) + local inputBox = UIInputBox.create(title, okCallback, cancelCallback) + inputBox:addSpinBox(label, min, max, value, step) + inputBox:display() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uilabel.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uilabel.lua new file mode 100644 index 0000000..ecd72bc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uilabel.lua @@ -0,0 +1,10 @@ +-- @docclass +UILabel = extends(UIWidget, "UILabel") + +function UILabel.create() + local label = UILabel.internalCreate() + label:setPhantom(true) + label:setFocusable(false) + label:setTextAlign(AlignLeft) + return label +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimessagebox.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimessagebox.lua new file mode 100644 index 0000000..62f84a2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimessagebox.lua @@ -0,0 +1,96 @@ +if not UIWindow then dofile 'uiwindow' end + +-- @docclass +UIMessageBox = extends(UIWindow, "UIMessageBox") + +-- messagebox cannot be created from otui files +UIMessageBox.create = nil + +function UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback) + local messageBox = UIMessageBox.internalCreate() + rootWidget:addChild(messageBox) + + messageBox:setStyle('MainWindow') + messageBox:setText(title) + + local messageLabel = g_ui.createWidget('MessageBoxLabel', messageBox) + messageLabel:setText(message) + + local buttonsWidth = 0 + local buttonsHeight = 0 + + local anchor = AnchorRight + if buttons.anchor then anchor = buttons.anchor end + + local buttonHolder = g_ui.createWidget('MessageBoxButtonHolder', messageBox) + buttonHolder:addAnchor(anchor, 'parent', anchor) + + for i=1,#buttons do + local button = messageBox:addButton(buttons[i].text, buttons[i].callback) + if i == 1 then + button:setMarginLeft(0) + button:addAnchor(AnchorBottom, 'parent', AnchorBottom) + button:addAnchor(AnchorLeft, 'parent', AnchorLeft) + buttonsHeight = button:getHeight() + else + button:addAnchor(AnchorBottom, 'prev', AnchorBottom) + button:addAnchor(AnchorLeft, 'prev', AnchorRight) + end + buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + end + + buttonHolder:setWidth(buttonsWidth) + buttonHolder:setHeight(buttonsHeight) + + if onEnterCallback then connect(messageBox, { onEnter = onEnterCallback }) end + if onEscapeCallback then connect(messageBox, { onEscape = onEscapeCallback }) end + + messageBox:setWidth(math.max(messageLabel:getWidth(), messageBox:getTextSize().width, buttonHolder:getWidth()) + messageBox:getPaddingLeft() + messageBox:getPaddingRight()) + messageBox:setHeight(messageLabel:getHeight() + messageBox:getPaddingTop() + messageBox:getPaddingBottom() + buttonHolder:getHeight() + buttonHolder:getMarginTop()) + return messageBox +end + +function displayInfoBox(title, message) + local messageBox + local defaultCallback = function() messageBox:ok() end + messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayErrorBox(title, message) + local messageBox + local defaultCallback = function() messageBox:ok() end + messageBox = UIMessageBox.display(title, message, {{text='Ok', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayCancelBox(title, message) + local messageBox + local defaultCallback = function() messageBox:cancel() end + messageBox = UIMessageBox.display(title, message, {{text='Cancel', callback=defaultCallback}}, defaultCallback, defaultCallback) + return messageBox +end + +function displayGeneralBox(title, message, buttons, onEnterCallback, onEscapeCallback) + return UIMessageBox.display(title, message, buttons, onEnterCallback, onEscapeCallback) +end + +function UIMessageBox:addButton(text, callback) + local buttonHolder = self:getChildById('buttonHolder') + local button = g_ui.createWidget('MessageBoxButton', buttonHolder) + button:setText(text) + connect(button, { onClick = callback }) + return button +end + +function UIMessageBox:ok() + signalcall(self.onOk, self) + self.onOk = nil + self:destroy() +end + +function UIMessageBox:cancel() + signalcall(self.onCancel, self) + self.onCancel = nil + self:destroy() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindow.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindow.lua new file mode 100644 index 0000000..0c9d7cd --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindow.lua @@ -0,0 +1,516 @@ +-- @docclass +UIMiniWindow = extends(UIWindow, "UIMiniWindow") + +function UIMiniWindow.create() + local miniwindow = UIMiniWindow.internalCreate() + miniwindow.UIMiniWindowContainer = true + return miniwindow +end + +function UIMiniWindow:open(dontSave) + self:setVisible(true) + + if not dontSave then + self:setSettings({closed = false}) + end + + signalcall(self.onOpen, self) +end + +function UIMiniWindow:close(dontSave) + if not self:isExplicitlyVisible() then return end + if self.forceOpen then return end + self:setVisible(false) + + if not dontSave then + self:setSettings({closed = true}) + end + + signalcall(self.onClose, self) +end + +function UIMiniWindow:minimize(dontSave) + self:setOn(true) + self:getChildById('contentsPanel'):hide() + self:getChildById('miniwindowScrollBar'):hide() + self:getChildById('bottomResizeBorder'):hide() + if self.minimizeButton then + self.minimizeButton:setOn(true) + end + self.maximizedHeight = self:getHeight() + self:setHeight(self.minimizedHeight) + + if not dontSave then + self:setSettings({minimized = true}) + end + + signalcall(self.onMinimize, self) +end + +function UIMiniWindow:maximize(dontSave) + self:setOn(false) + self:getChildById('contentsPanel'):show() + self:getChildById('miniwindowScrollBar'):show() + self:getChildById('bottomResizeBorder'):show() + if self.minimizeButton then + self.minimizeButton:setOn(false) + end + self:setHeight(self:getSettings('height') or self.maximizedHeight) + + if not dontSave then + self:setSettings({minimized = false}) + end + + local parent = self:getParent() + if parent and parent:getClassName() == 'UIMiniWindowContainer' then + parent:fitAll(self) + end + + signalcall(self.onMaximize, self) +end + +function UIMiniWindow:lock(dontSave) + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton:setOn(true) + end + self:setDraggable(false) + if not dontsave then + self:setSettings({locked = true}) + end + + signalcall(self.onLockChange, self) +end + +function UIMiniWindow:unlock(dontSave) + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton:setOn(false) + end + self:setDraggable(true) + if not dontsave then + self:setSettings({locked = false}) + end + signalcall(self.onLockChange, self) +end + +function UIMiniWindow:setup() + self:getChildById('closeButton').onClick = + function() + self:close() + end + if self.forceOpen then + if self.closeButton then + self.closeButton:hide() + end + end + + if(self.minimizeButton) then + self.minimizeButton.onClick = + function() + if self:isOn() then + self:maximize() + else + self:minimize() + end + end + end + + local lockButton = self:getChildById('lockButton') + if lockButton then + lockButton.onClick = + function () + if self:isDraggable() then + self:lock() + else + self:unlock() + end + end + end + + self:getChildById('miniwindowTopBar').onDoubleClick = + function() + if self:isOn() then + self:maximize() + else + self:minimize() + end + end + + local oldParent = self:getParent() + + + local settings = {} + if g_settings.getNodeSize('MiniWindows') < 100 then + settings = g_settings.getNode('MiniWindows') + end + + if settings then + local selfSettings = settings[self:getId()] + if selfSettings then + if selfSettings.parentId then + local parent = rootWidget:recursiveGetChildById(selfSettings.parentId) + if parent then + if parent:getClassName() == 'UIMiniWindowContainer' and selfSettings.index and parent:isOn() then + self.miniIndex = selfSettings.index + parent:scheduleInsert(self, selfSettings.index) + elseif selfSettings.position then + self:setParent(parent, true) + self:setPosition(topoint(selfSettings.position)) + end + end + end + + if selfSettings.minimized then + self:minimize(true) + else + if selfSettings.height and self:isResizeable() then + self:setHeight(selfSettings.height) + elseif selfSettings.height and not self:isResizeable() then + self:eraseSettings({height = true}) + end + end + if selfSettings.closed and not self.forceOpen and not self.containerWindow then + self:close(true) + end + + if selfSettings.locked then + self:lock(true) + end + else + if not self.forceOpen and self.autoOpen ~= nil and (self.autoOpen == 0 or self.autoOpen == false) and not self.containerWindow then + self:close(true) + end + end + end + + local newParent = self:getParent() + + self.miniLoaded = true + + if self.save then + if oldParent and oldParent:getClassName() == 'UIMiniWindowContainer' and not self.containerWindow then + addEvent(function() oldParent:order() end) + end + if newParent and newParent:getClassName() == 'UIMiniWindowContainer' and newParent ~= oldParent then + addEvent(function() newParent:order() end) + end + end + + self:fitOnParent() +end + +function UIMiniWindow:onVisibilityChange(visible) + self:fitOnParent() +end + +function UIMiniWindow:onDragEnter(mousePos) + local parent = self:getParent() + if not parent then return false end + + if parent:getClassName() == 'UIMiniWindowContainer' then + local containerParent = parent:getParent():getParent() + parent:removeChild(self) + containerParent:addChild(self) + parent:saveChildren() + end + + local oldPos = self:getPosition() + self.movingReference = { x = mousePos.x - oldPos.x, y = mousePos.y - oldPos.y } + self:setPosition(oldPos) + self.free = true + return true +end + +function UIMiniWindow:onDragLeave(droppedWidget, mousePos) + local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos) + local dropInPanel = 0 + for i=1,#children do + local child = children[i] + if child:getId():contains('gameLeftPanel') or child:getId():contains('gameRightPanel') then + dropInPanel = 1 + if self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.movedWidget = nil + self.setMovedChildMargin = nil + self.movedOldMargin = nil + self.movedIndex = nil + end + + UIWindow:onDragLeave(self, droppedWidget, mousePos) + self:saveParent(self:getParent()) + break + end + end + if dropInPanel == 0 then + tmpp = self + if(modules.game_interface.getLeftPanel():isVisible()) then + if modules.game_interface.getRootPanel():getWidth() / 2 < mousePos.x then + addEvent(function() tmpp:setParent(modules.game_interface.getRightPanel()) + if tmpp.movedWidget then + tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0) + tmpp.movedWidget = nil + tmpp.setMovedChildMargin = nil + tmpp.movedOldMargin = nil + tmpp.movedIndex = nil + end + + UIWindow:onDragLeave(tmpp, droppedWidget, mousePos) + tmpp:saveParent(tmpp:getParent()) + end) + else + addEvent(function() tmpp:setParent(modules.game_interface.getLeftPanel()) + if tmpp.movedWidget then + tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0) + tmpp.movedWidget = nil + tmpp.setMovedChildMargin = nil + tmpp.movedOldMargin = nil + tmpp.movedIndex = nil + end + + UIWindow:onDragLeave(tmpp, droppedWidget, mousePos) + tmpp:saveParent(tmpp:getParent()) + end) + end + else + addEvent(function() tmpp:setParent(modules.game_interface.getRightPanel()) +if tmpp.movedWidget then + tmpp.setMovedChildMargin(tmpp.movedOldMargin or 0) + tmpp.movedWidget = nil + tmpp.setMovedChildMargin = nil + tmpp.movedOldMargin = nil + tmpp.movedIndex = nil + end + + UIWindow:onDragLeave(tmpp, droppedWidget, mousePos) + tmpp:saveParent(tmpp:getParent()) + end) + end + end +end + +function UIMiniWindow:onDragMove(mousePos, mouseMoved) + local oldMousePosY = mousePos.y - mouseMoved.y + local children = rootWidget:recursiveGetChildrenByMarginPos(mousePos) + local overAnyWidget = false + for i=1,#children do + local child = children[i] + if child:getParent():getClassName() == 'UIMiniWindowContainer' then + overAnyWidget = true + + local childCenterY = child:getY() + child:getHeight() / 2 + if child == self.movedWidget and mousePos.y < childCenterY and oldMousePosY < childCenterY then + break + end + + if self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.setMovedChildMargin = nil + end + + if mousePos.y < childCenterY then + self.movedOldMargin = child:getMarginTop() + self.setMovedChildMargin = function(v) child:setMarginTop(v) end + self.movedIndex = 0 + else + self.movedOldMargin = child:getMarginBottom() + self.setMovedChildMargin = function(v) child:setMarginBottom(v) end + self.movedIndex = 1 + end + + self.movedWidget = child + self.setMovedChildMargin(self:getHeight()) + break + end + end + + if not overAnyWidget and self.movedWidget then + self.setMovedChildMargin(self.movedOldMargin or 0) + self.movedWidget = nil + end + + return UIWindow.onDragMove(self, mousePos, mouseMoved) +end + +function UIMiniWindow:onMousePress() + local parent = self:getParent() + if not parent then return false end + if parent:getClassName() ~= 'UIMiniWindowContainer' then + self:raise() + return true + end +end + +function UIMiniWindow:onFocusChange(focused) + if not focused then return end + local parent = self:getParent() + if parent and parent:getClassName() ~= 'UIMiniWindowContainer' then + self:raise() + end +end + +function UIMiniWindow:onHeightChange(height) + if not self:isOn() then + self:setSettings({height = height}) + end + self:fitOnParent() +end + +function UIMiniWindow:getSettings(name) + if not self.save then return nil end + local settings = g_settings.getNode('MiniWindows') + if settings then + local selfSettings = settings[self:getId()] + if selfSettings then + return selfSettings[name] + end + end + return nil +end + +function UIMiniWindow:setSettings(data) + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + if not settings[id] then + settings[id] = {} + end + + for key,value in pairs(data) do + settings[id][key] = value + end + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:eraseSettings(data) + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + if not settings[id] then + settings[id] = {} + end + + for key,value in pairs(data) do + settings[id][key] = nil + end + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:clearSettings() + if not self.save then return end + + local settings = g_settings.getNode('MiniWindows') + if not settings then + settings = {} + end + + local id = self:getId() + settings[id] = {} + + g_settings.setNode('MiniWindows', settings) +end + +function UIMiniWindow:saveParent(parent) + local parent = self:getParent() + if parent then + if parent:getClassName() == 'UIMiniWindowContainer' then + parent:saveChildren() + else + self:saveParentPosition(parent:getId(), self:getPosition()) + end + end +end + +function UIMiniWindow:saveParentPosition(parentId, position) + local selfSettings = {} + selfSettings.parentId = parentId + selfSettings.position = pointtostring(position) + self:setSettings(selfSettings) +end + +function UIMiniWindow:saveParentIndex(parentId, index) + local selfSettings = {} + selfSettings.parentId = parentId + selfSettings.index = index + self:setSettings(selfSettings) + self.miniIndex = index +end + +function UIMiniWindow:disableResize() + self:getChildById('bottomResizeBorder'):disable() +end + +function UIMiniWindow:enableResize() + self:getChildById('bottomResizeBorder'):enable() +end + +function UIMiniWindow:fitOnParent() + local parent = self:getParent() + if self:isVisible() and parent and parent:getClassName() == 'UIMiniWindowContainer' then + parent:fitAll(self) + end +end + +function UIMiniWindow:setParent(parent, dontsave) + UIWidget.setParent(self, parent) + if not dontsave then + self:saveParent(parent) + end + self:fitOnParent() +end + +function UIMiniWindow:setHeight(height) + UIWidget.setHeight(self, height) + signalcall(self.onHeightChange, self, height) +end + +function UIMiniWindow:setContentHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setParentSize(minHeight + height) +end + +function UIMiniWindow:setContentMinimumHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setMinimum(minHeight + height) +end + +function UIMiniWindow:setContentMaximumHeight(height) + local contentsPanel = self:getChildById('contentsPanel') + local minHeight = contentsPanel:getMarginTop() + contentsPanel:getMarginBottom() + contentsPanel:getPaddingTop() + contentsPanel:getPaddingBottom() + + local resizeBorder = self:getChildById('bottomResizeBorder') + resizeBorder:setMaximum(minHeight + height) +end + +function UIMiniWindow:getMinimumHeight() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:getMinimum() +end + +function UIMiniWindow:getMaximumHeight() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:getMaximum() +end + +function UIMiniWindow:isResizeable() + local resizeBorder = self:getChildById('bottomResizeBorder') + return resizeBorder:isExplicitlyVisible() and resizeBorder:isEnabled() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindowcontainer.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindowcontainer.lua new file mode 100644 index 0000000..6c6eea1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiminiwindowcontainer.lua @@ -0,0 +1,228 @@ +-- @docclass +UIMiniWindowContainer = extends(UIWidget, "UIMiniWindowContainer") + +function UIMiniWindowContainer.create() + local container = UIMiniWindowContainer.internalCreate() + container.scheduledWidgets = {} + container:setFocusable(false) + container:setPhantom(true) + return container +end + +-- TODO: connect to window onResize event +-- TODO: try to resize another widget? +-- TODO: try to find another panel? +function UIMiniWindowContainer:fitAll(noRemoveChild) + if not self:isVisible() then + return + end + + if not noRemoveChild then + local children = self:getChildren() + if #children > 0 then + noRemoveChild = children[#children] + else + return + end + end + + local sumHeight = 0 + local children = self:getChildren() + for i=1,#children do + if children[i]:isVisible() then + sumHeight = sumHeight + children[i]:getHeight() + end + end + + local selfHeight = self:getHeight() - (self:getPaddingTop() + self:getPaddingBottom()) + if sumHeight <= selfHeight then + return + end + + local removeChildren = {} + + -- try to resize noRemoveChild + local maximumHeight = selfHeight - (sumHeight - noRemoveChild:getHeight()) + if noRemoveChild:isResizeable() and noRemoveChild:getMinimumHeight() <= maximumHeight then + sumHeight = sumHeight - noRemoveChild:getHeight() + maximumHeight + addEvent(function() noRemoveChild:setHeight(maximumHeight) end) + end + + -- try to remove no-save widget + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and not child.save then + local childHeight = child:getHeight() + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- try to remove save widget, not forceOpen + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and child:isVisible() and not child.forceOpen then + local childHeight = child:getHeight() + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- try to remove save widget + for i=#children,1,-1 do + if sumHeight <= selfHeight then + break + end + + local child = children[i] + if child ~= noRemoveChild and child:isVisible() then + local childHeight = child:getHeight() - 50 + sumHeight = sumHeight - childHeight + table.insert(removeChildren, child) + end + end + + -- close widgets + for i=1,#removeChildren do + if removeChildren[i].forceOpen then + removeChildren[i]:minimize(true) + else + removeChildren[i]:close() + end + end +end + +function UIMiniWindowContainer:onDrop(widget, mousePos) + if widget.UIMiniWindowContainer then + local oldParent = widget:getParent() + if oldParent == self then + return true + end + + if oldParent then + oldParent:removeChild(widget) + end + + if widget.movedWidget then + local index = self:getChildIndex(widget.movedWidget) + self:insertChild(index + widget.movedIndex, widget) + else + self:addChild(widget) + end + + self:fitAll(widget) + return true + end +end + +function UIMiniWindowContainer:moveTo(newPanel) + if not newPanel or newPanel == self then + return + end + local children = self:getChildByIndex(1) + while children do + newPanel:addChild(children) + children = self:getChildByIndex(1) + end + newPanel:fitAll() +end + +function UIMiniWindowContainer:swapInsert(widget, index) + local oldParent = widget:getParent() + local oldIndex = self:getChildIndex(widget) + + if oldParent == self and oldIndex ~= index then + local oldWidget = self:getChildByIndex(index) + if oldWidget then + self:removeChild(oldWidget) + self:insertChild(oldIndex, oldWidget) + end + self:removeChild(widget) + self:insertChild(index, widget) + end +end + +function UIMiniWindowContainer:scheduleInsert(widget, index) + if index - 1 > self:getChildCount() then + if self.scheduledWidgets[index] then + pdebug('replacing scheduled widget id ' .. widget:getId()) + end + self.scheduledWidgets[index] = widget + else + local oldParent = widget:getParent() + if oldParent ~= self then + if oldParent then + oldParent:removeChild(widget) + end + self:insertChild(index, widget) + + while true do + local placed = false + for nIndex,nWidget in pairs(self.scheduledWidgets) do + if nIndex - 1 <= self:getChildCount() then + local oldParent = nWidget:getParent() + if oldParent ~= self then + if oldParent then + oldParent:removeChild(nWidget) + end + self:insertChild(nIndex, nWidget) + else + self:moveChildToIndex(nWidget, nIndex) + end + self.scheduledWidgets[nIndex] = nil + placed = true + break + end + end + if not placed then break end + end + end + end +end + +function UIMiniWindowContainer:order() + local children = self:getChildren() + for i=1,#children do + if not children[i].miniLoaded then return end + end + + table.sort(children, function(a, b) + local indexA = a.miniIndex or a.autoOpen or 999 + local indexB = b.miniIndex or b.autoOpen or 999 + return indexA < indexB + end) + + self:reorderChildren(children) + local ignoreIndex = 0 + for i=1,#children do + if children[i].save then + children[i].miniIndex = i - ignoreIndex + else + ignoreIndex = ignoreIndex + 1 + end + end +end + +function UIMiniWindowContainer:saveChildren() + local children = self:getChildren() + local ignoreIndex = 0 + for i=1,#children do + if children[i].save then + children[i]:saveParentIndex(self:getId(), i - ignoreIndex) + else + ignoreIndex = ignoreIndex + 1 + end + end +end + +function UIMiniWindowContainer:onGeometryChange() + self:fitAll() +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimovabletabbar.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimovabletabbar.lua new file mode 100644 index 0000000..c3e283e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uimovabletabbar.lua @@ -0,0 +1,505 @@ +-- @docclass +UIMoveableTabBar = extends(UIWidget, "UIMoveableTabBar") + +-- private functions +local function onTabClick(tab) + tab.tabBar:selectTab(tab) +end + +local function updateMargins(tabBar) + if #tabBar.tabs == 0 then return end + + local currentMargin = 0 + for i = 1, #tabBar.tabs do + tabBar.tabs[i]:setMarginLeft(currentMargin) + currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth() + end +end + +local function updateNavigation(tabBar) + if tabBar.prevNavigation then + if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then + tabBar.prevNavigation:enable() + else + tabBar.prevNavigation:disable() + end + end + + if tabBar.nextNavigation then + if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then + tabBar.nextNavigation:enable() + else + tabBar.nextNavigation:disable() + end + end +end + +local function updateIndexes(tabBar, tab, xoff) + local tabs = tabBar.tabs + local currentMargin = 0 + local prevIndex = table.find(tabs, tab) + local newIndex = prevIndex + local xmid = xoff + tab:getWidth()/2 + for i = 1, #tabs do + local nextTab = tabs[i] + if xmid >= currentMargin + nextTab:getWidth()/2 then + newIndex = table.find(tabs, nextTab) + end + currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth() + end + if newIndex ~= prevIndex then + table.remove(tabs, table.find(tabs, tab)) + table.insert(tabs, newIndex, tab) + end + updateNavigation(tabBar) +end + +local function getMaxMargin(tabBar, tab) + if #tabBar.tabs == 0 then return 0 end + + local maxMargin = 0 + for i = 1, #tabBar.tabs do + if tabBar.tabs[i] ~= tab then + maxMargin = maxMargin + tabBar.tabs[i]:getWidth() + end + end + return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1) +end + +local function updateTabs(tabBar) + if #tabBar.postTabs > 0 then + local i = 1 + while i <= #tabBar.postTabs do + local tab = tabBar.postTabs[i] + if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then + break + end + + table.remove(tabBar.postTabs, i) + table.insert(tabBar.tabs, tab) + tab:setVisible(true) + end + end + if #tabBar.preTabs > 0 then + for i = #tabBar.preTabs, 1, -1 do + local tab = tabBar.preTabs[i] + if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then + break + end + + table.remove(tabBar.preTabs, i) + table.insert(tabBar.tabs, 1, tab) + tab:setVisible(true) + end + end + updateNavigation(tabBar) + updateMargins(tabBar) + if not tabBar.currentTab and #tabBar.tabs > 0 then + tabBar:selectTab(tabBar.tabs[1]) + end +end + +local function hideTabs(tabBar, fromBack, toArray, width) + while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do + local index = fromBack and #tabBar.tabs or 1 + local tab = tabBar.tabs[index] + table.remove(tabBar.tabs, index) + if fromBack then + table.insert(toArray, 1, tab) + else + table.insert(toArray, tab) + end + if tabBar.currentTab == tab then + if #tabBar.tabs > 0 then + tabBar:selectTab(tabBar.tabs[#tabBar.tabs]) + else + tabBar.currentTab:setChecked(false) + tabBar.currentTab = nil + end + end + tab:setVisible(false) + end +end + +local function showPreTab(tabBar) + if #tabBar.preTabs == 0 then + return nil + end + + local tmpTab = tabBar.preTabs[#tabBar.preTabs] + hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth()) + + table.remove(tabBar.preTabs, #tabBar.preTabs) + table.insert(tabBar.tabs, 1, tmpTab) + tmpTab:setVisible(true) + return tmpTab +end + +local function showPostTab(tabBar) + if #tabBar.postTabs == 0 then + return nil + end + + local tmpTab = tabBar.postTabs[1] + hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth()) + + table.remove(tabBar.postTabs, 1) + table.insert(tabBar.tabs, tmpTab) + tmpTab:setVisible(true) + return tmpTab +end + +local function onTabMousePress(tab, mousePos, mouseButton) + if mouseButton == MouseRightButton then + if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end + return true + end +end + +local function onTabDragEnter(tab, mousePos) + tab:raise() + tab.hotSpot = mousePos.x - tab:getMarginLeft() + tab.tabBar.selected = tab + return true +end + +local function onTabDragLeave(tab) + updateMargins(tab.tabBar) + tab.tabBar.selected = nil + return true +end + +local function onTabDragMove(tab, mousePos, mouseMoved) + if tab == tab.tabBar.selected then + local xoff = mousePos.x - tab.hotSpot + + -- update indexes + updateIndexes(tab.tabBar, tab, xoff) + updateIndexes(tab.tabBar, tab, xoff) + + -- update margins + updateMargins(tab.tabBar) + xoff = math.max(xoff, 0) + xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab)) + tab:setMarginLeft(xoff) + end +end + +local function tabBlink(tab, step) + local step = step or 0 + tab:setOn(not tab:isOn()) + + removeEvent(tab.blinkEvent) + if step < 4 then + tab.blinkEvent = scheduleEvent(function() tabBlink(tab, step+1) end, 500) + else + tab:setOn(true) + tab.blinkEvent = nil + end +end + +-- public functions +function UIMoveableTabBar.create() + local tabbar = UIMoveableTabBar.internalCreate() + tabbar:setFocusable(false) + tabbar.tabs = {} + tabbar.selected = nil -- dragged tab + tabbar.tabSpacing = 0 + tabbar.tabsMoveable = false + tabbar.preTabs = {} + tabbar.postTabs = {} + tabbar.prevNavigation = nil + tabbar.nextNavigation = nil + tabbar.onGeometryChange = function() + hideTabs(tabbar, true, tabbar.postTabs, 0) + updateTabs(tabbar) + end + return tabbar +end + +function UIMoveableTabBar:onDestroy() + if self.prevNavigation then + self.prevNavigation:disable() + end + + if self.nextNavigation then + self.nextNavigation:disable() + end + + self.nextNavigation = nil + self.prevNavigation = nil +end + +function UIMoveableTabBar:setContentWidget(widget) + self.contentWidget = widget + if #self.tabs > 0 then + self.contentWidget:addChild(self.tabs[1].tabPanel) + end +end + +function UIMoveableTabBar:setTabSpacing(tabSpacing) + self.tabSpacing = tabSpacing + updateMargins(self) +end + +function UIMoveableTabBar:addTab(text, panel, menuCallback) + if panel == nil then + panel = g_ui.createWidget(self:getStyleName() .. 'Panel') + panel:setId('tabPanel') + end + + local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self) + panel.isTab = true + tab.tabPanel = panel + tab.tabBar = self + tab:setId('tab') + tab:setDraggable(self.tabsMoveable) + tab:setText(text) + tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) + tab.menuCallback = menuCallback or nil + tab.onClick = onTabClick + tab.onMousePress = onTabMousePress + tab.onDragEnter = onTabDragEnter + tab.onDragLeave = onTabDragLeave + tab.onDragMove = onTabDragMove + tab.onDestroy = function() tab.tabPanel:destroy() end + + if #self.tabs == 0 then + self:selectTab(tab) + tab:setMarginLeft(0) + table.insert(self.tabs, tab) + else + local newMargin = self.tabSpacing * #self.tabs + for i = 1, #self.tabs do + newMargin = newMargin + self.tabs[i]:getWidth() + end + tab:setMarginLeft(newMargin) + + hideTabs(self, true, self.postTabs, tab:getWidth()) + table.insert(self.tabs, tab) + if #self.tabs == 1 then + self:selectTab(tab) + end + updateMargins(self) + end + + updateNavigation(self) + return tab +end + +-- Additional function to move the tab by lua +function UIMoveableTabBar:moveTab(tab, units) + local index = table.find(self.tabs, tab) + if index == nil then return end + + local focus = false + if self.currentTab == tab then + self:selectPrevTab() + focus = true + end + + table.remove(self.tabs, index) + + local newIndex = math.min(#self.tabs+1, math.max(index + units, 1)) + table.insert(self.tabs, newIndex, tab) + if focus then self:selectTab(tab) end + updateMargins(self) + return newIndex +end + +function UIMoveableTabBar:onStyleApply(styleName, styleNode) + if styleNode['movable'] then + self.tabsMoveable = styleNode['movable'] + end + if styleNode['tab-spacing'] then + self:setTabSpacing(styleNode['tab-spacing']) + end +end + +function UIMoveableTabBar:clearTabs() + while #self.tabs > 0 do + self:removeTab(self.tabs[#self.tabs]) + end +end + +function UIMoveableTabBar:removeTab(tab) + local tabTables = {self.tabs, self.preTabs, self.postTabs} + local index = nil + local tabTable = nil + for i = 1, #tabTables do + index = table.find(tabTables[i], tab) + if index ~= nil then + tabTable = tabTables[i] + break + end + end + + if tabTable == nil then + return + end + table.remove(tabTable, index) + if self.currentTab == tab then + self:selectPrevTab() + if #self.tabs == 1 then + self.currentTab = nil + end + end + if tab.blinkEvent then + removeEvent(tab.blinkEvent) + end + updateTabs(self) + tab:destroy() +end + +function UIMoveableTabBar:getTab(text) + for k,tab in pairs(self.tabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end + for k,tab in pairs(self.preTabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end + for k,tab in pairs(self.postTabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end +end + +function UIMoveableTabBar:selectTab(tab) + if self.currentTab == tab then return end + if self.contentWidget then + local selectedWidget = self.contentWidget:getLastChild() + if selectedWidget and selectedWidget.isTab then + self.contentWidget:removeChild(selectedWidget) + end + self.contentWidget:addChild(tab.tabPanel) + tab.tabPanel:fill('parent') + end + + if self.currentTab then + self.currentTab:setChecked(false) + end + signalcall(self.onTabChange, self, tab) + self.currentTab = tab + tab:setChecked(true) + tab:setOn(false) + tab.blinking = false + + if tab.blinkEvent then + removeEvent(tab.blinkEvent) + tab.blinkEvent = nil + end + + local parent = tab:getParent() + parent:focusChild(tab, MouseFocusReason) + updateNavigation(self) +end + +function UIMoveableTabBar:selectNextTab() + if self.currentTab == nil then + return + end + + local index = table.find(self.tabs, self.currentTab) + if index == nil then + return + end + + local newIndex = index + 1 + if newIndex > #self.tabs then + if #self.postTabs > 0 then + local widget = showPostTab(self) + self:selectTab(widget) + else + if #self.preTabs > 0 then + for i = 1, #self.preTabs do + showPreTab(self) + end + end + + self:selectTab(self.tabs[1]) + end + updateTabs(self) + return + end + + local nextTab = self.tabs[newIndex] + if not nextTab then + return + end + + self:selectTab(nextTab) +end + +function UIMoveableTabBar:selectPrevTab() + if self.currentTab == nil then + return + end + + local index = table.find(self.tabs, self.currentTab) + if index == nil then + return + end + + local newIndex = index - 1 + if newIndex <= 0 then + if #self.preTabs > 0 then + local widget = showPreTab(self) + self:selectTab(widget) + else + if #self.postTabs > 0 then + for i = 1, #self.postTabs do + showPostTab(self) + end + end + + self:selectTab(self.tabs[#self.tabs]) + end + updateTabs(self) + return + end + + local prevTab = self.tabs[newIndex] + if not prevTab then + return + end + + self:selectTab(prevTab) +end + +function UIMoveableTabBar:blinkTab(tab) + if tab:isChecked() then return end + tab.blinking = true + tabBlink(tab) +end + +function UIMoveableTabBar:getTabPanel(tab) + return tab.tabPanel +end + +function UIMoveableTabBar:getCurrentTabPanel() + if self.currentTab then + return self.currentTab.tabPanel + end +end + +function UIMoveableTabBar:getCurrentTab() + return self.currentTab +end + +function UIMoveableTabBar:setNavigation(prevButton, nextButton) + self.prevNavigation = prevButton + self.nextNavigation = nextButton + + if self.prevNavigation then + self.prevNavigation.onClick = function() self:selectPrevTab() end + end + if self.nextNavigation then + self.nextNavigation.onClick = function() self:selectNextTab() end + end + updateNavigation(self) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupmenu.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupmenu.lua new file mode 100644 index 0000000..16aaca0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupmenu.lua @@ -0,0 +1,122 @@ +-- @docclass +UIPopupMenu = extends(UIWidget, "UIPopupMenu") + +local currentMenu + +function UIPopupMenu.create() + local menu = UIPopupMenu.internalCreate() + local layout = UIVerticalLayout.create(menu) + layout:setFitChildren(true) + menu:setLayout(layout) + menu.isGameMenu = false + return menu +end + +function UIPopupMenu:display(pos) + -- don't display if not options was added + if self:getChildCount() == 0 then + self:destroy() + return + end + + if g_ui.isMouseGrabbed() then + self:destroy() + return + end + + if currentMenu then + currentMenu:destroy() + end + + if pos == nil then + pos = g_window.getMousePosition() + end + + rootWidget:addChild(self) + self:setPosition(pos) + self:grabMouse() + self:focus() + --self:grabKeyboard() + currentMenu = self +end + +function UIPopupMenu:onGeometryChange(oldRect, newRect) + local parent = self:getParent() + if not parent then return end + local ymax = parent:getY() + parent:getHeight() + local xmax = parent:getX() + parent:getWidth() + if newRect.y + newRect.height > ymax then + local newy = ymax - newRect.height + if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end + end + if newRect.x + newRect.width > xmax then + local newx = xmax - newRect.width + if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end + end + self:bindRectToParent() +end + +function UIPopupMenu:addOption(optionName, optionCallback, shortcut) + local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self) + optionWidget.onClick = function(widget) + self:destroy() + optionCallback() + end + optionWidget:setText(optionName) + local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 + + if shortcut then + local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) + shortcutLabel:setText(shortcut) + width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() + end + + self:setWidth(math.max(self:getWidth(), width)) +end + +function UIPopupMenu:addSeparator() + g_ui.createWidget(self:getStyleName() .. 'Separator', self) +end + +function UIPopupMenu:setGameMenu(state) + self.isGameMenu = state +end + +function UIPopupMenu:onDestroy() + if currentMenu == self then + currentMenu = nil + end + self:ungrabMouse() +end + +function UIPopupMenu:onMousePress(mousePos, mouseButton) + -- clicks outside menu area destroys the menu + if not self:containsPoint(mousePos) then + self:destroy() + end + return true +end + +function UIPopupMenu:onKeyPress(keyCode, keyboardModifiers) + if keyCode == KeyEscape then + self:destroy() + return true + end + return false +end + +-- close all menus when the window is resized +local function onRootGeometryUpdate() + if currentMenu then + currentMenu:destroy() + end +end + +local function onGameEnd() + if currentMenu and currentMenu.isGameMenu then + currentMenu:destroy() + end +end + +connect(rootWidget, { onGeometryChange = onRootGeometryUpdate }) +connect(g_game, { onGameEnd = onGameEnd } ) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupscrollmenu.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupscrollmenu.lua new file mode 100644 index 0000000..405054f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uipopupscrollmenu.lua @@ -0,0 +1,129 @@ +-- @docclass +UIPopupScrollMenu = extends(UIWidget, "UIPopupScrollMenu") + +local currentMenu + +function UIPopupScrollMenu.create() + local menu = UIPopupScrollMenu.internalCreate() + + local scrollArea = g_ui.createWidget('UIScrollArea', menu) + scrollArea:setLayout(UIVerticalLayout.create(menu)) + scrollArea:setId('scrollArea') + + local scrollBar = g_ui.createWidget('VerticalScrollBar', menu) + scrollBar:setId('scrollBar') + scrollBar.pixelsScroll = false + + scrollBar:addAnchor(AnchorRight, 'parent', AnchorRight) + scrollBar:addAnchor(AnchorTop, 'parent', AnchorTop) + scrollBar:addAnchor(AnchorBottom, 'parent', AnchorBottom) + + scrollArea:addAnchor(AnchorLeft, 'parent', AnchorLeft) + scrollArea:addAnchor(AnchorTop, 'parent', AnchorTop) + scrollArea:addAnchor(AnchorBottom, 'parent', AnchorBottom) + scrollArea:addAnchor(AnchorRight, 'next', AnchorLeft) + scrollArea:setVerticalScrollBar(scrollBar) + + menu.scrollArea = scrollArea + menu.scrollBar = scrollBar + return menu +end + +function UIPopupScrollMenu:setScrollbarStep(step) + self.scrollBar:setStep(step) +end + +function UIPopupScrollMenu:display(pos) + -- don't display if not options was added + if self.scrollArea:getChildCount() == 0 then + self:destroy() + return + end + + if g_ui.isMouseGrabbed() then + self:destroy() + return + end + + if currentMenu then + currentMenu:destroy() + end + + if pos == nil then + pos = g_window.getMousePosition() + end + + rootWidget:addChild(self) + self:setPosition(pos) + self:grabMouse() + currentMenu = self +end + +function UIPopupScrollMenu:onGeometryChange(oldRect, newRect) + local parent = self:getParent() + if not parent then return end + local ymax = parent:getY() + parent:getHeight() + local xmax = parent:getX() + parent:getWidth() + if newRect.y + newRect.height > ymax then + local newy = newRect.y - newRect.height + if newy > 0 and newy + newRect.height < ymax then self:setY(newy) end + end + if newRect.x + newRect.width > xmax then + local newx = newRect.x - newRect.width + if newx > 0 and newx + newRect.width < xmax then self:setX(newx) end + end + self:bindRectToParent() +end + +function UIPopupScrollMenu:addOption(optionName, optionCallback, shortcut) + local optionWidget = g_ui.createWidget(self:getStyleName() .. 'Button', self.scrollArea) + optionWidget.onClick = function(widget) + self:destroy() + optionCallback() + end + optionWidget:setText(optionName) + local width = optionWidget:getTextSize().width + optionWidget:getMarginLeft() + optionWidget:getMarginRight() + 15 + + if shortcut then + local shortcutLabel = g_ui.createWidget(self:getStyleName() .. 'ShortcutLabel', optionWidget) + shortcutLabel:setText(shortcut) + width = width + shortcutLabel:getTextSize().width + shortcutLabel:getMarginLeft() + shortcutLabel:getMarginRight() + end + + self:setWidth(math.max(self:getWidth(), width)) +end + +function UIPopupScrollMenu:addSeparator() + g_ui.createWidget(self:getStyleName() .. 'Separator', self.scrollArea) +end + +function UIPopupScrollMenu:onDestroy() + if currentMenu == self then + currentMenu = nil + end + self:ungrabMouse() +end + +function UIPopupScrollMenu:onMousePress(mousePos, mouseButton) + -- clicks outside menu area destroys the menu + if not self:containsPoint(mousePos) then + self:destroy() + end + return true +end + +function UIPopupScrollMenu:onKeyPress(keyCode, keyboardModifiers) + if keyCode == KeyEscape then + self:destroy() + return true + end + return false +end + +-- close all menus when the window is resized +local function onRootGeometryUpdate() + if currentMenu then + currentMenu:destroy() + end +end +connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} ) diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiprogressbar.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiprogressbar.lua new file mode 100644 index 0000000..35658a2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiprogressbar.lua @@ -0,0 +1,99 @@ +-- @docclass +UIProgressBar = extends(UIWidget, "UIProgressBar") + +function UIProgressBar.create() + local progressbar = UIProgressBar.internalCreate() + progressbar:setFocusable(false) + progressbar:setOn(true) + progressbar.min = 0 + progressbar.max = 100 + progressbar.value = 0 + progressbar.bgBorderLeft = 0 + progressbar.bgBorderRight = 0 + progressbar.bgBorderTop = 0 + progressbar.bgBorderBottom = 0 + return progressbar +end + +function UIProgressBar:setMinimum(minimum) + self.minimum = minimum + if self.value < minimum then + self:setValue(minimum) + end +end + +function UIProgressBar:setMaximum(maximum) + self.maximum = maximum + if self.value > maximum then + self:setValue(maximum) + end +end + +function UIProgressBar:setValue(value, minimum, maximum) + if minimum then + self:setMinimum(minimum) + end + + if maximum then + self:setMaximum(maximum) + end + + self.value = math.max(math.min(value, self.maximum), self.minimum) + self:updateBackground() +end + +function UIProgressBar:setPercent(percent) + self:setValue(percent, 0, 100) +end + +function UIProgressBar:getPercent() + return self.value +end + +function UIProgressBar:getPercentPixels() + return (self.maximum - self.minimum) / self:getWidth() +end + +function UIProgressBar:getProgress() + if self.minimum == self.maximum then return 1 end + return (self.value - self.minimum) / (self.maximum - self.minimum) +end + +function UIProgressBar:updateBackground() + if self:isOn() then + local width = math.round(math.max((self:getProgress() * (self:getWidth() - self.bgBorderLeft - self.bgBorderRight)), 1)) + local height = self:getHeight() - self.bgBorderTop - self.bgBorderBottom + local rect = { x = self.bgBorderLeft, y = self.bgBorderTop, width = width, height = height } + self:setBackgroundRect(rect) + end +end + +function UIProgressBar:onSetup() + self:updateBackground() +end + +function UIProgressBar:onStyleApply(name, node) + for name,value in pairs(node) do + if name == 'background-border-left' then + self.bgBorderLeft = tonumber(value) + elseif name == 'background-border-right' then + self.bgBorderRight = tonumber(value) + elseif name == 'background-border-top' then + self.bgBorderTop = tonumber(value) + elseif name == 'background-border-bottom' then + self.bgBorderBottom = tonumber(value) + elseif name == 'background-border' then + self.bgBorderLeft = tonumber(value) + self.bgBorderRight = tonumber(value) + self.bgBorderTop = tonumber(value) + self.bgBorderBottom = tonumber(value) + end + end +end + +function UIProgressBar:onGeometryChange(oldRect, newRect) + if not self:isOn() then + self:setHeight(0) + end + self:updateBackground() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiradiogroup.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiradiogroup.lua new file mode 100644 index 0000000..682aece --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiradiogroup.lua @@ -0,0 +1,66 @@ +-- @docclass +UIRadioGroup = newclass("UIRadioGroup") + +function UIRadioGroup.create() + local radiogroup = UIRadioGroup.internalCreate() + radiogroup.widgets = {} + radiogroup.selectedWidget = nil + return radiogroup +end + +function UIRadioGroup:destroy() + for k,widget in pairs(self.widgets) do + widget.onClick = nil + end + self.widgets = {} +end + +function UIRadioGroup:addWidget(widget) + table.insert(self.widgets, widget) + widget.onClick = function(widget) self:selectWidget(widget) end +end + +function UIRadioGroup:removeWidget(widget) + if self.selectedWidget == widget then + self:selectWidget(nil) + end + widget.onClick = nil + table.removevalue(self.widgets, widget) +end + +function UIRadioGroup:selectWidget(selectedWidget, dontSignal) + if selectedWidget == self.selectedWidget then return end + + local previousSelectedWidget = self.selectedWidget + self.selectedWidget = selectedWidget + + if previousSelectedWidget then + previousSelectedWidget:setChecked(false) + end + + if selectedWidget then + selectedWidget:setChecked(true) + end + + if not dontSignal then + signalcall(self.onSelectionChange, self, selectedWidget, previousSelectedWidget) + end +end + +function UIRadioGroup:clearSelected() + if not self.selectedWidget then return end + + local previousSelectedWidget = self.selectedWidget + self.selectedWidget:setChecked(false) + self.selectedWidget = nil + + signalcall(self.onSelectionChange, self, nil, previousSelectedWidget) +end + +function UIRadioGroup:getSelectedWidget() + return self.selectedWidget +end + +function UIRadioGroup:getFirstWidget() + return self.widgets[1] +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiresizeborder.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiresizeborder.lua new file mode 100644 index 0000000..4a86ce3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiresizeborder.lua @@ -0,0 +1,132 @@ +-- @docclass +UIResizeBorder = extends(UIWidget, "UIResizeBorder") + +function UIResizeBorder.create() + local resizeborder = UIResizeBorder.internalCreate() + resizeborder:setFocusable(false) + resizeborder.minimum = 0 + resizeborder.maximum = 1000 + return resizeborder +end + +function UIResizeBorder:onSetup() + if self:getWidth() > self:getHeight() then + self.vertical = true + else + self.vertical = false + end +end + +function UIResizeBorder:onDestroy() + if self.hovering then + g_mouse.popCursor(self.cursortype) + end +end + +function UIResizeBorder:onHoverChange(hovered) + if hovered then + if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end + if self:getWidth() > self:getHeight() then + self.vertical = true + self.cursortype = 'vertical' + else + self.vertical = false + self.cursortype = 'horizontal' + end + g_mouse.pushCursor(self.cursortype) + self.hovering = true + if not self:isPressed() then + g_effects.fadeIn(self) + end + else + if not self:isPressed() and self.hovering then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end + end +end + +function UIResizeBorder:onMouseMove(mousePos, mouseMoved) + if self:isPressed() then + local parent = self:getParent() + local newSize = 0 + if self.vertical then + local delta = mousePos.y - self:getY() - self:getHeight()/2 + newSize = math.min(math.max(parent:getHeight() + delta, self.minimum), self.maximum) + parent:setHeight(newSize) + else + local delta = mousePos.x - self:getX() - self:getWidth()/2 + newSize = math.min(math.max(parent:getWidth() + delta, self.minimum), self.maximum) + parent:setWidth(newSize) + end + + self:checkBoundary(newSize) + return true + end +end + +function UIResizeBorder:onMouseRelease(mousePos, mouseButton) + if not self:isHovered() then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end +end + +function UIResizeBorder:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'maximum' then + self:setMaximum(tonumber(value)) + elseif name == 'minimum' then + self:setMinimum(tonumber(value)) + end + end +end + +function UIResizeBorder:onVisibilityChange(visible) + if visible and self.maximum == self.minimum then + self:hide() + end +end + +function UIResizeBorder:setMaximum(maximum) + self.maximum = maximum + self:checkBoundary() +end + +function UIResizeBorder:setMinimum(minimum) + self.minimum = minimum + self:checkBoundary() +end + +function UIResizeBorder:getMaximum() return self.maximum end +function UIResizeBorder:getMinimum() return self.minimum end + +function UIResizeBorder:setParentSize(size) + local parent = self:getParent() + if self.vertical then + parent:setHeight(size) + else + parent:setWidth(size) + end + self:checkBoundary(size) +end + +function UIResizeBorder:getParentSize() + local parent = self:getParent() + if self.vertical then + return parent:getHeight() + else + return parent:getWidth() + end +end + +function UIResizeBorder:checkBoundary(size) + size = size or self:getParentSize() + if self.maximum == self.minimum and size == self.maximum then + self:hide() + else + self:show() + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollarea.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollarea.lua new file mode 100644 index 0000000..5f274ec --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollarea.lua @@ -0,0 +1,190 @@ +-- @docclass +UIScrollArea = extends(UIWidget, "UIScrollArea") + +-- public functions +function UIScrollArea.create() + local scrollarea = UIScrollArea.internalCreate() + scrollarea:setClipping(true) + scrollarea.inverted = false + scrollarea.alwaysScrollMaximum = false + return scrollarea +end + +function UIScrollArea:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'vertical-scrollbar' then + addEvent(function() + local parent = self:getParent() + if parent then + self:setVerticalScrollBar(parent:getChildById(value)) + end + end) + elseif name == 'horizontal-scrollbar' then + addEvent(function() + local parent = self:getParent() + if parent then + self:setHorizontalScrollBar(self:getParent():getChildById(value)) + end + end) + elseif name == 'inverted-scroll' then + self:setInverted(value) + elseif name == 'always-scroll-maximum' then + self:setAlwaysScrollMaximum(value) + end + end +end + +function UIScrollArea:updateScrollBars() + local scrollWidth = math.max(self:getChildrenRect().width - self:getPaddingRect().width, 0) + local scrollHeight = math.max(self:getChildrenRect().height - self:getPaddingRect().height, 0) + + local scrollbar = self.verticalScrollBar + if scrollbar then + if self.inverted then + scrollbar:setMinimum(-scrollHeight) + scrollbar:setMaximum(0) + else + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollHeight) + end + end + + local scrollbar = self.horizontalScrollBar + if scrollbar then + if self.inverted then + scrollbar:setMinimum(-scrollWidth) + scrollbar:setMaximum(0) + else + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollWidth) + end + end + + if self.lastScrollWidth ~= scrollWidth then + self:onScrollWidthChange() + end + if self.lastScrollHeight ~= scrollHeight then + self:onScrollHeightChange() + end + + self.lastScrollWidth = scrollWidth + self.lastScrollHeight = scrollHeight +end + +function UIScrollArea:setVerticalScrollBar(scrollbar) + self.verticalScrollBar = scrollbar + connect(self.verticalScrollBar, 'onValueChange', function(scrollbar, value) + local virtualOffset = self:getVirtualOffset() + virtualOffset.y = value + self:setVirtualOffset(virtualOffset) + signalcall(self.onScrollChange, self, virtualOffset) + end) + self:updateScrollBars() +end + +function UIScrollArea:setHorizontalScrollBar(scrollbar) + self.horizontalScrollBar = scrollbar + connect(self.horizontalScrollBar, 'onValueChange', function(scrollbar, value) + local virtualOffset = self:getVirtualOffset() + virtualOffset.x = value + self:setVirtualOffset(virtualOffset) + signalcall(self.onScrollChange, self, virtualOffset) + end) + self:updateScrollBars() +end + +function UIScrollArea:setInverted(inverted) + self.inverted = inverted +end + +function UIScrollArea:setAlwaysScrollMaximum(value) + self.alwaysScrollMaximum = value +end + +function UIScrollArea:onLayoutUpdate() + self:updateScrollBars() +end + +function UIScrollArea:onMouseWheel(mousePos, mouseWheel) + if self.verticalScrollBar then + if not self.verticalScrollBar:isOn() then + return false + end + if mouseWheel == MouseWheelUp then + local minimum = self.verticalScrollBar:getMinimum() + if self.verticalScrollBar:getValue() <= minimum then + return false + end + self.verticalScrollBar:decrement() + else + local maximum = self.verticalScrollBar:getMaximum() + if self.verticalScrollBar:getValue() >= maximum then + return false + end + self.verticalScrollBar:increment() + end + elseif self.horizontalScrollBar then + if not self.horizontalScrollBar:isOn() then + return false + end + if mouseWheel == MouseWheelUp then + local maximum = self.horizontalScrollBar:getMaximum() + if self.horizontalScrollBar:getValue() >= maximum then + return false + end + self.horizontalScrollBar:increment() + else + local minimum = self.horizontalScrollBar:getMinimum() + if self.horizontalScrollBar:getValue() <= minimum then + return false + end + self.horizontalScrollBar:decrement() + end + end + return true +end + +function UIScrollArea:ensureChildVisible(child) + if child then + local paddingRect = self:getPaddingRect() + if self.verticalScrollBar then + local deltaY = paddingRect.y - child:getY() + if deltaY > 0 then + self.verticalScrollBar:decrement(deltaY) + end + + deltaY = (child:getY() + child:getHeight()) - (paddingRect.y + paddingRect.height) + if deltaY > 0 then + self.verticalScrollBar:increment(deltaY) + end + elseif self.horizontalScrollBar then + local deltaX = paddingRect.x - child:getX() + if deltaX > 0 then + self.horizontalScrollBar:decrement(deltaX) + end + + deltaX = (child:getX() + child:getWidth()) - (paddingRect.x + paddingRect.width) + if deltaX > 0 then + self.horizontalScrollBar:increment(deltaX) + end + end + end +end + +function UIScrollArea:onChildFocusChange(focusedChild, oldFocused, reason) + if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then + self:ensureChildVisible(focusedChild) + end +end + +function UIScrollArea:onScrollWidthChange() + if self.alwaysScrollMaximum and self.horizontalScrollBar then + self.horizontalScrollBar:setValue(self.horizontalScrollBar:getMaximum()) + end +end + +function UIScrollArea:onScrollHeightChange() + if self.alwaysScrollMaximum and self.verticalScrollBar then + self.verticalScrollBar:setValue(self.verticalScrollBar:getMaximum()) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollbar.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollbar.lua new file mode 100644 index 0000000..940d3eb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiscrollbar.lua @@ -0,0 +1,290 @@ +-- @docclass +UIScrollBar = extends(UIWidget, "UIScrollBar") + +-- private functions +local function calcValues(self) + local slider = self:getChildById('sliderButton') + local decrementButton = self:getChildById('decrementButton') + local incrementButton = self:getChildById('incrementButton') + + local pxrange, center + if self.orientation == 'vertical' then + pxrange = (self:getHeight() - decrementButton:getHeight() - decrementButton:getMarginTop() - decrementButton:getMarginBottom() + - incrementButton:getHeight() - incrementButton:getMarginTop() - incrementButton:getMarginBottom()) + center = self:getY() + math.floor(self:getHeight() / 2) + else -- horizontal + pxrange = (self:getWidth() - decrementButton:getWidth() - decrementButton:getMarginLeft() - decrementButton:getMarginRight() + - incrementButton:getWidth() - incrementButton:getMarginLeft() - incrementButton:getMarginRight()) + center = self:getX() + math.floor(self:getWidth() / 2) + end + + local range = self.maximum - self.minimum + 1 + + local proportion + + if self.pixelsScroll then + proportion = pxrange/(range+pxrange) + else + proportion = math.min(math.max(self.step, 1), range)/range + end + + local px = math.max(proportion * pxrange, 6) + if g_app.isMobile() then + px = math.max(proportion * pxrange, 24) + end + px = px - px % 2 + 1 + + local offset = 0 + if range == 0 or self.value == self.minimum then + if self.orientation == 'vertical' then + offset = -math.floor((self:getHeight() - px) / 2) + decrementButton:getMarginRect().height + else + offset = -math.floor((self:getWidth() - px) / 2) + decrementButton:getMarginRect().width + end + elseif range > 1 and self.value == self.maximum then + if self.orientation == 'vertical' then + offset = math.ceil((self:getHeight() - px) / 2) - incrementButton:getMarginRect().height + else + offset = math.ceil((self:getWidth() - px) / 2) - incrementButton:getMarginRect().width + end + elseif range > 1 then + offset = (((self.value - self.minimum) / (range - 1)) - 0.5) * (pxrange - px) + end + + return range, pxrange, px, offset, center +end + +local function updateValueDisplay(widget) + if widget == nil then return end + + if widget:getShowValue() then + widget:setText(widget:getValue() .. (widget:getSymbol() or '')) + end +end + +local function updateSlider(self) + local slider = self:getChildById('sliderButton') + if slider == nil then return end + + local range, pxrange, px, offset, center = calcValues(self) + if self.orientation == 'vertical' then + slider:setHeight(px) + slider:setMarginTop(offset) + else -- horizontal + slider:setWidth(px) + slider:setMarginLeft(offset) + end + updateValueDisplay(self) + + local status = (self.maximum ~= self.minimum) + + self:setOn(status) + for _i,child in pairs(self:getChildren()) do + child:setEnabled(status) + end +end + +local function parseSliderPos(self, slider, pos, move) + local delta, hotDistance + if self.orientation == 'vertical' then + delta = move.y + hotDistance = pos.y - slider:getY() + else + delta = move.x + hotDistance = pos.x - slider:getX() + end + + if (delta > 0 and hotDistance + delta > self.hotDistance) or + (delta < 0 and hotDistance + delta < self.hotDistance) then + local range, pxrange, px, offset, center = calcValues(self) + local newvalue = self.value + delta * (range / (pxrange - px)) + self:setValue(newvalue) + end +end + +local function parseSliderPress(self, slider, pos, button) + if self.orientation == 'vertical' then + self.hotDistance = pos.y - slider:getY() + else + self.hotDistance = pos.x - slider:getX() + end +end + +-- public functions +function UIScrollBar.create() + local scrollbar = UIScrollBar.internalCreate() + scrollbar:setFocusable(false) + scrollbar.value = 0 + scrollbar.minimum = -999999 + scrollbar.maximum = 999999 + scrollbar.step = 1 + scrollbar.orientation = 'vertical' + scrollbar.pixelsScroll = false + scrollbar.showValue = false + scrollbar.symbol = nil + scrollbar.mouseScroll = true + return scrollbar +end + +function UIScrollBar:onSetup() + self.setupDone = true + local sliderButton = self:getChildById('sliderButton') + g_mouse.bindAutoPress(self:getChildById('decrementButton'), function() self:onDecrement() end, 300) + g_mouse.bindAutoPress(self:getChildById('incrementButton'), function() self:onIncrement() end, 300) + g_mouse.bindPressMove(sliderButton, function(mousePos, mouseMoved) parseSliderPos(self, sliderButton, mousePos, mouseMoved) end) + g_mouse.bindPress(sliderButton, function(mousePos, mouseButton) parseSliderPress(self, sliderButton, mousePos, mouseButton) end) + + updateSlider(self) +end + +function UIScrollBar:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'maximum' then + self:setMaximum(tonumber(value)) + elseif name == 'minimum' then + self:setMinimum(tonumber(value)) + elseif name == 'step' then + self:setStep(tonumber(value)) + elseif name == 'orientation' then + self:setOrientation(value) + elseif name == 'value' then + self:setValue(value) + elseif name == 'pixels-scroll' then + self.pixelsScroll = true + elseif name == 'show-value' then + self.showValue = true + elseif name == 'symbol' then + self.symbol = value + elseif name == 'mouse-scroll' then + self.mouseScroll = value + end + end +end + +function UIScrollBar:onDecrement() + if g_keyboard.isCtrlPressed() then + self:decrement(self.value) + elseif g_keyboard.isShiftPressed() then + self:decrement(10) + else + self:decrement() + end +end + +function UIScrollBar:onIncrement() + if g_keyboard.isCtrlPressed() then + self:increment(self.maximum) + elseif g_keyboard.isShiftPressed() then + self:increment(10) + else + self:increment() + end +end + +function UIScrollBar:decrement(count) + count = count or self.step + self:setValue(self.value - count) +end + +function UIScrollBar:increment(count) + count = count or self.step + self:setValue(self.value + count) +end + +function UIScrollBar:setMaximum(maximum) + if maximum == self.maximum then return end + self.maximum = maximum + if self.minimum > maximum then + self:setMinimum(maximum) + end + if self.value > maximum then + self:setValue(maximum) + else + updateSlider(self) + end +end + +function UIScrollBar:setMinimum(minimum) + if minimum == self.minimum then return end + self.minimum = minimum + if self.maximum < minimum then + self:setMaximum(minimum) + end + if self.value < minimum then + self:setValue(minimum) + else + updateSlider(self) + end +end + +function UIScrollBar:setRange(minimum, maximum) + self:setMinimum(minimum) + self:setMaximum(maximum) +end + +function UIScrollBar:setValue(value) + value = math.max(math.min(value, self.maximum), self.minimum) + if self.value == value then return end + local delta = value - self.value + self.value = value + updateSlider(self) + if self.setupDone then + signalcall(self.onValueChange, self, math.round(value), delta) + end +end + +function UIScrollBar:setMouseScroll(scroll) + self.mouseScroll = scroll +end + +function UIScrollBar:setStep(step) + self.step = step +end + +function UIScrollBar:setOrientation(orientation) + self.orientation = orientation +end + +function UIScrollBar:setText(text) + local valueLabel = self:getChildById('valueLabel') + if valueLabel then + valueLabel:setText(text) + end +end + +function UIScrollBar:onGeometryChange() + updateSlider(self) +end + +function UIScrollBar:onMouseWheel(mousePos, mouseWheel) + if not self.mouseScroll or not self:isOn() or self.disableScroll then + return false + end + if mouseWheel == MouseWheelUp then + if self.orientation == 'vertical' then + if self.value <= self.minimum then return false end + self:decrement() + else + if self.value >= self.maximum then return false end + self:increment() + end + else + if self.orientation == 'vertical' then + if self.value >= self.maximum then return false end + self:increment() + else + if self.value <= self.minimum then return false end + self:decrement() + end + end + return true +end + +function UIScrollBar:getMaximum() return self.maximum end +function UIScrollBar:getMinimum() return self.minimum end +function UIScrollBar:getValue() return math.round(self.value) end +function UIScrollBar:getStep() return self.step end +function UIScrollBar:getOrientation() return self.orientation end +function UIScrollBar:getShowValue() return self.showValue end +function UIScrollBar:getSymbol() return self.symbol end +function UIScrollBar:getMouseScroll() return self.mouseScroll end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uispinbox.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uispinbox.lua new file mode 100644 index 0000000..a644db4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uispinbox.lua @@ -0,0 +1,191 @@ +-- @docclass +UISpinBox = extends(UITextEdit, "UISpinBox") + +function UISpinBox.create() + local spinbox = UISpinBox.internalCreate() + spinbox:setFocusable(false) + spinbox:setValidCharacters('0123456789') + spinbox.displayButtons = true + spinbox.minimum = 0 + spinbox.maximum = 1 + spinbox.value = 0 + spinbox.step = 1 + spinbox.firstchange = true + spinbox.mouseScroll = true + spinbox:setText("1") + spinbox:setValue(1) + return spinbox +end + +function UISpinBox:onSetup() + g_mouse.bindAutoPress(self:getChildById('up'), function() self:up() end, 300) + g_mouse.bindAutoPress(self:getChildById('down'), function() self:down() end, 300) +end + +function UISpinBox:onMouseWheel(mousePos, direction) + if not self.mouseScroll or self.disableScroll then + return false + end + if direction == MouseWheelUp then + self:up() + elseif direction == MouseWheelDown then + self:down() + end + return true +end + +function UISpinBox:onKeyPress() + if self.firstchange then + self.firstchange = false + self:setText('') + end + return false +end + +function UISpinBox:onTextChange(text, oldText) + if text:len() == 0 then + self:setValue(self.minimum) + return + end + + local number = tonumber(text) + if not number then + self:setText(number) + return + else + if number < self.minimum then + self:setText(self.minimum) + return + elseif number > self.maximum then + self:setText(self.maximum) + return + end + end + + self:setValue(number) +end + +function UISpinBox:onValueChange(value) + -- nothing to do +end + +function UISpinBox:onFocusChange(focused) + if not focused then + if self:getText():len() == 0 then + self:setText(self.minimum) + end + end +end + +function UISpinBox:onStyleApply(styleName, styleNode) + for name, value in pairs(styleNode) do + if name == 'maximum' then + self.maximum = value + addEvent(function() self:setMaximum(value) end) + elseif name == 'minimum' then + self.minimum = value + addEvent(function() self:setMinimum(value) end) + elseif name == 'mouse-scroll' then + addEvent(function() self:setMouseScroll(value) end) + elseif name == 'buttons' then + addEvent(function() + if value then + self:showButtons() + else + self:hideButtons() + end + end) + end + end +end + +function UISpinBox:showButtons() + self:getChildById('up'):show() + self:getChildById('down'):show() + self.displayButtons = true +end + +function UISpinBox:hideButtons() + self:getChildById('up'):hide() + self:getChildById('down'):hide() + self.displayButtons = false +end + +function UISpinBox:up() + self:setValue(self.value + self.step) +end + +function UISpinBox:down() + self:setValue(self.value - self.step) +end + +function UISpinBox:setValue(value, dontSignal) + if type(value) == "string" then + value = tonumber(value) + end + value = value or 0 + value = math.max(math.min(self.maximum, value), self.minimum) + + if value == self.value then return end + + self.value = value + if self:getText():len() > 0 then + self:setText(value) + end + + local upButton = self:getChildById('up') + local downButton = self:getChildById('down') + if upButton then + upButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.maximum) + end + if downButton then + downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum) + end + + if not dontSignal then + signalcall(self.onValueChange, self, value) + end +end + +function UISpinBox:getValue() + return self.value +end + +function UISpinBox:setMinimum(minimum) + minimum = minimum or -9223372036854775808 + self.minimum = minimum + if self.minimum > self.maximum then + self.maximum = self.minimum + end + if self.value < minimum then + self:setValue(minimum) + end +end + +function UISpinBox:getMinimum() + return self.minimum +end + +function UISpinBox:setMaximum(maximum) + maximum = maximum or 9223372036854775807 + self.maximum = maximum + if self.value > maximum then + self:setValue(maximum) + end +end + +function UISpinBox:getMaximum() + return self.maximum +end + +function UISpinBox:setStep(step) + self.step = step or 1 +end + +function UISpinBox:setMouseScroll(mouseScroll) + self.mouseScroll = mouseScroll +end + +function UISpinBox:getMouseScroll() + return self.mouseScroll +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uisplitter.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uisplitter.lua new file mode 100644 index 0000000..d8fb2e2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uisplitter.lua @@ -0,0 +1,85 @@ +-- @docclass +UISplitter = extends(UIWidget, "UISplitter") + +function UISplitter.create() + local splitter = UISplitter.internalCreate() + splitter:setFocusable(false) + splitter.relativeMargin = 'bottom' + return splitter +end + +function UISplitter:onHoverChange(hovered) + -- Check if margin can be changed + local margin = (self.vertical and self:getMarginBottom() or self:getMarginRight()) + if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then + if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end + if self:getWidth() > self:getHeight() then + self.vertical = true + self.cursortype = 'vertical' + else + self.vertical = false + self.cursortype = 'horizontal' + end + self.hovering = true + g_mouse.pushCursor(self.cursortype) + if not self:isPressed() then + g_effects.fadeIn(self) + end + else + if not self:isPressed() and self.hovering then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end + end +end + +function UISplitter:onMouseMove(mousePos, mouseMoved) + if self:isPressed() then + --local currentmargin, newmargin, delta + if self.vertical then + local delta = mousePos.y - self:getY() - self:getHeight()/2 + local newMargin = self:canUpdateMargin(self:getMarginBottom() - delta) + local currentMargin = self:getMarginBottom() + if newMargin ~= currentMargin then + self.newMargin = newMargin + if not self.event or self.event:isExecuted() then + self.event = addEvent(function() + self:setMarginBottom(self.newMargin) + end) + end + end + else + local delta = mousePos.x - self:getX() - self:getWidth()/2 + local newMargin = self:canUpdateMargin(self:getMarginRight() - delta) + local currentMargin = self:getMarginRight() + if newMargin ~= currentMargin then + self.newMargin = newMargin + if not self.event or self.event:isExecuted() then + self.event = addEvent(function() + self:setMarginRight(self.newMargin) + end) + end + end + end + return true + end +end + +function UISplitter:onMouseRelease(mousePos, mouseButton) + if not self:isHovered() then + g_mouse.popCursor(self.cursortype) + g_effects.fadeOut(self) + self.hovering = false + end +end + +function UISplitter:onStyleApply(styleName, styleNode) + if styleNode['relative-margin'] then + self.relativeMargin = styleNode['relative-margin'] + end +end + +function UISplitter:canUpdateMargin(newMargin) + return newMargin +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitabbar.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitabbar.lua new file mode 100644 index 0000000..9840c53 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitabbar.lua @@ -0,0 +1,163 @@ +-- @docclass +UITabBar = extends(UIWidget, "UITabBar") + +-- private functions +local function onTabClick(tab) + tab.tabBar:selectTab(tab) +end + +local function onTabMouseRelease(tab, mousePos, mouseButton) + if mouseButton == MouseRightButton and tab:containsPoint(mousePos) then + signalcall(tab.tabBar.onTabLeftClick, tab.tabBar, tab) + end +end + +-- public functions +function UITabBar.create() + local tabbar = UITabBar.internalCreate() + tabbar:setFocusable(false) + tabbar.tabs = {} + return tabbar +end + +function UITabBar:onSetup() + self.buttonsPanel = self:getChildById('buttonsPanel') +end + +function UITabBar:setContentWidget(widget) + self.contentWidget = widget + if #self.tabs > 0 then + self.contentWidget:addChild(self.tabs[1].tabPanel) + end +end + +function UITabBar:addTab(text, panel, icon) + if panel == nil then + panel = g_ui.createWidget(self:getStyleName() .. 'Panel') + panel:setId('tabPanel') + end + + local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) + + panel.isTab = true + tab.tabPanel = panel + tab.tabBar = self + tab:setId('tab') + tab:setText(text) + tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) + tab.onClick = onTabClick + tab.onMouseRelease = onTabMouseRelease + tab.onDestroy = function() tab.tabPanel:destroy() end + + table.insert(self.tabs, tab) + if #self.tabs == 1 then + self:selectTab(tab) + end + + local tabStyle = {} + tabStyle['icon-source'] = icon + tab:mergeStyle(tabStyle) + + return tab +end + +function UITabBar:addButton(text, func, icon) + local button = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) + button:setText(text) + + local style = {} + style['icon-source'] = icon + button:mergeStyle(style) + + button.onClick = func + return button +end + +function UITabBar:removeTab(tab) + local index = table.find(self.tabs, tab) + if index == nil then return end + if self.currentTab == tab then + self:selectPrevTab() + end + table.remove(self.tabs, index) + tab:destroy() +end + +function UITabBar:getTab(text) + for k,tab in pairs(self.tabs) do + if tab:getText():lower() == text:lower() then + return tab + end + end +end + +function UITabBar:selectTab(tab) + if self.currentTab == tab then return end + if self.contentWidget then + local selectedWidget = self.contentWidget:getLastChild() + if selectedWidget and selectedWidget.isTab then + self.contentWidget:removeChild(selectedWidget) + end + self.contentWidget:addChild(tab.tabPanel) + tab.tabPanel:fill('parent') + end + + if self.currentTab then + self.currentTab:setChecked(false) + end + signalcall(self.onTabChange, self, tab) + self.currentTab = tab + tab:setChecked(true) + tab:setOn(false) + + local parent = tab:getParent() + if parent then + parent:focusChild(tab, MouseFocusReason) + end +end + +function UITabBar:selectNextTab() + if self.currentTab == nil then return end + local index = table.find(self.tabs, self.currentTab) + if index == nil then return end + local nextTab = self.tabs[index + 1] or self.tabs[1] + if not nextTab then return end + self:selectTab(nextTab) +end + +function UITabBar:selectPrevTab() + if self.currentTab == nil then return end + local index = table.find(self.tabs, self.currentTab) + if index == nil then return end + local prevTab = self.tabs[index - 1] or self.tabs[#self.tabs] + if not prevTab then return end + self:selectTab(prevTab) +end + +function UITabBar:getTabPanel(tab) + return tab.tabPanel +end + +function UITabBar:getCurrentTabPanel() + if self.currentTab then + return self.currentTab.tabPanel + end +end + +function UITabBar:getCurrentTab() + return self.currentTab +end + +function UITabBar:getTabs() + return self.tabs +end + +function UITabBar:getTabsPanel() + return table.collect(self.tabs, function(_,tab) return tab.tabPanel end) +end + +function UITabBar:clearTabs() + while #self.tabs > 0 do + self:removeTab(self.tabs[#self.tabs]) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitable.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitable.lua new file mode 100644 index 0000000..fb644e7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitable.lua @@ -0,0 +1,432 @@ +-- @docclass +--[[ + TODO: + * Make table headers more robust. + * Get dynamic row heights working with text wrapping. +]] + +TABLE_SORTING_ASC = 0 +TABLE_SORTING_DESC = 1 + +UITable = extends(UIWidget, "UITable") + +-- Initialize default values +function UITable.create() + local table = UITable.internalCreate() + table.headerRow = nil + table.headerColumns = {} + table.dataSpace = nil + table.rows = {} + table.rowBaseStyle = nil + table.columns = {} + table.columnWidth = {} + table.columBaseStyle = nil + table.headerRowBaseStyle = nil + table.headerColumnBaseStyle = nil + table.selectedRow = nil + table.defaultColumnWidth = 80 + table.sortColumn = -1 + table.sortType = TABLE_SORTING_ASC + table.autoSort = false + + return table +end + +-- Clear table values +function UITable:onDestroy() + for _,row in pairs(self.rows) do + row.onClick = nil + end + self.rows = {} + self.columns = {} + self.headerRow = nil + self.headerColumns = {} + self.columnWidth = {} + self.selectedRow = nil + + if self.dataSpace then + self.dataSpace:destroyChildren() + self.dataSpace = nil + end +end + +-- Detect if a header is already defined +function UITable:onSetup() + local header = self:getChildById('header') + if header then + self:setHeader(header) + end +end + +-- Parse table related styles +function UITable:onStyleApply(styleName, styleNode) + for name, value in pairs(styleNode) do + if value ~= false then + if name == 'table-data' then + addEvent(function() + self:setTableData(self:getParent():getChildById(value)) + end) + elseif name == 'column-style' then + addEvent(function() + self:setColumnStyle(value) + end) + elseif name == 'row-style' then + addEvent(function() + self:setRowStyle(value) + end) + elseif name == 'header-column-style' then + addEvent(function() + self:setHeaderColumnStyle(value) + end) + elseif name == 'header-row-style' then + addEvent(function() + self:setHeaderRowStyle(value) + end) + end + end + end +end + +function UITable:setColumnWidth(width) + if self:hasHeader() then return end + self.columnWidth = width +end + +function UITable:setDefaultColumnWidth(width) + self.defaultColumnWidth = width +end + +-- Check if the table has a header +function UITable:hasHeader() + return self.headerRow ~= nil +end + +-- Clear all rows +function UITable:clearData() + if not self.dataSpace then + return + end + self.dataSpace:destroyChildren() + self.selectedRow = nil + self.columns = {} + self.rows = {} +end + +-- Set existing child as header +function UITable:setHeader(headerWidget) + self:removeHeader() + + if self.dataSpace then + local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + end + + self.headerColumns = {} + self.columnWidth = {} + for colId, column in pairs(headerWidget:getChildren()) do + column.colId = colId + column.table = self + table.insert(self.columnWidth, column:getWidth()) + table.insert(self.headerColumns, column) + end + + self.headerRow = headerWidget +end + +-- Create and add header from table data +function UITable:addHeader(data) + if not data or type(data) ~= 'table' then + g_logger.error('UITable:addHeaderRow - table columns must be provided in a table') + return + end + + self:removeHeader() + + -- build header columns + local columns = {} + for colId, column in pairs(data) do + local col = g_ui.createWidget(self.headerColumnBaseStyle) + col.colId = colId + col.table = self + for type, value in pairs(column) do + if type == 'width' then + col:setWidth(value) + elseif type == 'height' then + col:setHeight(value) + elseif type == 'text' then + col:setText(value) + elseif type == 'onClick' then + col.onClick = value + end + end + table.insert(columns, col) + end + + -- create a new header + local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) + local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + + headerRow:setId('header') + self.headerColumns = {} + self.columnWidth = {} + for _, column in pairs(columns) do + headerRow:addChild(column) + table.insert(self.columnWidth, column:getWidth()) + table.insert(self.headerColumns, column) + end + + self.headerRow = headerRow + return headerRow +end + +-- Remove header +function UITable:removeHeader() + if self:hasHeader() then + if self.dataSpace then + local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop() + self.dataSpace:applyStyle({ height = newHeight }) + end + self.headerColumns = {} + self.columnWidth = {} + self.headerRow:destroy() + self.headerRow = nil + end +end + +function UITable:addRow(data, height) + if not self.dataSpace then + g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.') + return + end + if not data or type(data) ~= 'table' then + g_logger.error('UITable:addRow - table columns must be provided in a table.') + return + end + + local row = g_ui.createWidget(self.rowBaseStyle) + row.table = self + if height then row:setHeight(height) end + + local rowId = #self.rows + 1 + row.rowId = rowId + row:setId('row'..rowId) + row:updateBackgroundColor() + + self.columns[rowId] = {} + for colId, column in pairs(data) do + local col = g_ui.createWidget(self.columBaseStyle, row) + if column.width then + col:setWidth(column.width) + else + col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth) + end + if column.height then + col:setHeight(column.height) + end + if column.text then + col:setText(column.text) + end + if column.sortvalue then + col.sortvalue = column.sortvalue + else + col.sortvalue = column.text or 0 + end + table.insert(self.columns[rowId], col) + end + + self.dataSpace:addChild(row) + table.insert(self.rows, row) + + if self.autoSort then + self:sort() + end + + return row +end + +-- Update row indices and background color +function UITable:updateRows() + for rowId = 1, #self.rows do + local row = self.rows[rowId] + row.rowId = rowId + row:setId('row'..rowId) + row:updateBackgroundColor() + end +end + +-- Removes the given row widget from the table +function UITable:removeRow(row) + if self.selectedRow == row then + self:selectRow(nil) + end + row.onClick = nil + row.table = nil + table.remove(self.columns, row.rowId) + table.remove(self.rows, row.rowId) + self.dataSpace:removeChild(row) + self:updateRows() +end + +function UITable:toggleSorting(enabled) + self.autoSort = enabled +end + +function UITable:setSorting(colId, sortType) + self.headerColumns[colId]:focus() + + if sortType then + self.sortType = sortType + elseif self.sortColumn == colId then + if self.sortType == TABLE_SORTING_ASC then + self.sortType = TABLE_SORTING_DESC + else + self.sortType = TABLE_SORTING_ASC + end + else + self.sortType = TABLE_SORTING_ASC + end + self.sortColumn = colId +end + +function UITable:sort() + if self.sortColumn <= 0 then + return + end + + if self.sortType == TABLE_SORTING_ASC then + table.sort(self.rows, function(rowA, b) + return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue + end) + else + table.sort(self.rows, function(rowA, b) + return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue + end) + end + + if self.dataSpace then + for _, child in pairs(self.dataSpace:getChildren()) do + self.dataSpace:removeChild(child) + end + end + + self:updateRows() + self.columns = {} + for _, row in pairs(self.rows) do + if self.dataSpace then + self.dataSpace:addChild(row) + end + + self.columns[row.rowId] = {} + for _, column in pairs(row:getChildren()) do + table.insert(self.columns[row.rowId], column) + end + end +end + +function UITable:selectRow(selectedRow) + if selectedRow == self.selectedRow then return end + + local previousSelectedRow = self.selectedRow + self.selectedRow = selectedRow + + if previousSelectedRow then + previousSelectedRow:setChecked(false) + end + + if selectedRow then + selectedRow:setChecked(true) + end + + signalcall(self.onSelectionChange, self, selectedRow, previousSelectedRow) +end + +function UITable:setTableData(tableData) + local headerHeight = 0 + if self.headerRow then + headerHeight = self.headerRow:getHeight() + end + + self.dataSpace = tableData + self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() }) +end + +function UITable:setRowStyle(style, dontUpdate) + self.rowBaseStyle = style + + if not dontUpdate then + for _, row in pairs(self.rows) do + row:setStyle(style) + end + end +end + +function UITable:setColumnStyle(style, dontUpdate) + self.columBaseStyle = style + + if not dontUpdate then + for _, columns in pairs(self.columns) do + for _, col in pairs(columns) do + col:setStyle(style) + end + end + end +end + +function UITable:setHeaderRowStyle(style) + self.headerRowBaseStyle = style + if self.headerRow then + self.headerRow:setStyle(style) + end +end + +function UITable:setHeaderColumnStyle(style) + self.headerColumnBaseStyle = style + for _, col in pairs(self.headerColumns) do + col:setStyle(style) + end +end + + +UITableRow = extends(UIWidget, "UITableRow") + +function UITableRow:onFocusChange(focused) + if focused then + if self.table then self.table:selectRow(self) end + end +end + +function UITableRow:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'even-background-color' then + self.evenBackgroundColor = value + elseif name == 'odd-background-color' then + self.oddBackgroundColor = value + end + end +end + +function UITableRow:updateBackgroundColor() + self.backgroundColor = nil + + local isEven = (self.rowId % 2 == 0) + if isEven and self.evenBackgroundColor then + self.backgroundColor = self.evenBackgroundColor + elseif not isEven and self.oddBackgroundColor then + self.backgroundColor = self.oddBackgroundColor + end + + if self.backgroundColor then + self:mergeStyle({ ['background-color'] = self.backgroundColor }) + end +end + + +UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn") + +function UITableHeaderColumn:onClick() + if self.table then + self.table:setSorting(self.colId) + self.table:sort() + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitextedit.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitextedit.lua new file mode 100644 index 0000000..b671c7d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uitextedit.lua @@ -0,0 +1,78 @@ +function UITextEdit:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'vertical-scrollbar' then + addEvent(function() + self:setVerticalScrollBar(self:getParent():getChildById(value)) + end) + elseif name == 'horizontal-scrollbar' then + addEvent(function() + self:setHorizontalScrollBar(self:getParent():getChildById(value)) + end) + end + end +end + +function UITextEdit:onMouseWheel(mousePos, mouseWheel) + if self.verticalScrollBar and self:isMultiline() then + if mouseWheel == MouseWheelUp then + self.verticalScrollBar:decrement() + else + self.verticalScrollBar:increment() + end + return true + elseif self.horizontalScrollBar then + if mouseWheel == MouseWheelUp then + self.horizontalScrollBar:increment() + else + self.horizontalScrollBar:decrement() + end + return true + end +end + +function UITextEdit:onTextAreaUpdate(virtualOffset, virtualSize, totalSize) + self:updateScrollBars() +end + +function UITextEdit:setVerticalScrollBar(scrollbar) + self.verticalScrollBar = scrollbar + self.verticalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.y = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:setHorizontalScrollBar(scrollbar) + self.horizontalScrollBar = scrollbar + self.horizontalScrollBar.onValueChange = function(scrollbar, value) + local virtualOffset = self:getTextVirtualOffset() + virtualOffset.x = value + self:setTextVirtualOffset(virtualOffset) + end + self:updateScrollBars() +end + +function UITextEdit:updateScrollBars() + local scrollSize = self:getTextTotalSize() + local scrollWidth = math.max(scrollSize.width - self:getTextVirtualSize().width, 0) + local scrollHeight = math.max(scrollSize.height - self:getTextVirtualSize().height, 0) + + local scrollbar = self.verticalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollHeight) + scrollbar:setValue(self:getTextVirtualOffset().y) + end + + local scrollbar = self.horizontalScrollBar + if scrollbar then + scrollbar:setMinimum(0) + scrollbar:setMaximum(scrollWidth) + scrollbar:setValue(self:getTextVirtualOffset().x) + end + +end + +-- todo: ontext change, focus to cursor \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwidget.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwidget.lua new file mode 100644 index 0000000..a6007ca --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwidget.lua @@ -0,0 +1,21 @@ +-- @docclass UIWidget + +function UIWidget:setMargin(...) + local params = {...} + if #params == 1 then + self:setMarginTop(params[1]) + self:setMarginRight(params[1]) + self:setMarginBottom(params[1]) + self:setMarginLeft(params[1]) + elseif #params == 2 then + self:setMarginTop(params[1]) + self:setMarginRight(params[2]) + self:setMarginBottom(params[1]) + self:setMarginLeft(params[2]) + elseif #params == 4 then + self:setMarginTop(params[1]) + self:setMarginRight(params[2]) + self:setMarginBottom(params[3]) + self:setMarginLeft(params[4]) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwindow.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwindow.lua new file mode 100644 index 0000000..39208df --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/ui/uiwindow.lua @@ -0,0 +1,46 @@ +-- @docclass +UIWindow = extends(UIWidget, "UIWindow") + +function UIWindow.create() + local window = UIWindow.internalCreate() + window:setTextAlign(AlignTopCenter) + window:setDraggable(true) + window:setAutoFocusPolicy(AutoFocusFirst) + return window +end + +function UIWindow:onKeyDown(keyCode, keyboardModifiers) + if keyboardModifiers == KeyboardNoModifier then + if keyCode == KeyEnter then + signalcall(self.onEnter, self) + elseif keyCode == KeyEscape then + signalcall(self.onEscape, self) + end + end +end + +function UIWindow:onFocusChange(focused) + if focused then self:raise() end +end + +function UIWindow:onDragEnter(mousePos) + if self.static then + return false + end + self:breakAnchors() + self.movingReference = { x = mousePos.x - self:getX(), y = mousePos.y - self:getY() } + return true +end + +function UIWindow:onDragLeave(droppedWidget, mousePos) + -- TODO: auto detect and reconnect anchors +end + +function UIWindow:onDragMove(mousePos, mouseMoved) + if self.static then + return + end + local pos = { x = mousePos.x - self.movingReference.x, y = mousePos.y - self.movingReference.y } + self:setPosition(pos) + self:bindRectToParent() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/util.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/util.lua new file mode 100644 index 0000000..910f06f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/corelib/util.lua @@ -0,0 +1,376 @@ +-- @docfuncs @{ + +function print(...) + local msg = "" + local args = {...} + local appendSpace = #args > 1 + for i,v in ipairs(args) do + msg = msg .. tostring(v) + if appendSpace and i < #args then + msg = msg .. ' ' + end + end + g_logger.log(LogInfo, msg) +end + +function pinfo(msg) + g_logger.log(LogInfo, msg) +end + +function perror(msg) + g_logger.log(LogError, msg) +end + +function pwarning(msg) + g_logger.log(LogWarning, msg) +end + +function pdebug(msg) + g_logger.log(LogDebug, msg) +end + +function fatal(msg) + g_logger.log(LogFatal, msg) +end + +function exit() + g_app.exit() +end + +function quit() + g_app.exit() +end + +function connect(object, arg1, arg2, arg3) + local signalsAndSlots + local pushFront + if type(arg1) == 'string' then + signalsAndSlots = { [arg1] = arg2 } + pushFront = arg3 + else + signalsAndSlots = arg1 + pushFront = arg2 + end + + for signal,slot in pairs(signalsAndSlots) do + if not object[signal] then + local mt = getmetatable(object) + if mt and type(object) == 'userdata' then + object[signal] = function(...) + return signalcall(mt[signal], ...) + end + end + end + + if not object[signal] then + object[signal] = slot + elseif type(object[signal]) == 'function' then + object[signal] = { object[signal] } + end + + if type(slot) ~= 'function' then + perror(debug.traceback('unable to connect a non function value')) + end + + if type(object[signal]) == 'table' then + if pushFront then + table.insert(object[signal], 1, slot) + else + table.insert(object[signal], #object[signal]+1, slot) + end + end + end +end + +function disconnect(object, arg1, arg2) + local signalsAndSlots + if type(arg1) == 'string' then + if arg2 == nil then + object[arg1] = nil + return + end + signalsAndSlots = { [arg1] = arg2 } + elseif type(arg1) == 'table' then + signalsAndSlots = arg1 + else + perror(debug.traceback('unable to disconnect')) + end + + for signal,slot in pairs(signalsAndSlots) do + if not object[signal] then + elseif type(object[signal]) == 'function' then + if object[signal] == slot then + object[signal] = nil + end + elseif type(object[signal]) == 'table' then + for k,func in pairs(object[signal]) do + if func == slot then + table.remove(object[signal], k) + + if #object[signal] == 1 then + object[signal] = object[signal][1] + end + break + end + end + end + end +end + +function newclass(name) + if not name then + perror(debug.traceback('new class has no name.')) + end + + local class = {} + function class.internalCreate() + local instance = {} + for k,v in pairs(class) do + instance[k] = v + end + return instance + end + class.create = class.internalCreate + class.__class = name + class.getClassName = function() return name end + return class +end + +function extends(base, name) + if not name then + perror(debug.traceback('extended class has no name.')) + end + + local derived = {} + function derived.internalCreate() + local instance = base.create() + for k,v in pairs(derived) do + instance[k] = v + end + return instance + end + derived.create = derived.internalCreate + derived.__class = name + derived.getClassName = function() return name end + return derived +end + +function runinsandbox(func, ...) + if type(func) == 'string' then + func, err = loadfile(resolvepath(func, 2)) + if not func then + error(err) + end + end + local env = { } + local oldenv = getfenv(0) + setmetatable(env, { __index = oldenv } ) + setfenv(0, env) + func(...) + setfenv(0, oldenv) + return env +end + +function loadasmodule(name, file) + file = file or resolvepath(name, 2) + if package.loaded[name] then + return package.loaded[name] + end + local env = runinsandbox(file) + package.loaded[name] = env + return env +end + +local function module_loader(modname) + local module = g_modules.getModule(modname) + if not module then + return '\n\tno module \'' .. modname .. '\'' + end + return function() + if not module:load() then + error('unable to load required module ' .. modname) + end + return module:getSandbox() + end +end +table.insert(package.loaders, 1, module_loader) + +function import(table) + assert(type(table) == 'table') + local env = getfenv(2) + for k,v in pairs(table) do + env[k] = v + end +end + +function export(what, key) + if key ~= nil then + _G[key] = what + else + for k,v in pairs(what) do + _G[k] = v + end + end +end + +function unexport(key) + if type(key) == 'table' then + for _k,v in pairs(key) do + _G[v] = nil + end + else + _G[key] = nil + end +end + +function getfsrcpath(depth) + depth = depth or 2 + local info = debug.getinfo(1+depth, "Sn") + local path + if info.short_src then + path = info.short_src:match("(.*)/.*") + end + if not path then + path = '/' + elseif path:sub(0, 1) ~= '/' then + path = '/' .. path + end + return path +end + +function resolvepath(filePath, depth) + if not filePath then return nil end + depth = depth or 1 + if filePath then + if filePath:sub(0, 1) ~= '/' then + local basepath = getfsrcpath(depth+1) + if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end + return basepath .. filePath + else + return filePath + end + else + local basepath = getfsrcpath(depth+1) + if basepath:sub(#basepath) ~= '/' then basepath = basepath .. '/' end + return basepath + end +end + +function toboolean(v) + if type(v) == 'string' then + v = v:trim():lower() + if v == '1' or v == 'true' then + return true + end + elseif type(v) == 'number' then + if v == 1 then + return true + end + elseif type(v) == 'boolean' then + return v + end + return false +end + +function fromboolean(boolean) + if boolean then + return 'true' + else + return 'false' + end +end + +function booleantonumber(boolean) + if boolean then + return 1 + else + return 0 + end +end + +function numbertoboolean(number) + if number ~= 0 then + return true + else + return false + end +end + +function protectedcall(func, ...) + local status, ret = pcall(func, ...) + if status then + return ret + end + + perror(ret) + return false +end + +function signalcall(param, ...) + if type(param) == 'function' then + local status, ret = pcall(param, ...) + if status then + return ret + else + perror(ret) + end + elseif type(param) == 'table' then + for k,v in pairs(param) do + local status, ret = pcall(v, ...) + if status then + if ret then return true end + else + perror(ret) + end + end + elseif param ~= nil then + error('attempt to call a non function value') + end + return false +end + +function tr(s, ...) + return string.format(s, ...) +end + +function getOppositeAnchor(anchor) + if anchor == AnchorLeft then + return AnchorRight + elseif anchor == AnchorRight then + return AnchorLeft + elseif anchor == AnchorTop then + return AnchorBottom + elseif anchor == AnchorBottom then + return AnchorTop + elseif anchor == AnchorVerticalCenter then + return AnchorHorizontalCenter + elseif anchor == AnchorHorizontalCenter then + return AnchorVerticalCenter + end + return anchor +end + +function makesingleton(obj) + local singleton = {} + if obj.getClassName then + for key,value in pairs(_G[obj:getClassName()]) do + if type(value) == 'function' then + singleton[key] = function(...) return value(obj, ...) end + end + end + end + return singleton +end + +function comma_value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then + break + end + end + return formatted +end + +-- @} \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.lua new file mode 100644 index 0000000..1c4057f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.lua @@ -0,0 +1,22 @@ +local CRASH_FILE = "exception.dmp" + +function init() + if g_resources.fileExists(CRASH_FILE) then + local crashLog = g_resources.readFileContents(CRASH_FILE) + local clientLog = g_logger.getLastLog() + HTTP.post(Services.crash, { + version = APP_VERSION, + build = g_app.getVersion(), + os = g_app.getOs(), + platform = g_window.getPlatformType(), + crash = base64.encode(crashLog), + log = base64.encode(clientLog) + }, function(data, err) + if err then + return g_logger.error("Error while reporting crash report: " .. err) + end + g_resources.deleteFile(CRASH_FILE) + end) + end +end + \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.otmod new file mode 100644 index 0000000..8c879a0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/crash_reporter/crash_reporter.otmod @@ -0,0 +1,8 @@ +Module + name: crash_reporter + description: Sends crash log to remote server + author: otclient@otclient.ovh + website: otclient.ovh + reloadable: false + scripts: [ crash_reporter ] + @onLoad: init() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.lua new file mode 100644 index 0000000..0500dfc --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.lua @@ -0,0 +1,391 @@ +actionPanel1 = nil +actionPanel2 = nil + +local actionConfig +local hotkeyAssignWindow +local actionButtonsInPanel = 50 + +ActionTypes = { + USE = 0, + USE_SELF = 1, + USE_TARGET = 2, + USE_WITH = 3, + EQUIP = 4 +} + +ActionColors = { + empty = '#00000033', + text = '#00000033', + itemUse = '#8888FF88', + itemUseSelf = '#00FF0088', + itemUseTarget = '#FF000088', + itemUseWith = '#F5B32588', + itemEquip = '#FFFFFF88' +} + +function init() + local bottomPanel = modules.game_interface.getActionPanel() + actionPanel1 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(actionPanel1, 1) + actionPanel2 = g_ui.loadUI('actionbar', bottomPanel) + bottomPanel:moveChildToIndex(actionPanel2, 1) + + actionConfig = g_configs.create("/actionbar.otml") + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown + }) + + -- remove hotkeys, also saves config + if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then + offline() + end + + actionPanel1:destroy() + actionPanel2:destroy() +end + +function show() + if not g_game.isOnline() then return end + actionPanel1:setOn(g_settings.getBoolean("actionBar1", false)) + actionPanel2:setOn(g_settings.getBoolean("actionBar2", false)) +end + +function hide() + actionPanel1:setOn(false) + actionPanel2:setOn(false) +end + +function switchMode(newMode) + if newMode then + actionPanel1:setImageColor('#ffffff88') + actionPanel2:setImageColor('#ffffff88') + else + actionPanel1:setImageColor('white') + actionPanel2:setImageColor('white') + end +end + +function online() + setupActionPanel(1, actionPanel1) + setupActionPanel(2, actionPanel2) + show() +end + +function offline() + hide() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + hotkeyAssignWindow = nil + end + + local gameRootPanel = modules.game_interface.getRootPanel() + for index, panel in ipairs({actionPanel1, actionPanel2}) do + local config = {} + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.config then + table.insert(config, child.config) + if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then + g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) + end + else + table.insert(config, {}) + end + if child.cooldownEvent then + removeEvent(child.cooldownEvent) + end + end + actionConfig:setNode('actions_' .. index, config) + panel.tabBar:destroyChildren() + end + actionConfig:save() +end + +function setupActionPanel(index, panel) + local rawConfig = actionConfig:getNode('actions_' .. index) or {} + local config = {} + for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string + config[tonumber(i)] = buttonConfig + end + + for i=1,actionButtonsInPanel do + local action = g_ui.createWidget('ActionButton', panel.tabBar) + action.config = config[i] or {} + setupAction(action) + end + + panel.nextButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount()) + end + panel.prevButton.onClick = function() + panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1) + end +end + +function setupAction(action) + local config = action.config + action.item:setShowCount(false) + action.onMouseRelease = actionOnMouseRelease + action.onTouchRelease = actionOnMouseRelease + action.callback = function(k, c, ticks) executeAction(action, ticks) end + action.item.onItemChange = nil -- disable callbacks for setup + + if config then + if type(config.text) == 'number' then + config.text = tostring(config.text) + end + if type(config.hotkey) == 'number' then + config.hotkey = tostring(config.hotkey) + end + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) + action.hotkeyLabel:setText(config.hotkey) + else + action.hotkeyLabel:setText("") + end + + action.text:setImageSource("") + action.cooldownTill = 0 + action.cooldownStart = 0 + if type(config.text) == 'string' and config.text:len() > 0 then + action.text:setText(config.text) + action.item:setBorderColor(ActionColors.text) + action.item:setOn(true) -- removes background + action.item:setItemId(0) + if Spells then + local spell, profile = Spells.getSpellByWords(config.text:lower()) + action.spell = spell + if action.spell and action.spell.icon and profile then + action.text:setImageSource(SpelllistSettings[profile].iconFile) + action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) + action.text:setText("") + end + end + else + action.text:setText("") + action.spell = nil + if type(config.item) == 'number' and config.item > 100 then + --action.item:setOn(true) + --action.item:setItemId(config.item) + --action.item:setItemCount(config.count or 1) + --setupActionType(action, config.actionType) + else + action.item:setItemId(0) + action.item:setOn(false) + action.item:setBorderColor(ActionColors.empty) + end + end + end + + action.item.onItemChange = actionOnItemChange +end + +function setupActionType(action, actionType) + local item = action.item:getItem() + if action.item:getItem():isMultiUse() then + if not actionType or actionType <= ActionTypes.USE then + actionType = ActionTypes.USE_WITH + end + elseif g_game.getClientVersion() >= 910 then + if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then + actionType = ActionTypes.USE + end + else + actionType = ActionTypes.USE + end + + action.config.actionType = actionType + if action.config.actionType == ActionTypes.USE then + action.item:setBorderColor(ActionColors.itemUse) + elseif action.config.actionType == ActionTypes.USE_SELF then + action.item:setBorderColor(ActionColors.itemUseSelf) + elseif action.config.actionType == ActionTypes.USE_TARGET then + action.item:setBorderColor(ActionColors.itemUseTarget) + elseif action.config.actionType == ActionTypes.USE_WITH then + action.item:setBorderColor(ActionColors.itemUseWith) + elseif action.config.actionType == ActionTypes.EQUIP then + action.item:setBorderColor(ActionColors.itemEquip) + end +end + +function updateAction(action, newConfig) + local config = action.config + if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) + end + for key, val in pairs(newConfig) do + action.config[key] = val + end + setupAction(action) +end + +function actionOnMouseRelease(action, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end + if mouseButton == MouseRightButton or not action.item:isOn() then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Set text'), function() + modules.client_textedit.singlelineEditor(action.config.text or "", function(newText) + updateAction(action, {text=newText, item=0}) + end) + end) + menu:addOption(tr('Set hotkey'), function() + if hotkeyAssignWindow then + hotkeyAssignWindow:destroy() + end + local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) + assignWindow:grabKeyboard() + assignWindow.comboPreview.keyCombo = '' + assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) + assignWindow.comboPreview.keyCombo = keyCombo + assignWindow.comboPreview:resizeToText() + return true + end + assignWindow.onDestroy = function(widget) + if widget == hotkeyAssignWindow then + hotkeyAssignWindow = nil + end + end + assignWindow.addButton.onClick = function() + updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)}) + assignWindow:destroy() + end + hotkeyAssignWindow = assignWindow + end) + menu:addSeparator() + menu:addOption(tr('Clear'), function() + updateAction(action, {hotkey="", text="", item=0, count=1}) + end) + menu:display(mousePosition) + return true + elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then + action.callback() + return true + end + return false +end + +function actionOnItemChange(widget) + updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()}) +end + +function onSpellCooldown(iconId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.id == iconId then + startCooldown(child, duration) + end + end + end +end + +function onSpellGroupCooldown(groupId, duration) + for index, panel in ipairs({actionPanel1, actionPanel2}) do + for i, child in ipairs(panel.tabBar:getChildren()) do + if child.spell and child.spell.group then + for group, duration in pairs(child.spell.group) do + if groupId == group then + startCooldown(child, duration) + end + end + end + end + end +end + +function startCooldown(action, duration) + if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then + return -- already has cooldown with greater duration + end + action.cooldownStart = g_clock.millis() + action.cooldownTill = g_clock.millis() + duration + updateCooldown(action) +end + +function updateCooldown(action) + if not action or not action.cooldownTill then return end + local timeleft = action.cooldownTill - g_clock.millis() + if timeleft <= 30 then + action.cooldown:setPercent(100) + action.cooldownEvent = nil + return + end + local duration = action.cooldownTill - action.cooldownStart + action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) + action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) +end + +function executeAction(action, ticks) + if not action.config then return end + if type(ticks) ~= 'number' then ticks = 0 end + + local actionDelay = 100 + if ticks == 0 then + actionDelay = 200 -- for first use + elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then + return + end + + local actionType = action.config.actionType + + if type(action.config.text) == 'string' and action.config.text:len() > 0 then + if g_app.isMobile() then -- turn to direction of targer + local target = g_game.getAttackingCreature() + if target then + local pos = g_game.getLocalPlayer():getPosition() + local tpos = target:getPosition() + if pos and tpos then + local offx = tpos.x - pos.x + local offy = tpos.y - pos.y + if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.North) + elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then + g_game.turn(Directions.South) + elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.West) + elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then + g_game.turn(Directions.East) + end + end + end + end + if modules.game_interface.isChatVisible() then + modules.game_console.sendMessage(action.config.text) + else + g_game.talk(action.config.text) + end + action.actionDelayTo = g_clock.millis() + actionDelay + elseif action.item:getItemId() > 0 then + if actionType == ActionTypes.USE then + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_SELF then + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_TARGET then + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.USE_WITH then + action.actionDelayTo = g_clock.millis() + actionDelay + elseif actionType == ActionTypes.EQUIP then + action.actionDelayTo = g_clock.millis() + actionDelay + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otmod new file mode 100644 index 0000000..b11c62c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otmod @@ -0,0 +1,9 @@ +Module + name: game_actionbar + description: Action bar + author: otclient@otclient.ovh + website: otclient.ovh + sandboxed: true + scripts: [ actionbar ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otui new file mode 100644 index 0000000..2f30eef --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_actionbar/actionbar.otui @@ -0,0 +1,145 @@ +ActionButton < Panel + font: cipsoftFont + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 40 + padding: 1 1 1 1 + margin-left: 1 + + $first: + anchors.left: parent.left + + $!first: + anchors.left: prev.right + + Item + id: item + anchors.fill: parent + &selectable: true + &editable: false + virtual: true + border-width: 1 + + border-color: #00000000 + + $!on: + image-source: /images/game/actionbarslot + + Label + id: text + anchors.fill: parent + text-auto-resize: true + text-wrap: true + phantom: true + text-align: center + font: verdana-9px + + Label + id: hotkeyLabel + anchors.top: parent.top + anchors.left: parent.left + margin: 2 3 3 3 + text-auto-resize: true + text-wrap: false + phantom: true + font: small-9px + color: yellow + + UIProgressRect + id: cooldown + background: #585858AA + percent: 100 + focusable: false + phantom: true + anchors.fill: parent + margin: 1 1 1 1 + +Panel + id: actionBar + focusable: false + image-source: /images/ui/panel_side + image-border: 4 + margin-top: -1 + + $on: + height: 40 + visible: true + + $!on: + height: 0 + visible: false + + TabButton + id: prevButton + icon: /images/game/console/leftarrow + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-left: 1 + margin-top: 1 + margin-bottom: 2 + + Panel + id: tabBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.right + anchors.right: next.left + margin-right: 3 + clipping: true + + TabButton + id: nextButton + icon: /images/game/console/rightarrow + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-right: 1 + margin-top: 1 + margin-bottom: 2 + + +ActionAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to use for action') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current action hotkey: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.lua new file mode 100644 index 0000000..bc2e03c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.lua @@ -0,0 +1,458 @@ +battleWindow = nil +battleButton = nil +battlePanel = nil +filterPanel = nil +toggleFilterButton = nil + +mouseWidget = nil +updateEvent = nil + +hoveredCreature = nil +newHoveredCreature = nil +prevCreature = nil + +battleButtons = {} +local ageNumber = 1 +local ages = {} + +function init() + g_ui.importStyle('battlebutton') + battleButton = modules.client_topmenu.addRightGameToggleButton('battleButton', tr('Battle') .. ' (Ctrl+B)', '/images/topbuttons/battle', toggle, false, 2) + battleButton:setOn(true) + battleWindow = g_ui.loadUI('battle', modules.game_interface.getRightPanel()) + g_keyboard.bindKeyDown('Ctrl+B', toggle) + + -- this disables scrollbar auto hiding + local scrollbar = battleWindow:getChildById('miniwindowScrollBar') + scrollbar:mergeStyle({ ['$!on'] = { }}) + + battlePanel = battleWindow:recursiveGetChildById('battlePanel') + + filterPanel = battleWindow:recursiveGetChildById('filterPanel') + toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton') + + if isHidingFilters() then + hideFilterPanel() + end + + local sortTypeBox = filterPanel.sortPanel.sortTypeBox + local sortOrderBox = filterPanel.sortPanel.sortOrderBox + + mouseWidget = g_ui.createWidget('UIButton') + mouseWidget:setVisible(false) + mouseWidget:setFocusable(false) + mouseWidget.cancelNextRelease = false + + battleWindow:setContentMinimumHeight(80) + + sortTypeBox:addOption('Name', 'name') + sortTypeBox:addOption('Distance', 'distance') + sortTypeBox:addOption('Total age', 'age') + sortTypeBox:addOption('Screen age', 'screenage') + sortTypeBox:addOption('Health', 'health') + sortTypeBox:setCurrentOptionByData(getSortType()) + sortTypeBox.onOptionChange = onChangeSortType + + sortOrderBox:addOption('Asc.', 'asc') + sortOrderBox:addOption('Desc.', 'desc') + sortOrderBox:setCurrentOptionByData(getSortOrder()) + sortOrderBox.onOptionChange = onChangeSortOrder + + battleWindow:setup() + + for i=1,30 do + local battleButton = g_ui.createWidget('BattleButton', battlePanel) + battleButton:setup() + battleButton:hide() + battleButton.onHoverChange = onBattleButtonHoverChange + battleButton.onMouseRelease = onBattleButtonMouseRelease + table.insert(battleButtons, battleButton) + end + + updateBattleList() + + connect(LocalPlayer, { + onPositionChange = onPlayerPositionChange + }) + connect(Creature, { + onAppear = updateSquare, + onDisappear = updateSquare + }) + connect(g_game, { + onAttackingCreatureChange = updateSquare, + onFollowingCreatureChange = updateSquare + }) +end + +function terminate() + if battleButton == nil then + return + end + + battleButtons = {} + + g_keyboard.unbindKeyDown('Ctrl+B') + battleButton:destroy() + battleWindow:destroy() + mouseWidget:destroy() + + disconnect(LocalPlayer, { + onPositionChange = onPlayerPositionChange + }) + disconnect(Creature, { + onAppear = onCreatureAppear, + onDisappear = onCreatureDisappear + }) + disconnect(g_game, { + onAttackingCreatureChange = updateSquare, + onFollowingCreatureChange = updateSquare + }) + + removeEvent(updateEvent) +end + +function toggle() + if battleButton:isOn() then + battleWindow:close() + battleButton:setOn(false) + else + battleWindow:open() + battleButton:setOn(true) + end +end + +function onMiniWindowClose() + battleButton:setOn(false) +end + +function getSortType() + local settings = g_settings.getNode('BattleList') + if not settings then + if g_app.isMobile() then + return 'distance' + else + return 'name' + end + end + return settings['sortType'] +end + +function setSortType(state) + settings = {} + settings['sortType'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function getSortOrder() + local settings = g_settings.getNode('BattleList') + if not settings then + return 'asc' + end + return settings['sortOrder'] +end + +function setSortOrder(state) + settings = {} + settings['sortOrder'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function isSortAsc() + return getSortOrder() == 'asc' +end + +function isSortDesc() + return getSortOrder() == 'desc' +end + +function isHidingFilters() + local settings = g_settings.getNode('BattleList') + if not settings then + return false + end + return settings['hidingFilters'] +end + +function setHidingFilters(state) + settings = {} + settings['hidingFilters'] = state + g_settings.mergeNode('BattleList', settings) +end + +function hideFilterPanel() + filterPanel.originalHeight = filterPanel:getHeight() + filterPanel:setHeight(0) + toggleFilterButton:getParent():setMarginTop(0) + toggleFilterButton:setImageClip(torect("0 0 21 12")) + setHidingFilters(true) + filterPanel:setVisible(false) +end + +function showFilterPanel() + toggleFilterButton:getParent():setMarginTop(5) + filterPanel:setHeight(filterPanel.originalHeight) + toggleFilterButton:setImageClip(torect("21 0 21 12")) + setHidingFilters(false) + filterPanel:setVisible(true) +end + +function toggleFilterPanel() + if filterPanel:isVisible() then + hideFilterPanel() + else + showFilterPanel() + end +end + +function onChangeSortType(comboBox, option, value) + setSortType(value:lower()) +end + +function onChangeSortOrder(comboBox, option, value) + -- Replace dot in option name + setSortOrder(value:lower():gsub('[.]', '')) +end + +-- functions +function updateBattleList() + removeEvent(updateEvent) + updateEvent = scheduleEvent(updateBattleList, 100) + checkCreatures() +end + +function checkCreatures() + if not battlePanel or not g_game.isOnline() then + return + end + + local player = g_game.getLocalPlayer() + if not player then + return + end + + local dimension = modules.game_interface.getMapPanel():getVisibleDimension() + local spectators = g_map.getSpectatorsInRangeEx(player:getPosition(), false, math.floor(dimension.width / 2), math.floor(dimension.width / 2), math.floor(dimension.height / 2), math.floor(dimension.height / 2)) + local maxCreatures = battlePanel:getChildCount() + + local creatures = {} + local now = g_clock.millis() + local resetAgePoint = now - 250 + for _, creature in ipairs(spectators) do + if doCreatureFitFilters(creature) and #creatures < maxCreatures then + if not creature.lastSeen or creature.lastSeen < resetAgePoint then + creature.screenAge = now + end + creature.lastSeen = now + if not ages[creature:getId()] then + if ageNumber > 1000 then + ageNumber = 1 + ages = {} + end + ages[creature:getId()] = ageNumber + ageNumber = ageNumber + 1 + end + table.insert(creatures, creature) + end + end + + updateSquare() + sortCreatures(creatures) + battlePanel:getLayout():disableUpdates() + + -- sorting + local ascOrder = isSortAsc() + for i=1,#creatures do + local creature = creatures[i] + if ascOrder then + creature = creatures[#creatures - i + 1] + end + local battleButton = battleButtons[i] + battleButton:creatureSetup(creature) + battleButton:show() + battleButton:setOn(true) + end + + if g_app.isMobile() and #creatures > 0 then + onBattleButtonHoverChange(battleButtons[1], true) + end + + for i=#creatures + 1,maxCreatures do + if battleButtons[i]:isHidden() then break end + battleButtons[i]:hide() + battleButton:setOn(false) + end + + battlePanel:getLayout():enableUpdates() + battlePanel:getLayout():update() +end + +function doCreatureFitFilters(creature) + if creature:isLocalPlayer() then + return false + end + if creature:getHealthPercent() <= 0 then + return false + end + + local pos = creature:getPosition() + if not pos then return false end + + local localPlayer = g_game.getLocalPlayer() + if pos.z ~= localPlayer:getPosition().z or not creature:canBeSeen() then return false end + + local hidePlayers = filterPanel.buttons.hidePlayers:isChecked() + local hideNPCs = filterPanel.buttons.hideNPCs:isChecked() + local hideMonsters = filterPanel.buttons.hideMonsters:isChecked() + local hideSkulls = filterPanel.buttons.hideSkulls:isChecked() + local hideParty = filterPanel.buttons.hideParty:isChecked() + + if hidePlayers and creature:isPlayer() then + return false + elseif hideNPCs and creature:isNpc() then + return false + elseif hideMonsters and creature:isMonster() then + return false + elseif hideSkulls and creature:isPlayer() and creature:getSkull() == SkullNone then + return false + elseif hideParty and creature:getShield() > ShieldWhiteBlue then + return false + end + + return true +end + +local function getDistanceBetween(p1, p2) + return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) +end + +function sortCreatures(creatures) + local player = g_game.getLocalPlayer() + + if getSortType() == 'distance' then + local playerPos = player:getPosition() + table.sort(creatures, function(a, b) + if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then + return ages[a:getId()] > ages[b:getId()] + end + return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition()) + end) + elseif getSortType() == 'health' then + table.sort(creatures, function(a, b) + if a:getHealthPercent() == b:getHealthPercent() then + return ages[a:getId()] > ages[b:getId()] + end + return a:getHealthPercent() > b:getHealthPercent() + end) + elseif getSortType() == 'age' then + table.sort(creatures, function(a, b) return ages[a:getId()] > ages[b:getId()] end) + elseif getSortType() == 'screenage' then + table.sort(creatures, function(a, b) return a.screenAge > b.screenAge end) + else -- name + table.sort(creatures, function(a, b) + if a:getName():lower() == b:getName():lower() then + return ages[a:getId()] > ages[b:getId()] + end + return a:getName():lower() > b:getName():lower() + end) + end +end + +-- other functions +function onBattleButtonMouseRelease(self, mousePosition, mouseButton) + if mouseWidget.cancelNextRelease then + mouseWidget.cancelNextRelease = false + return false + end + if not self.creature then + return false + end + if ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) + or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + mouseWidget.cancelNextRelease = true + g_game.look(self.creature, true) + return true + elseif mouseButton == MouseLeftButton and g_keyboard.isShiftPressed() then + g_game.look(self.creature, true) + return true + elseif mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then + modules.game_interface.createThingMenu(mousePosition, nil, nil, self.creature) + return true + elseif mouseButton == MouseLeftButton and not g_mouse.isPressed(MouseRightButton) then + if self.isTarget then + g_game.cancelAttack() + else + g_game.attack(self.creature) + end + return true + end + return false +end + +function onBattleButtonHoverChange(battleButton, hovered) + if not hovered then + newHoveredCreature = nil + else + newHoveredCreature = battleButton.creature + end + if battleButton.isHovered ~= hovered then + battleButton.isHovered = hovered + battleButton:update() + end + updateSquare() +end + +function onPlayerPositionChange(creature, newPos, oldPos) + addEvent(checkCreatures) +end + +local CreatureButtonColors = { + onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, + onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, + onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' } +} + +function updateSquare() + local following = g_game.getFollowingCreature() + local attacking = g_game.getAttackingCreature() + + if newHoveredCreature == nil then + if hoveredCreature ~= nil then + hoveredCreature:hideStaticSquare() + hoveredCreature = nil + end + else + if hoveredCreature ~= nil then + hoveredCreature:hideStaticSquare() + end + hoveredCreature = newHoveredCreature + hoveredCreature:showStaticSquare(CreatureButtonColors.onIdle.hovered) + end + + local color = CreatureButtonColors.onIdle + local creature = nil + if attacking then + color = CreatureButtonColors.onTargeted + creature = attacking + elseif following then + color = CreatureButtonColors.onFollowed + creature = following + end + + if prevCreature ~= creature then + if prevCreature ~= nil then + prevCreature:hideStaticSquare() + end + prevCreature = creature + end + + if not creature then + return + end + + color = creature == hoveredCreature and color.hovered or color.notHovered + creature:showStaticSquare(color) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otmod new file mode 100644 index 0000000..ba50425 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otmod @@ -0,0 +1,9 @@ +Module + name: game_battle + description: Manage battle window (new) + author: otclient@otclient.ovh + website: otclient.ovh + sandboxed: true + scripts: [ battle ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otui new file mode 100644 index 0000000..c3af2e3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battle.otui @@ -0,0 +1,155 @@ +BattleIcon < UICheckBox + size: 20 20 + image-color: white + image-rect: 0 0 20 20 + + $hover !disabled: + color: #cccccc + + $!checked: + image-clip: 0 0 20 20 + + $hover !checked: + image-clip: 0 40 20 20 + + $checked: + image-clip: 0 20 20 20 + + $hover checked: + image-clip: 0 60 20 20 + + $disabled: + image-color: #ffffff88 + +BattlePlayers < BattleIcon + image-source: /images/game/battle/battle_players + +BattleNPCs < BattleIcon + image-source: /images/game/battle/battle_npcs + +BattleMonsters < BattleIcon + image-source: /images/game/battle/battle_monsters + +BattleSkulls < BattleIcon + image-source: /images/game/battle/battle_skulls + +BattleParty < BattleIcon + image-source: /images/game/battle/battle_party + +MiniWindow + id: battleWindow + !text: tr('Battle') + height: 166 + icon: /images/topbuttons/battle + @onClose: modules.game_battle.onMiniWindowClose() + &save: true + &autoOpen: false + + Panel + id: filterPanel + margin-top: 26 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + height: 45 + + Panel + id: buttons + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + height: 20 + width: 120 + layout: + type: horizontalBox + spacing: 5 + + BattlePlayers + id: hidePlayers + !tooltip: tr('Hide players') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + BattleNPCs + id: hideNPCs + !tooltip: tr('Hide Npcs') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + BattleMonsters + id: hideMonsters + !tooltip: tr('Hide monsters') + @onCheckChange: modules.game_battle.checkCreatures() + + BattleSkulls + id: hideSkulls + !tooltip: tr('Hide non-skull players') + @onCheckChange: modules.game_battle.checkCreatures() + + BattleParty + id: hideParty + !tooltip: tr('Hide party members') + @onSetup: if g_app.isMobile() then self:setChecked(true) end + @onCheckChange: modules.game_battle.checkCreatures() + + Panel + id: sortPanel + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 20 + margin-top: 6 + + ComboBox + id: sortTypeBox + width: 90 + anchors.top: parent.top + anchors.left: prev.right + anchors.horizontalCenter: parent.horizontalCenter + margin-left: -31 + + ComboBox + id: sortOrderBox + width: 60 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 4 + + Panel + height: 18 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + margin-top: 4 + + UIWidget + id: toggleFilterButton + anchors.top: prev.top + width: 21 + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/ui/arrow_vertical + image-rect: 0 0 21 12 + image-clip: 21 0 21 12 + @onClick: modules.game_battle.toggleFilterPanel() + phantom: false + + HorizontalSeparator + anchors.top: prev.top + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + margin-right: 1 + margin-top: 11 + + MiniWindowContents + anchors.top: prev.bottom + margin-top: 6 + + Panel + id: battlePanel + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 5 + padding-right: 5 + layout: + type: verticalBox + fit-children: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battlebutton.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battlebutton.otui new file mode 100644 index 0000000..38a0158 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_battle/battlebutton.otui @@ -0,0 +1,2 @@ +BattleButton < CreatureButton + &isBattleButton: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.lua new file mode 100644 index 0000000..51d631a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.lua @@ -0,0 +1,36 @@ +-- TODO: find another hotkey for this. Ctrl+Z will be reserved to undo on textedits. +HOTKEY = 'Ctrl+Z' + +bugReportWindow = nil +bugTextEdit = nil + +function init() + g_ui.importStyle('bugreport') + + bugReportWindow = g_ui.createWidget('BugReportWindow', rootWidget) + bugReportWindow:hide() + + bugTextEdit = bugReportWindow:getChildById('bugTextEdit') + + g_keyboard.bindKeyDown(HOTKEY, show, modules.game_interface.getRootPanel()) +end + +function terminate() + g_keyboard.unbindKeyDown(HOTKEY, modules.game_interface.getRootPanel()) + bugReportWindow:destroy() +end + +function doReport() + g_game.reportBug(bugTextEdit:getText()) + bugReportWindow:hide() + modules.game_textmessage.displayGameMessage(tr('Bug report sent.')) +end + +function show() + if g_game.isOnline() then + bugTextEdit:setText('') + bugReportWindow:show() + bugReportWindow:raise() + bugReportWindow:focus() + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otmod new file mode 100644 index 0000000..5306bb5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otmod @@ -0,0 +1,9 @@ +Module + name: game_bugreport + description: Bug report interface (Ctrl+Z) + author: edubart + website: https://github.com/edubart/otclient + scripts: [ bugreport ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otui new file mode 100644 index 0000000..8ce215e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_bugreport/bugreport.otui @@ -0,0 +1,39 @@ +BugReportWindow < MainWindow + !text: tr('Report Bug') + size: 280 250 + @onEscape: self:hide() + + Label + id: bugLabel + !text: tr('Please use this dialog to only report bugs. Do not report rule violations here!') + text-wrap: true + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + MultilineTextEdit + id: bugTextEdit + anchors.top: bugLabel.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: sendButton.top + margin-top: 4 + margin-bottom: 8 + + Button + id: sendButton + !text: tr('Send') + anchors.bottom: cancelButton.bottom + anchors.right: cancelButton.left + margin-right: 10 + width: 80 + &onClick: doReport + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 80 + @onClick: self:getParent():hide() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.lua new file mode 100644 index 0000000..49a1385 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.lua @@ -0,0 +1,49 @@ +buttonsWindow = nil +contentsPanel = nil + +function init() + buttonsWindow = g_ui.loadUI('buttons', modules.game_interface.getRightPanel()) + buttonsWindow:disableResize() + buttonsWindow:setup() + contentsPanel = buttonsWindow.contentsPanel + if not buttonsWindow.forceOpen or not contentsPanel.buttons then + buttonsWindow:close() + end +end + +function terminate() + buttonsWindow:destroy() +end + +function takeButtons(buttons) + if not buttonsWindow.forceOpen or not contentsPanel.buttons then return end + for i, button in ipairs(buttons) do + takeButton(button, true) + end + updateOrder() +end + +function takeButton(button, dontUpdateOrder) + if not buttonsWindow.forceOpen or not contentsPanel.buttons then return end + button:setParent(contentsPanel.buttons) + if not dontUpdateOrder then + updateOrder() + end +end + +function updateOrder() + local children = contentsPanel.buttons:getChildren() + table.sort(children, function(a, b) + return (a.index or 1000) < (b.index or 1000) + end) + contentsPanel.buttons:reorderChildren(children) + local visibleCount = 0 + for _, child in ipairs(children) do + if child:isVisible() then + visibleCount = visibleCount + 1 + end + end + if visibleCount > 6 and buttonsWindow:getHeight() < 30 then + buttonsWindow:setHeight(buttonsWindow:getHeight() + 22) + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otmod new file mode 100644 index 0000000..3f807e6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otmod @@ -0,0 +1,8 @@ +Module + name: game_buttons + description: Shows miniwindow with buttons + author: otclient@otclient.ovh + sandboxed: true + scripts: [ buttons ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otui new file mode 100644 index 0000000..963a541 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_buttons/buttons.otui @@ -0,0 +1,6 @@ +GameButtonsWindow + id: buttons + &save: true + !text: tr("Buttons") + icon: /images/topbuttons/buttons + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/channelswindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/channelswindow.otui new file mode 100644 index 0000000..94e4d40 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/channelswindow.otui @@ -0,0 +1,65 @@ +ChannelListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #ffffff22 + color: #ffffff + +MainWindow + id: channelsWindow + !text: tr('Channels') + size: 250 238 + @onEscape: self:destroy() + + TextList + id: channelList + vertical-scrollbar: channelsScrollBar + anchors.fill: parent + anchors.bottom: next.top + margin-bottom: 10 + padding: 1 + focusable: false + + Label + id: openPrivateChannelWithLabel + !text: tr('Open a private message channel:') + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + text-align: center + margin-bottom: 2 + + TextEdit + id: openPrivateChannelWith + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonOpen + !text: tr('Open') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: self:getParent():onEnter() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() + + VerticalScrollBar + id: channelsScrollBar + anchors.top: channelList.top + anchors.bottom: channelList.bottom + anchors.right: channelList.right + step: 14 + pixels-scroll: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/communicationwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/communicationwindow.otui new file mode 100644 index 0000000..c5e44c0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/communicationwindow.otui @@ -0,0 +1,206 @@ +IgnoreListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + color: #ffffff + +WhiteListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + color: #ffffff + + +MainWindow + id: communicationWindow + !text: tr('Ignore List') + size: 515 410 + @onEscape: self:destroy() + + CheckBox + id: checkboxUseIgnoreList + !text: tr('Activate ignorelist') + anchors.left: parent.left + anchors.top: parent.top + width: 180 + + Label + !text: tr('Ignored Players:') + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 10 + + TextList + id: ignoreList + vertical-scrollbar: ignoreListScrollBar + anchors.left: parent.left + anchors.top: prev.bottom + height: 150 + width: 230 + margin-bottom: 10 + margin-top: 3 + padding: 1 + focusable: false + + TextEdit + id: ignoreNameEdit + anchors.top: prev.bottom + anchors.left: parent.left + width: 110 + margin-top: 5 + + Button + id: buttonIgnoreAdd + !text: tr('Add') + width: 48 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Button + id: buttonIgnoreRemove + !text: tr('Remove') + width: 64 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Label + !text: tr('Global ignore settings') + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 20 + + CheckBox + id: checkboxIgnorePrivateMessages + !text: tr('Ignore Private Messages') + anchors.left: parent.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + CheckBox + id: checkboxIgnoreYelling + !text: tr('Ignore Yelling') + anchors.left: parent.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + CheckBox + id: checkboxUseWhiteList + !text: tr('Activate whitelist') + anchors.top: parent.top + anchors.left: ignoreList.right + margin-left: 20 + width: 180 + + Label + !text: tr('Allowed Players:') + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 10 + + TextList + id: whiteList + vertical-scrollbar: whiteListScrollBar + anchors.left: prev.left + anchors.top: prev.bottom + height: 150 + width: 230 + margin-bottom: 10 + margin-top: 3 + padding: 1 + focusable: false + + TextEdit + id: whitelistNameEdit + anchors.top: prev.bottom + anchors.left: prev.left + width: 110 + margin-top: 5 + + Button + id: buttonWhitelistAdd + !text: tr('Add') + width: 48 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Button + id: buttonWhitelistRemove + !text: tr('Remove') + width: 64 + height: 20 + margin-left: 5 + anchors.top: prev.top + anchors.left: prev.right + + Label + !text: tr('Global whitelist settings') + anchors.left: whiteList.left + anchors.top: prev.bottom + margin-top: 20 + + CheckBox + id: checkboxAllowVIPs + !text: tr('Allow VIPs to message you') + anchors.left: prev.left + anchors.top: prev.bottom + width: 180 + margin-top: 5 + + Panel + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 30 + + Panel + size: 160 30 + anchors.horizontalCenter: parent.horizontalCenter + + Button + id: buttonSave + !text: tr('Save') + width: 75 + anchors.top: parent.top + anchors.left: parent.left + + Button + id: buttonCancel + !text: tr('Cancel') + width: 75 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 10 + + VerticalScrollBar + id: ignoreListScrollBar + anchors.top: ignoreList.top + anchors.bottom: ignoreList.bottom + anchors.right: ignoreList.right + step: 14 + pixels-scroll: true + + VerticalScrollBar + id: whiteListScrollBar + anchors.top: whiteList.top + anchors.bottom: whiteList.bottom + anchors.right: whiteList.right + step: 14 + pixels-scroll: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.lua new file mode 100644 index 0000000..f76a218 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.lua @@ -0,0 +1,1524 @@ +SpeakTypesSettings = { + none = {}, + say = { speakType = MessageModes.Say, color = '#FFFF00' }, + whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' }, + yell = { speakType = MessageModes.Yell, color = '#FFFF00' }, + broadcast = { speakType = MessageModes.GamemasterBroadcast, color = '#F55E5E' }, + private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true }, + privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true }, + privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true }, + privatePlayerToNpc = { speakType = MessageModes.NpcTo, color = '#9F9DFD', private = true, npcChat = true }, + privateNpcToPlayer = { speakType = MessageModes.NpcFrom, color = '#5FF7F7', private = true, npcChat = true }, + channelYellow = { speakType = MessageModes.Channel, color = '#FFFF00' }, + channelWhite = { speakType = MessageModes.ChannelManagement, color = '#FFFFFF' }, + channelRed = { speakType = MessageModes.GamemasterChannel, color = '#F55E5E' }, + channelOrange = { speakType = MessageModes.ChannelHighlight, color = '#F6A731' }, + monsterSay = { speakType = MessageModes.MonsterSay, color = '#FE6500', hideInConsole = true}, + monsterYell = { speakType = MessageModes.MonsterYell, color = '#FE6500', hideInConsole = true}, + rvrAnswerFrom = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, + rvrAnswerTo = { speakType = MessageModes.RVRAnswer, color = '#FE6500' }, + rvrContinue = { speakType = MessageModes.RVRContinue, color = '#FFFF00' }, +} + +SpeakTypes = { + [MessageModes.Say] = SpeakTypesSettings.say, + [MessageModes.Whisper] = SpeakTypesSettings.whisper, + [MessageModes.Yell] = SpeakTypesSettings.yell, + [MessageModes.GamemasterBroadcast] = SpeakTypesSettings.broadcast, + [MessageModes.PrivateFrom] = SpeakTypesSettings.private, + [MessageModes.GamemasterPrivateFrom] = SpeakTypesSettings.privateRed, + [MessageModes.NpcTo] = SpeakTypesSettings.privatePlayerToNpc, + [MessageModes.NpcFrom] = SpeakTypesSettings.privateNpcToPlayer, + [MessageModes.Channel] = SpeakTypesSettings.channelYellow, + [MessageModes.ChannelManagement] = SpeakTypesSettings.channelWhite, + [MessageModes.GamemasterChannel] = SpeakTypesSettings.channelRed, + [MessageModes.ChannelHighlight] = SpeakTypesSettings.channelOrange, + [MessageModes.MonsterSay] = SpeakTypesSettings.monsterSay, + [MessageModes.MonsterYell] = SpeakTypesSettings.monsterYell, + [MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite, + [MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue, + [MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom, + [MessageModes.NpcFromStartBlock] = SpeakTypesSettings.privateNpcToPlayer, + + -- ignored types + [MessageModes.Spell] = SpeakTypesSettings.none, + [MessageModes.BarkLow] = SpeakTypesSettings.none, + [MessageModes.BarkLoud] = SpeakTypesSettings.none, +} + +SayModes = { + [1] = { speakTypeDesc = 'whisper', icon = '/images/game/console/whisper' }, + [2] = { speakTypeDesc = 'say', icon = '/images/game/console/say' }, + [3] = { speakTypeDesc = 'yell', icon = '/images/game/console/yell' } +} + +ChannelEventFormats = { + [ChannelEvent.Join] = '%s joined the channel.', + [ChannelEvent.Leave] = '%s left the channel.', + [ChannelEvent.Invite] = '%s has been invited to the channel.', + [ChannelEvent.Exclude] = '%s has been removed from the channel.', +} + +MAX_HISTORY = 500 +MAX_LINES = 100 +HELP_CHANNEL = 9 + +consolePanel = nil +consoleContentPanel = nil +consoleTabBar = nil +consoleTextEdit = nil +consoleToggleChat = nil +channels = nil +channelsWindow = nil +communicationWindow = nil +ownPrivateName = nil +messageHistory = {} +currentMessageIndex = 0 +ignoreNpcMessages = false +defaultTab = nil +serverTab = nil +violationsChannelId = nil +violationWindow = nil +violationReportTab = nil +ignoredChannels = {} +filters = {} + +floatingMode = false + +local communicationSettings = { + useIgnoreList = true, + useWhiteList = true, + privateMessages = false, + yelling = false, + allowVIPs = false, + ignoredPlayers = {}, + whitelistedPlayers = {} +} + +function init() + connect(g_game, { + onTalk = onTalk, + onChannelList = onChannelList, + onOpenChannel = onOpenChannel, + onCloseChannel = onCloseChannel, + onChannelEvent = onChannelEvent, + onOpenPrivateChannel = onOpenPrivateChannel, + onOpenOwnPrivateChannel = onOpenOwnPrivateChannel, + onRuleViolationChannel = onRuleViolationChannel, + onRuleViolationRemove = onRuleViolationRemove, + onRuleViolationCancel = onRuleViolationCancel, + onRuleViolationLock = onRuleViolationLock, + onGameStart = online, + onGameEnd = offline, + }) + + consolePanel = g_ui.loadUI('console', modules.game_interface.getBottomPanel()) + consoleTextEdit = consolePanel:getChildById('consoleTextEdit') + consoleContentPanel = consolePanel:getChildById('consoleContentPanel') + consoleTabBar = consolePanel:getChildById('consoleTabBar') + consoleTabBar:setContentWidget(consoleContentPanel) + channels = {} + + consolePanel.onDragEnter = onDragEnter + consolePanel.onDragLeave = onDragLeave + consolePanel.onDragMove = onDragMove + consoleTabBar.onDragEnter = onDragEnter + consoleTabBar.onDragLeave = onDragLeave + consoleTabBar.onDragMove = onDragMove + + consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers) + if not (keyboardModifiers == KeyboardCtrlModifier and keyCode == KeyC) then return false end + + local tab = consoleTabBar:getCurrentTab() + if not tab then return false end + + local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText + if not selection then return false end + + g_window.setClipboardText(selection) + return true + end + + g_keyboard.bindKeyPress('Shift+Up', function() navigateMessageHistory(1) end, consolePanel) + g_keyboard.bindKeyPress('Shift+Down', function() navigateMessageHistory(-1) end, consolePanel) + g_keyboard.bindKeyPress('Tab', function() consoleTabBar:selectNextTab() end, consolePanel) + g_keyboard.bindKeyPress('Shift+Tab', function() consoleTabBar:selectPrevTab() end, consolePanel) + g_keyboard.bindKeyDown('Enter', sendCurrentMessage, consolePanel) + g_keyboard.bindKeyPress('Ctrl+A', function() consoleTextEdit:clearText() end, consolePanel) + + -- apply buttom functions after loaded + consoleTabBar:setNavigation(consolePanel:getChildById('prevChannelButton'), consolePanel:getChildById('nextChannelButton')) + consoleTabBar.onTabChange = onTabChange + + -- tibia like hotkeys + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown('Ctrl+O', g_game.requestChannels, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+E', removeCurrentTab, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+H', openHelp, gameRootPanel) + + consoleToggleChat = consolePanel:getChildById('toggleChat') + load() + + if g_game.isOnline() then + online() + end +end + +function clearSelection(consoleBuffer) + for _,label in pairs(consoleBuffer:getChildren()) do + label:clearSelection() + end + consoleBuffer.selectionText = nil + consoleBuffer.selection = nil +end + +function selectAll(consoleBuffer) + clearSelection(consoleBuffer) + if consoleBuffer:getChildCount() > 0 then + local text = {} + for _,label in pairs(consoleBuffer:getChildren()) do + label:selectAll() + table.insert(text, label:getSelection()) + end + consoleBuffer.selectionText = table.concat(text, '\n') + consoleBuffer.selection = { first = consoleBuffer:getChildIndex(consoleBuffer:getFirstChild()), last = consoleBuffer:getChildIndex(consoleBuffer:getLastChild()) } + end +end + +function toggleChat() + if consoleToggleChat:isChecked() then + disableChat() + else + enableChat() + end +end + +function enableChat(temporarily) + if g_app.isMobile() then return end + if consoleToggleChat:isChecked() then + return consoleToggleChat:setChecked(false) + end + if not temporarily then + modules.client_options.setOption("wsadWalking", false) + end + + consoleTextEdit:setVisible(true) + consoleTextEdit:setText("") + consoleTextEdit:focus() + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown("Enter", gameRootPanel) + + if temporarily then + local quickFunc = function() + if not g_game.isOnline() then return end + g_keyboard.unbindKeyDown("Enter", gameRootPanel) + g_keyboard.unbindKeyDown("Escape", gameRootPanel) + disableChat(temporarily) + end + g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel) + g_keyboard.bindKeyDown("Escape", quickFunc, gameRootPanel) + end + + modules.game_walking.disableWSAD() + + consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW")) +end + +function disableChat(temporarily) + if g_app.isMobile() then return end + if not consoleToggleChat:isChecked() then + return consoleToggleChat:setChecked(true) + end + if not temporarily then + modules.client_options.setOption("wsadWalking", true) + end + + consoleTextEdit:setVisible(false) + consoleTextEdit:setText("") + + local quickFunc = function() + if not g_game.isOnline() then return end + if consoleToggleChat:isChecked() then + consoleToggleChat:setChecked(false) + end + enableChat(true) + end + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown("Enter", quickFunc, gameRootPanel) + + modules.game_walking.enableWSAD() + + consoleToggleChat:setTooltip(tr("Enable chat mode")) +end + +function isChatEnabled() + return consoleTextEdit:isVisible() +end + +function terminate() + save() + disconnect(g_game, { + onTalk = onTalk, + onChannelList = onChannelList, + onOpenChannel = onOpenChannel, + onOpenPrivateChannel = onOpenPrivateChannel, + onOpenOwnPrivateChannel = onOpenPrivateChannel, + onCloseChannel = onCloseChannel, + onRuleViolationChannel = onRuleViolationChannel, + onRuleViolationRemove = onRuleViolationRemove, + onRuleViolationCancel = onRuleViolationCancel, + onRuleViolationLock = onRuleViolationLock, + onGameStart = online, + onGameEnd = offline, + onChannelEvent = onChannelEvent, + }) + + if g_game.isOnline() then clear() end + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown('Ctrl+O', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+E', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+H', gameRootPanel) + + saveCommunicationSettings() + + if channelsWindow then + channelsWindow:destroy() + end + + if communicationWindow then + communicationWindow:destroy() + end + + if violationWindow then + violationWindow:destroy() + end + + consoleTabBar = nil + consoleContentPanel = nil + consoleToggleChat = nil + consoleTextEdit = nil + + consolePanel:destroy() + consolePanel = nil + ownPrivateName = nil + + Console = nil +end + +function save() + local settings = {} + settings.messageHistory = messageHistory + g_settings.setNode('game_console', settings) +end + +function load() + local settings = g_settings.getNode('game_console') + if settings then + messageHistory = settings.messageHistory or {} + end + loadCommunicationSettings() +end + +function onTabChange(tabBar, tab) + if tab == defaultTab or tab == serverTab then + consolePanel:getChildById('closeChannelButton'):disable() + else + consolePanel:getChildById('closeChannelButton'):enable() + end +end + +function clear() + -- save last open channels + local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') or {} + local char = g_game.getCharacterName() + local savedChannels = {} + local set = false + for channelId, channelName in pairs(channels) do + if type(channelId) == 'number' then + savedChannels[channelName] = channelId + set = true + end + end + if set then + lastChannelsOpen[char] = savedChannels + else + lastChannelsOpen[char] = nil + end + g_settings.setNode('lastChannelsOpen', lastChannelsOpen) + + -- close channels + for _, channelName in pairs(channels) do + local tab = consoleTabBar:getTab(channelName) + consoleTabBar:removeTab(tab) + end + channels = {} + + consoleTabBar:removeTab(defaultTab) + defaultTab = nil + consoleTabBar:removeTab(serverTab) + serverTab = nil + + local npcTab = consoleTabBar:getTab('NPCs') + if npcTab then + consoleTabBar:removeTab(npcTab) + npcTab = nil + end + + if violationReportTab then + consoleTabBar:removeTab(violationReportTab) + violationReportTab = nil + end + + consoleTextEdit:clearText() + + if violationWindow then + violationWindow:destroy() + violationWindow = nil + end + + if channelsWindow then + channelsWindow:destroy() + channelsWindow = nil + end +end + +function switchMode(newView) + if newView then + consolePanel:setImageColor('#ffffff88') + else + consolePanel:setImageColor('white') + end + --consolePanel:setDraggable(floating) + --consoleTabBar:setDraggable(floating) + --floatingMode = floating +end + +function onDragEnter(widget, pos) + return floatingMode +end + +function onDragMove(widget, pos, moved) + if not floatingMode then + return + end + -- update margin + return true +end + +function onDragLeave(widget, pos) + return floatingMode +end + +function clearChannel(consoleTabBar) + consoleTabBar:getCurrentTab().tabPanel:getChildById('consoleBuffer'):destroyChildren() +end + +function setTextEditText(text) + consoleTextEdit:setText(text) + consoleTextEdit:setCursorPos(-1) +end + +function openHelp() + local helpChannel = 9 + if g_game.getClientVersion() <= 810 then + helpChannel = 8 + end + g_game.joinChannel(helpChannel) +end + +function openPlayerReportRuleViolationWindow() + if violationWindow or violationReportTab then return end + violationWindow = g_ui.loadUI('violationwindow', rootWidget) + violationWindow.onEscape = function() + violationWindow:destroy() + violationWindow = nil + end + violationWindow.onEnter = function() + local text = violationWindow:getChildById('text'):getText() + g_game.talkChannel(MessageModes.RVRChannel, 0, text) + violationReportTab = addTab(tr('Report Rule') .. '...', true) + addTabText(tr('Please wait patiently for a gamemaster to reply') .. '.', SpeakTypesSettings.privateRed, violationReportTab) + addTabText(applyMessagePrefixies(g_game.getCharacterName(), 0, text), SpeakTypesSettings.say, violationReportTab, g_game.getCharacterName()) + violationReportTab.locked = true + violationWindow:destroy() + violationWindow = nil + end +end + +function addTab(name, focus) + local tab = getTab(name) + if tab then -- is channel already open + if not focus then focus = true end + else + tab = consoleTabBar:addTab(name, nil, processChannelTabMenu) + end + if focus then + consoleTabBar:selectTab(tab) + end + return tab +end + +function removeTab(tab) + if type(tab) == 'string' then + tab = consoleTabBar:getTab(tab) + end + + if tab == defaultTab or tab == serverTab then + return + end + + if tab == violationReportTab then + g_game.cancelRuleViolation() + violationReportTab = nil + elseif tab.violationChatName then + g_game.closeRuleViolation(tab.violationChatName) + elseif tab.channelId then + -- notificate the server that we are leaving the channel + for k, v in pairs(channels) do + if (k == tab.channelId) then channels[k] = nil end + end + g_game.leaveChannel(tab.channelId) + elseif tab:getText() == "NPCs" then + g_game.closeNpcChannel() + end + + if getCurrentTab() == tab then + consoleTabBar:selectTab(defaultTab) + end + + consoleTabBar:removeTab(tab) +end + +function removeCurrentTab() + removeTab(consoleTabBar:getCurrentTab()) +end + +function getTab(name) + return consoleTabBar:getTab(name) +end + +function getChannelTab(channelId) + local channel = channels[channelId] + if channel then + return getTab(channel) + end + return nil +end + +function getRuleViolationsTab() + if violationsChannelId then + return getChannelTab(violationsChannelId) + end + return nil +end + +function getCurrentTab() + return consoleTabBar:getCurrentTab() +end + +function addChannel(name, id) + channels[id] = name + local focus = not table.find(ignoredChannels, id) + local tab = addTab(name, focus) + tab.channelId = id + return tab +end + +function addPrivateChannel(receiver) + channels[receiver] = receiver + return addTab(receiver, true) +end + +function addPrivateText(text, speaktype, name, isPrivateCommand, creatureName) + local focus = false + if speaktype.npcChat then + name = 'NPCs' + focus = true + end + + local privateTab = getTab(name) + if privateTab == nil then + if (modules.client_options.getOption('showPrivateMessagesInConsole') and not focus) or (isPrivateCommand and not privateTab) then + privateTab = defaultTab + else + privateTab = addTab(name, focus) + channels[name] = name + end + privateTab.npcChat = speaktype.npcChat + elseif focus then + consoleTabBar:selectTab(privateTab) + end + addTabText(text, speaktype, privateTab, creatureName) +end + +function addText(text, speaktype, tabName, creatureName) + local tab = getTab(tabName) + if tab ~= nil then + addTabText(text, speaktype, tab, creatureName) + end +end + +-- Contains letter width for font "verdana-11px-antialised" as console is based on it +local letterWidth = { -- New line (10) and Space (32) have width 1 because they are printed and not replaced with spacer + [10] = 1, [32] = 1, [33] = 3, [34] = 6, [35] = 8, [36] = 7, [37] = 13, [38] = 9, [39] = 3, [40] = 5, [41] = 5, [42] = 6, [43] = 8, [44] = 4, [45] = 5, [46] = 3, [47] = 8, + [48] = 7, [49] = 6, [50] = 7, [51] = 7, [52] = 7, [53] = 7, [54] = 7, [55] = 7, [56] = 7, [57] = 7, [58] = 3, [59] = 4, [60] = 8, [61] = 8, [62] = 8, [63] = 6, + [64] = 10, [65] = 9, [66] = 7, [67] = 7, [68] = 8, [69] = 7, [70] = 7, [71] = 8, [72] = 8, [73] = 5, [74] = 5, [75] = 7, [76] = 7, [77] = 9, [78] = 8, [79] = 8, + [80] = 7, [81] = 8, [82] = 8, [83] = 7, [84] = 8, [85] = 8, [86] = 8, [87] = 12, [88] = 8, [89] = 8, [90] = 7, [91] = 5, [92] = 8, [93] = 5, [94] = 9, [95] = 8, + [96] = 5, [97] = 7, [98] = 7, [99] = 6, [100] = 7, [101] = 7, [102] = 5, [103] = 7, [104] = 7, [105] = 3, [106] = 4, [107] = 7, [108] = 3, [109] = 11, [110] = 7, + [111] = 7, [112] = 7, [113] = 7, [114] = 6, [115] = 6, [116] = 5, [117] = 7, [118] = 8, [119] = 10, [120] = 8, [121] = 8, [122] = 6, [123] = 7, [124] = 4, [125] = 7, [126] = 8, + [127] = 1, [128] = 7, [129] = 6, [130] = 3, [131] = 7, [132] = 6, [133] = 11, [134] = 7, [135] = 7, [136] = 7, [137] = 13, [138] = 7, [139] = 4, [140] = 11, [141] = 6, [142] = 6, + [143] = 6, [144] = 6, [145] = 4, [146] = 3, [147] = 7, [148] = 6, [149] = 6, [150] = 7, [151] = 10, [152] = 7, [153] = 10, [154] = 6, [155] = 5, [156] = 11, [157] = 6, [158] = 6, + [159] = 8, [160] = 4, [161] = 3, [162] = 7, [163] = 7, [164] = 7, [165] = 8, [166] = 4, [167] = 7, [168] = 6, [169] = 10, [170] = 6, [171] = 8, [172] = 8, [173] = 16, [174] = 10, + [175] = 8, [176] = 5, [177] = 8, [178] = 5, [179] = 5, [180] = 6, [181] = 7, [182] = 7, [183] = 3, [184] = 5, [185] = 6, [186] = 6, [187] = 8, [188] = 12, [189] = 12, [190] = 12, + [191] = 6, [192] = 9, [193] = 9, [194] = 9, [195] = 9, [196] = 9, [197] = 9, [198] = 11, [199] = 7, [200] = 7, [201] = 7, [202] = 7, [203] = 7, [204] = 5, [205] = 5, [206] = 6, + [207] = 5, [208] = 8, [209] = 8, [210] = 8, [211] = 8, [212] = 8, [213] = 8, [214] = 8, [215] = 8, [216] = 8, [217] = 8, [218] = 8, [219] = 8, [220] = 8, [221] = 8, [222] = 7, + [223] = 7, [224] = 7, [225] = 7, [226] = 7, [227] = 7, [228] = 7, [229] = 7, [230] = 11, [231] = 6, [232] = 7, [233] = 7, [234] = 7, [235] = 7, [236] = 3, [237] = 4, [238] = 4, + [239] = 4, [240] = 7, [241] = 7, [242] = 7, [243] = 7, [244] = 7, [245] = 7, [246] = 7, [247] = 9, [248] = 7, [249] = 7, [250] = 7, [251] = 7, [252] = 7, [253] = 8, [254] = 7, [255] = 8 +} + +-- Return information about start, end in the string and the highlighted words +function getHighlightedText(text) + local tmpData = {} + + repeat + local tmp = {string.find(text, "{([^}]+)}", tmpData[#tmpData-1])} + for _, v in pairs(tmp) do + table.insert(tmpData, v) + end + until not(string.find(text, "{([^}]+)}", tmpData[#tmpData-1])) + + return tmpData +end + +function getNewHighlightedText(text, color, highlightColor) + local tmpData = {} + + for i, part in ipairs(text:split("{")) do + if i == 1 then + table.insert(tmpData, part) + table.insert(tmpData, color) + else + for j, part2 in ipairs(part:split("}")) do + if j == 1 then + table.insert(tmpData, part2) + table.insert(tmpData, highlightColor) + else + table.insert(tmpData, part2) + table.insert(tmpData, color) + end + end + end + end + + return tmpData +end + +function addTabText(text, speaktype, tab, creatureName) + if not tab or tab.locked or not text or #text == 0 then return end + + if modules.client_options.getOption('showTimestampsInConsole') then + text = os.date('%H:%M') .. ' ' .. text + end + + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + + local label = nil + if consoleBuffer:getChildCount() > MAX_LINES then + label = consoleBuffer:getFirstChild() + consoleBuffer:moveChildToIndex(label, consoleBuffer:getChildCount()) + end + + if not label then + label = g_ui.createWidget('ConsoleLabel', consoleBuffer) + end + label:setId('consoleLabel' .. consoleBuffer:getChildCount()) + label:setText(text) + label:setColor(speaktype.color) + consoleTabBar:blinkTab(tab) + + if speaktype.npcChat and (g_game.getCharacterName() ~= creatureName or g_game.getCharacterName() == 'Account Manager') then + local highlightData = getNewHighlightedText(text, speaktype.color, "#1f9ffe") + if #highlightData > 2 then + label:setColoredText(highlightData) + end + end + + label.name = creatureName + consoleBuffer.onMouseRelease = function(self, mousePos, mouseButton) + processMessageMenu(mousePos, mouseButton, nil, nil, nil, tab) + end + label.onMouseRelease = function(self, mousePos, mouseButton) + processMessageMenu(mousePos, mouseButton, creatureName, text, self, tab) + end + label.onMousePress = function(self, mousePos, button) + if button == MouseLeftButton then clearSelection(consoleBuffer) end + end + label.onDragEnter = function(self, mousePos) + clearSelection(consoleBuffer) + return true + end + label.onDragLeave = function(self, droppedWidget, mousePos) + local text = {} + for selectionChild = consoleBuffer.selection.first, consoleBuffer.selection.last do + local label = self:getParent():getChildByIndex(selectionChild) + table.insert(text, label:getSelection()) + end + consoleBuffer.selectionText = table.concat(text, '\n') + return true + end + label.onDragMove = function(self, mousePos, mouseMoved) + local parent = self:getParent() + local parentRect = parent:getPaddingRect() + local selfIndex = parent:getChildIndex(self) + local child = parent:getChildByPos(mousePos) + + -- find bonding children + if not child then + if mousePos.y < self:getY() then + for index = selfIndex - 1, 1, -1 do + local label = parent:getChildByIndex(index) + if label:getY() + label:getHeight() > parentRect.y then + if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == 1 then + child = label + break + end + else + child = parent:getChildByIndex(index + 1) + break + end + end + elseif mousePos.y > self:getY() + self:getHeight() then + for index = selfIndex + 1, parent:getChildCount(), 1 do + local label = parent:getChildByIndex(index) + if label:getY() < parentRect.y + parentRect.height then + if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == parent:getChildCount() then + child = label + break + end + else + child = parent:getChildByIndex(index - 1) + break + end + end + else + child = self + end + end + + if not child then return false end + + local childIndex = parent:getChildIndex(child) + + -- remove old selection + clearSelection(consoleBuffer) + + -- update self selection + local textBegin = self:getTextPos(self:getLastClickPosition()) + local textPos = self:getTextPos(mousePos) + self:setSelection(textBegin, textPos) + + consoleBuffer.selection = { first = math.min(selfIndex, childIndex), last = math.max(selfIndex, childIndex) } + + -- update siblings selection + if child ~= self then + for selectionChild = consoleBuffer.selection.first + 1, consoleBuffer.selection.last - 1 do + parent:getChildByIndex(selectionChild):selectAll() + end + + local textPos = child:getTextPos(mousePos) + if childIndex > selfIndex then + child:setSelection(0, textPos) + else + child:setSelection(string.len(child:getText()), textPos) + end + end + + return true + end +end + +function removeTabLabelByName(tab, name) + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + for _,label in pairs(consoleBuffer:getChildren()) do + if label.name == name then + label:destroy() + end + end +end + +function processChannelTabMenu(tab, mousePos, mouseButton) + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + + local worldName = g_game.getWorldName() + local characterName = g_game.getCharacterName() + channelName = tab:getText() + if tab ~= defaultTab and tab ~= serverTab then + menu:addOption(tr('Close'), function() removeTab(channelName) end) + --menu:addOption(tr('Show Server Messages'), function() --[[TODO]] end) + menu:addSeparator() + end + + if consoleTabBar:getCurrentTab() == tab then + menu:addOption(tr('Clear Messages'), function() clearChannel(consoleTabBar) end) + menu:addOption(tr('Save Messages'), function() + local panel = consoleTabBar:getTabPanel(tab) + local consoleBuffer = panel:getChildById('consoleBuffer') + local lines = {} + for _,label in pairs(consoleBuffer:getChildren()) do + table.insert(lines, label:getText()) + end + + local filename = worldName .. ' - ' .. characterName .. ' - ' .. channelName .. '.txt' + local filepath = '/user_dir/' .. filename + + -- extra information at the beginning + table.insert(lines, 1, os.date('\nChannel saved at %a %b %d %H:%M:%S %Y')) + + if g_resources.fileExists(filepath) then + table.insert(lines, 1, protectedcall(g_resources.readFileContents, filepath) or '') + end + + g_resources.writeFileContents(filepath, table.concat(lines, '\n')) + modules.game_textmessage.displayStatusMessage(tr('Channel appended to %s', filename)) + end) + end + + menu:display(mousePos) +end + +function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab) + if mouseButton == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + if creatureName and #creatureName > 0 then + if creatureName ~= g_game.getCharacterName() then + menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end) + if not g_game.getLocalPlayer():hasVip(creatureName) then + menu:addOption(tr('Add to VIP list'), function () g_game.addVip(creatureName) end) + end + if modules.game_console.getOwnPrivateTab() then + menu:addSeparator() + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(creatureName) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(creatureName) end) + end + if isIgnored(creatureName) then + menu:addOption(tr('Unignore') .. ' ' .. creatureName, function() removeIgnoredPlayer(creatureName) end) + else + menu:addOption(tr('Ignore') .. ' ' .. creatureName, function() addIgnoredPlayer(creatureName) end) + end + menu:addSeparator() + end + if modules.game_ruleviolation.hasWindowAccess() then + menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureName, text:match('.+%:%s(.+)')) end) + menu:addSeparator() + end + + menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end) + end + local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText + if selection and #selection > 0 then + menu:addOption(tr('Copy'), function() g_window.setClipboardText(selection) end, '(Ctrl+C)') + end + if text then + menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end) + end + menu:addOption(tr('Select all'), function() selectAll(tab.tabPanel:getChildById('consoleBuffer')) end) + if tab.violations and creatureName then + menu:addSeparator() + menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end) + menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end) + end + menu:display(mousePos) + end +end + +function sendCurrentMessage() + local message = consoleTextEdit:getText() + if #message == 0 then return end + if not isChatEnabled() then return end + consoleTextEdit:clearText() + + -- send message + sendMessage(message) +end + +function addFilter(filter) + table.insert(filters, filter) +end + +function removeFilter(filter) + table.removevalue(filters, filter) +end + +function sendMessage(message, tab) + local tab = tab or getCurrentTab() + if not tab then return end + + for k,func in pairs(filters) do + if func(message) then + return true + end + end + + -- when talking on server log, the message goes to default channel + local name = tab:getText() + if tab == serverTab or tab == getRuleViolationsTab() then + tab = defaultTab + name = defaultTab:getText() + end + + -- handling chat commands + local channel = tab.channelId + local originalMessage = message + local chatCommandSayMode + local chatCommandPrivate + local chatCommandPrivateReady + local chatCommandMessage + + -- player used yell command + chatCommandMessage = message:match("^%#[y|Y] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'yell' + channel = 0 + message = chatCommandMessage + end + + -- player used whisper + chatCommandMessage = message:match("^%#[w|W] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'whisper' + message = chatCommandMessage + channel = 0 + end + + -- player say + chatCommandMessage = message:match("^%#[s|S] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'say' + message = chatCommandMessage + channel = 0 + end + + -- player red talk on channel + chatCommandMessage = message:match("^%#[c|C] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'channelRed' + message = chatCommandMessage + end + + -- player broadcast + chatCommandMessage = message:match("^%#[b|B] (.*)") + if chatCommandMessage ~= nil then + chatCommandSayMode = 'broadcast' + message = chatCommandMessage + channel = 0 + end + + local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)") + if findIni ~= nil and findIni == 1 then -- player used private chat command + if chatCommandInitial == chatCommandEnd then + chatCommandPrivateRepeat = false + if chatCommandInitial == "*" then + setTextEditText('*'.. chatCommandPrivate .. '* ') + end + message = chatCommandMessage:trim() + chatCommandPrivateReady = true + end + end + + message = message:gsub("^(%s*)(.*)","%2") -- remove space characters from message init + if #message == 0 then return end + + -- add new command to history + currentMessageIndex = 0 + if #messageHistory == 0 or messageHistory[#messageHistory] ~= originalMessage then + table.insert(messageHistory, originalMessage) + if #messageHistory > MAX_HISTORY then + table.remove(messageHistory, 1) + end + end + + local speaktypedesc + if (channel or tab == defaultTab) and not chatCommandPrivateReady then + if tab == defaultTab then + speaktypedesc = chatCommandSayMode or SayModes[consolePanel:getChildById('sayModeButton').sayMode].speakTypeDesc + if speaktypedesc ~= 'say' then sayModeChange(2) end -- head back to say mode + else + speaktypedesc = chatCommandSayMode or 'channelYellow' + end + + g_game.talkChannel(SpeakTypesSettings[speaktypedesc].speakType, channel, message) + return + else + local isPrivateCommand = false + local priv = true + local tabname = name + local dontAdd = false + if chatCommandPrivateReady then + speaktypedesc = 'privatePlayerToPlayer' + name = chatCommandPrivate + isPrivateCommand = true + elseif tab.npcChat then + speaktypedesc = 'privatePlayerToNpc' + elseif tab == violationReportTab then + if violationReportTab.locked then + modules.game_textmessage.displayFailureMessage('Wait for a gamemaster reply.') + dontAdd = true + else + speaktypedesc = 'rvrContinue' + tabname = tr('Report Rule') .. '...' + end + elseif tab.violationChatName then + speaktypedesc = 'rvrAnswerTo' + name = tab.violationChatName + tabname = tab.violationChatName .. '\'...' + else + speaktypedesc = 'privatePlayerToPlayer' + end + + + local speaktype = SpeakTypesSettings[speaktypedesc] + local player = g_game.getLocalPlayer() + g_game.talkPrivate(speaktype.speakType, name, message) + if not dontAdd then + message = applyMessagePrefixies(g_game.getCharacterName(), player:getLevel(), message) + addPrivateText(message, speaktype, tabname, isPrivateCommand, g_game.getCharacterName()) + end + end +end + +function sayModeChange(sayMode) + local buttom = consolePanel:getChildById('sayModeButton') + if sayMode == nil then + sayMode = buttom.sayMode + 1 + end + + if sayMode > #SayModes then sayMode = 1 end + + buttom:setIcon(SayModes[sayMode].icon) + buttom.sayMode = sayMode +end + +function getOwnPrivateTab() + if not ownPrivateName then return end + return getTab(ownPrivateName) +end + +function setIgnoreNpcMessages(ignore) + ignoreNpcMessages = ignore +end + +function navigateMessageHistory(step) + if not isChatEnabled() then + return + end + + local numCommands = #messageHistory + if numCommands > 0 then + currentMessageIndex = math.min(math.max(currentMessageIndex + step, 0), numCommands) + if currentMessageIndex > 0 then + local command = messageHistory[numCommands - currentMessageIndex + 1] + setTextEditText(command) + else + consoleTextEdit:clearText() + end + end + local player = g_game.getLocalPlayer() + if player then + player:lockWalk(200) -- lock walk for 200 ms to avoid walk during release of shift + end +end + +function applyMessagePrefixies(name, level, message) + if name and #name > 0 then + if modules.client_options.getOption('showLevelsInConsole') and level > 0 then + message = name .. ' [' .. level .. ']: ' .. message + else + message = name .. ': ' .. message + end + end + return message +end + +function onTalk(name, level, mode, message, channelId, creaturePos) + if mode == MessageModes.GamemasterBroadcast then + modules.game_textmessage.displayBroadcastMessage(name .. ': ' .. message) + return + end + + local isNpcMode = (mode == MessageModes.NpcFromStartBlock or mode == MessageModes.NpcFrom) + + if ignoreNpcMessages and isNpcMode then return end + + speaktype = SpeakTypes[mode] + + if not speaktype then + perror('unhandled onTalk message mode ' .. mode .. ': ' .. message) + return + end + + local localPlayer = g_game.getLocalPlayer() + if name ~= g_game.getCharacterName() + and isUsingIgnoreList() + and not(isUsingWhiteList()) or (isUsingWhiteList() and not(isWhitelisted(name)) and not(isAllowingVIPs() and localPlayer:hasVip(name))) then + + if mode == MessageModes.Yell and isIgnoringYelling() then + return + elseif speaktype.private and isIgnoringPrivate() and not isNpcMode then + return + elseif isIgnored(name) then + return + end + end + + if mode == MessageModes.RVRChannel then + channelId = violationsChannelId + end + + if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or + mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or + mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud or + mode == MessageModes.NpcFromStartBlock) and creaturePos then + local staticText = StaticText.create() + -- Remove curly braces from screen message + local staticMessage = message + if isNpcMode then + local highlightData = getNewHighlightedText(staticMessage, speaktype.color, "#1f9ffe") + if #highlightData > 2 then + staticText:addColoredMessage(name, mode, highlightData) + else + staticText:addMessage(name, mode, staticMessage) + end + staticText:setColor(speaktype.color) + else + staticText:addMessage(name, mode, staticMessage) + end + g_map.addThing(staticText, creaturePos, -1) + end + + local defaultMessage = mode <= 3 and true or false + + if speaktype == SpeakTypesSettings.none then return end + + if speaktype.hideInConsole then return end + + local composedMessage = applyMessagePrefixies(name, level, message) + + if mode == MessageModes.RVRAnswer then + violationReportTab.locked = false + addTabText(composedMessage, speaktype, violationReportTab, name) + elseif mode == MessageModes.RVRContinue then + addText(composedMessage, speaktype, name .. '\'...', name) + elseif speaktype.private then + addPrivateText(composedMessage, speaktype, name, false, name) + if modules.client_options.getOption('showPrivateMessagesOnScreen') and speaktype ~= SpeakTypesSettings.privateNpcToPlayer then + modules.game_textmessage.displayPrivateMessage(name .. ':\n' .. message) + end + else + local channel = tr('Default') + if not defaultMessage then + channel = channels[channelId] + end + + if channel then + addText(composedMessage, speaktype, channel, name) + else + -- server sent a message on a channel that is not open + pwarning('message in channel id ' .. channelId .. ' which is unknown, this is a server bug, relogin if you want to see messages in this channel') + end + end +end + +function onOpenChannel(channelId, channelName) + addChannel(channelName, channelId) +end + +function onOpenPrivateChannel(receiver) + addPrivateChannel(receiver) +end + +function onOpenOwnPrivateChannel(channelId, channelName) + local privateTab = getTab(channelName) + if privateTab == nil then + addChannel(channelName, channelId) + end + ownPrivateName = channelName +end + +function onCloseChannel(channelId) + local channel = channels[channelId] + if channel then + local tab = getTab(channel) + if tab then + consoleTabBar:removeTab(tab) + end + for k, v in pairs(channels) do + if (k == tab.channelId) then channels[k] = nil end + end + end +end + +function processViolation(name, text) + local tabname = name .. '\'...' + local tab = addTab(tabname, true) + channels[tabname] = tabname + tab.violationChatName = name + g_game.openRuleViolation(name) + addTabText(text, SpeakTypesSettings.say, tab, name) +end + +function onRuleViolationChannel(channelId) + violationsChannelId = channelId + local tab = addChannel(tr('Rule Violations'), channelId) + tab.violations = true +end + +function onRuleViolationRemove(name) + local tab = getRuleViolationsTab() + if not tab then return end + removeTabLabelByName(tab, name) +end + +function onRuleViolationCancel(name) + local tab = getTab(name .. '\'...') + if not tab then return end + addTabText(tr('%s has finished the request', name) .. '.', SpeakTypesSettings.privateRed, tab) + tab.locked = true +end + +function onRuleViolationLock() + if not violationReportTab then return end + violationReportTab.locked = false + addTabText(tr('Your request has been closed') .. '.', SpeakTypesSettings.privateRed, violationReportTab) + violationReportTab.locked = true +end + +function doChannelListSubmit() + local channelListPanel = channelsWindow:getChildById('channelList') + local openPrivateChannelWith = channelsWindow:getChildById('openPrivateChannelWith'):getText() + if openPrivateChannelWith ~= '' then + if openPrivateChannelWith:lower() ~= g_game.getCharacterName():lower() then + g_game.openPrivateChannel(openPrivateChannelWith) + else + modules.game_textmessage.displayFailureMessage('You cannot create a private chat channel with yourself.') + end + else + local selectedChannelLabel = channelListPanel:getFocusedChild() + if not selectedChannelLabel then return end + if selectedChannelLabel.channelId == 0xFFFF then + g_game.openOwnChannel() + else + g_game.leaveChannel(selectedChannelLabel.channelId) + g_game.joinChannel(selectedChannelLabel.channelId) + end + end + + channelsWindow:destroy() +end + +function onChannelList(channelList) + if channelsWindow then channelsWindow:destroy() end + channelsWindow = g_ui.displayUI('channelswindow') + local channelListPanel = channelsWindow:getChildById('channelList') + channelsWindow.onEnter = doChannelListSubmit + channelsWindow.onDestroy = function() channelsWindow = nil end + g_keyboard.bindKeyPress('Down', function() channelListPanel:focusNextChild(KeyboardFocusReason) end, channelsWindow) + g_keyboard.bindKeyPress('Up', function() channelListPanel:focusPreviousChild(KeyboardFocusReason) end, channelsWindow) + + for k,v in pairs(channelList) do + local channelId = v[1] + local channelName = v[2] + + if #channelName > 0 then + local label = g_ui.createWidget('ChannelListLabel', channelListPanel) + label.channelId = channelId + label:setText(channelName) + + label:setPhantom(false) + label.onDoubleClick = doChannelListSubmit + end + end +end + +function loadCommunicationSettings() + communicationSettings.whitelistedPlayers = {} + communicationSettings.ignoredPlayers = {} + + local ignoreNode = g_settings.getNode('IgnorePlayers') + if ignoreNode then + for _, player in pairs(ignoreNode) do + table.insert(communicationSettings.ignoredPlayers, player) + end + end + + local whitelistNode = g_settings.getNode('WhitelistedPlayers') + if whitelistNode then + for _, player in pairs(whitelistNode) do + table.insert(communicationSettings.whitelistedPlayers, player) + end + end + + communicationSettings.useIgnoreList = g_settings.getBoolean('UseIgnoreList') + communicationSettings.useWhiteList = g_settings.getBoolean('UseWhiteList') + communicationSettings.privateMessages = g_settings.getBoolean('IgnorePrivateMessages') + communicationSettings.yelling = g_settings.getBoolean('IgnoreYelling') + communicationSettings.allowVIPs = g_settings.getBoolean('AllowVIPs') +end + +function saveCommunicationSettings() + local tmpIgnoreList = {} + local ignoredPlayers = getIgnoredPlayers() + for i = 1, #ignoredPlayers do + table.insert(tmpIgnoreList, ignoredPlayers[i]) + end + + local tmpWhiteList = {} + local whitelistedPlayers = getWhitelistedPlayers() + for i = 1, #whitelistedPlayers do + table.insert(tmpWhiteList, whitelistedPlayers[i]) + end + + g_settings.set('UseIgnoreList', communicationSettings.useIgnoreList) + g_settings.set('UseWhiteList', communicationSettings.useWhiteList) + g_settings.set('IgnorePrivateMessages', communicationSettings.privateMessages) + g_settings.set('IgnoreYelling', communicationSettings.yelling) + g_settings.setNode('IgnorePlayers', tmpIgnoreList) + g_settings.setNode('WhitelistedPlayers', tmpWhiteList) +end + +function getIgnoredPlayers() + return communicationSettings.ignoredPlayers +end + +function getWhitelistedPlayers() + return communicationSettings.whitelistedPlayers +end + +function isUsingIgnoreList() + return communicationSettings.useIgnoreList +end + +function isUsingWhiteList() + return communicationSettings.useWhiteList +end +function isIgnored(name) + return table.find(communicationSettings.ignoredPlayers, name, true) +end + +function addIgnoredPlayer(name) + if isIgnored(name) then return end + table.insert(communicationSettings.ignoredPlayers, name) + communicationSettings.useIgnoreList = true +end + +function removeIgnoredPlayer(name) + table.removevalue(communicationSettings.ignoredPlayers, name) +end + +function isWhitelisted(name) + return table.find(communicationSettings.whitelistedPlayers, name, true) +end + +function addWhitelistedPlayer(name) + if isWhitelisted(name) then return end + table.insert(communicationSettings.whitelistedPlayers, name) +end + +function removeWhitelistedPlayer(name) + table.removevalue(communicationSettings.whitelistedPlayers, name) +end + +function isIgnoringPrivate() + return communicationSettings.privateMessages +end + +function isIgnoringYelling() + return communicationSettings.yelling +end + +function isAllowingVIPs() + return communicationSettings.allowVIPs +end + +function onClickIgnoreButton() + if communicationWindow then return end + communicationWindow = g_ui.displayUI('communicationwindow') + local ignoreListPanel = communicationWindow:getChildById('ignoreList') + local whiteListPanel = communicationWindow:getChildById('whiteList') + communicationWindow.onDestroy = function() communicationWindow = nil end + + local useIgnoreListBox = communicationWindow:getChildById('checkboxUseIgnoreList') + useIgnoreListBox:setChecked(communicationSettings.useIgnoreList) + local useWhiteListBox = communicationWindow:getChildById('checkboxUseWhiteList') + useWhiteListBox:setChecked(communicationSettings.useWhiteList) + + local removeIgnoreButton = communicationWindow:getChildById('buttonIgnoreRemove') + removeIgnoreButton:disable() + ignoreListPanel.onChildFocusChange = function() removeIgnoreButton:enable() end + removeIgnoreButton.onClick = function() + local selection = ignoreListPanel:getFocusedChild() + if selection then + ignoreListPanel:removeChild(selection) + selection:destroy() + end + removeIgnoreButton:disable() + end + + local removeWhitelistButton = communicationWindow:getChildById('buttonWhitelistRemove') + removeWhitelistButton:disable() + whiteListPanel.onChildFocusChange = function() removeWhitelistButton:enable() end + removeWhitelistButton.onClick = function() + local selection = whiteListPanel:getFocusedChild() + if selection then + whiteListPanel:removeChild(selection) + selection:destroy() + end + removeWhitelistButton:disable() + end + + local newlyIgnoredPlayers = {} + local addIgnoreName = communicationWindow:getChildById('ignoreNameEdit') + local addIgnoreButton = communicationWindow:getChildById('buttonIgnoreAdd') + local addIgnoreFunction = function() + local newEntry = addIgnoreName:getText() + if newEntry == '' then return end + if table.find(getIgnoredPlayers(), newEntry) then return end + if table.find(newlyIgnoredPlayers, newEntry) then return end + local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) + label:setText(newEntry) + table.insert(newlyIgnoredPlayers, newEntry) + addIgnoreName:setText('') + end + addIgnoreButton.onClick = addIgnoreFunction + + local newlyWhitelistedPlayers = {} + local addWhitelistName = communicationWindow:getChildById('whitelistNameEdit') + local addWhitelistButton = communicationWindow:getChildById('buttonWhitelistAdd') + local addWhitelistFunction = function() + local newEntry = addWhitelistName:getText() + if newEntry == '' then return end + if table.find(getWhitelistedPlayers(), newEntry) then return end + if table.find(newlyWhitelistedPlayers, newEntry) then return end + local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) + label:setText(newEntry) + table.insert(newlyWhitelistedPlayers, newEntry) + addWhitelistName:setText('') + end + addWhitelistButton.onClick = addWhitelistFunction + + communicationWindow.onEnter = function() + if addWhitelistName:isFocused() then + addWhitelistFunction() + elseif addIgnoreName:isFocused() then + addIgnoreFunction() + end + end + + local ignorePrivateMessageBox = communicationWindow:getChildById('checkboxIgnorePrivateMessages') + ignorePrivateMessageBox:setChecked(communicationSettings.privateMessages) + local ignoreYellingBox = communicationWindow:getChildById('checkboxIgnoreYelling') + ignoreYellingBox:setChecked(communicationSettings.yelling) + local allowVIPsBox = communicationWindow:getChildById('checkboxAllowVIPs') + allowVIPsBox:setChecked(communicationSettings.allowVIPs) + + local saveButton = communicationWindow:recursiveGetChildById('buttonSave') + saveButton.onClick = function() + communicationSettings.ignoredPlayers = {} + for i = 1, ignoreListPanel:getChildCount() do + addIgnoredPlayer(ignoreListPanel:getChildByIndex(i):getText()) + end + + communicationSettings.whitelistedPlayers = {} + for i = 1, whiteListPanel:getChildCount() do + addWhitelistedPlayer(whiteListPanel:getChildByIndex(i):getText()) + end + + communicationSettings.useIgnoreList = useIgnoreListBox:isChecked() + communicationSettings.useWhiteList = useWhiteListBox:isChecked() + communicationSettings.yelling = ignoreYellingBox:isChecked() + communicationSettings.privateMessages = ignorePrivateMessageBox:isChecked() + communicationSettings.allowVIPs = allowVIPsBox:isChecked() + communicationWindow:destroy() + end + + local cancelButton = communicationWindow:recursiveGetChildById('buttonCancel') + cancelButton.onClick = function() + communicationWindow:destroy() + end + + local ignoredPlayers = getIgnoredPlayers() + for i = 1, #ignoredPlayers do + local label = g_ui.createWidget('IgnoreListLabel', ignoreListPanel) + label:setText(ignoredPlayers[i]) + end + + local whitelistedPlayers = getWhitelistedPlayers() + for i = 1, #whitelistedPlayers do + local label = g_ui.createWidget('WhiteListLabel', whiteListPanel) + label:setText(whitelistedPlayers[i]) + end +end + +function online() + defaultTab = addTab(tr('Default'), true) + serverTab = addTab(tr('Server Log'), false) + + + if g_game.getClientVersion() >= 820 then + local tab = addTab("NPCs", false) + tab.npcChat = true + end + + if g_game.getClientVersion() < 862 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown('Ctrl+R', openPlayerReportRuleViolationWindow, gameRootPanel) + end + -- open last channels + local lastChannelsOpen = g_settings.getNode('lastChannelsOpen') + if lastChannelsOpen then + local savedChannels = lastChannelsOpen[g_game.getCharacterName()] + if savedChannels then + for channelName, channelId in pairs(savedChannels) do + channelId = tonumber(channelId) + if channelId ~= -1 and channelId < 100 then + if not table.find(channels, channelId) then + g_game.joinChannel(channelId) + table.insert(ignoredChannels, channelId) + end + end + end + end + end + scheduleEvent(function() consoleTabBar:selectTab(defaultTab) end, 500) + scheduleEvent(function() ignoredChannels = {} end, 3000) +end + +function offline() + if g_game.getClientVersion() < 862 then + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown('Ctrl+R', gameRootPanel) + end + clear() +end + +function onChannelEvent(channelId, name, type) + local fmt = ChannelEventFormats[type] + if not fmt then + print(('Unknown channel event type (%d).'):format(type)) + return + end + + local channel = channels[channelId] + if channel then + local tab = getTab(channel) + if tab then + addTabText(fmt:format(name), SpeakTypesSettings.channelOrange, tab) + end + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otmod new file mode 100644 index 0000000..361e5d7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otmod @@ -0,0 +1,9 @@ +Module + name: game_console + description: Manage chat window + author: edubart, andrefaramir, baxnie, sn4ake, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ console ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otui new file mode 100644 index 0000000..6f800e8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/console.otui @@ -0,0 +1,3 @@ +ConsolePanel + id: consolePanel + phantom: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/violationwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/violationwindow.otui new file mode 100644 index 0000000..19c55ca --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_console/violationwindow.otui @@ -0,0 +1,40 @@ +MainWindow + id: ignoreWindow + !text: tr('Report Rule Violation') + size: 300 240 + + Label + !text: tr('Please state the rule violation in one clear sentence and wait for a reply from a gamemaster. Please note that your message will disappear if you close the channel.') + text-wrap: true + text-auto-resize: true + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + TextEdit + id: text + text-wrap: true + multiline: true + anchors.top: prev.bottom + anchors.bottom: next.top + anchors.left: parent.left + anchors.right: parent.right + margin: 8 0 + max-length: 255 + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: self:getParent():onEnter() + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():onEscape() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.lua new file mode 100644 index 0000000..aa2e6e0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.lua @@ -0,0 +1,191 @@ +local gameStart = 0 + +function init() + connect(Container, { onOpen = onContainerOpen, + onClose = onContainerClose, + onSizeChange = onContainerChangeSize, + onUpdateItem = onContainerUpdateItem }) + connect(g_game, { + onGameStart = markStart, + onGameEnd = clean + }) + + reloadContainers() +end + +function terminate() + disconnect(Container, { onOpen = onContainerOpen, + onClose = onContainerClose, + onSizeChange = onContainerChangeSize, + onUpdateItem = onContainerUpdateItem }) + disconnect(g_game, { + onGameStart = markStart, + onGameEnd = clean + }) +end + +function reloadContainers() + clean() + for _,container in pairs(g_game.getContainers()) do + onContainerOpen(container) + end +end + +function clean() + for containerid,container in pairs(g_game.getContainers()) do + destroy(container) + end +end + +function markStart() + gameStart = g_clock.millis() +end + +function destroy(container) + if container.window then + container.window:destroy() + container.window = nil + container.itemsPanel = nil + end +end + +function refreshContainerItems(container) + for slot=0,container:getCapacity()-1 do + local itemWidget = container.itemsPanel:getChildById('item' .. slot) + itemWidget:setItem(container:getItem(slot)) + end + + if container:hasPages() then + refreshContainerPages(container) + end +end + +function toggleContainerPages(containerWindow, hasPages) + if hasPages == containerWindow.pagePanel:isOn() then + return + end + containerWindow.pagePanel:setOn(hasPages) + if hasPages then + containerWindow.miniwindowScrollBar:setMarginTop(containerWindow.miniwindowScrollBar:getMarginTop() + containerWindow.pagePanel:getHeight()) + containerWindow.contentsPanel:setMarginTop(containerWindow.contentsPanel:getMarginTop() + containerWindow.pagePanel:getHeight()) + else + containerWindow.miniwindowScrollBar:setMarginTop(containerWindow.miniwindowScrollBar:getMarginTop() - containerWindow.pagePanel:getHeight()) + containerWindow.contentsPanel:setMarginTop(containerWindow.contentsPanel:getMarginTop() - containerWindow.pagePanel:getHeight()) + end +end + +function refreshContainerPages(container) + local currentPage = 1 + math.floor(container:getFirstIndex() / container:getCapacity()) + local pages = 1 + math.floor(math.max(0, (container:getSize() - 1)) / container:getCapacity()) + container.window:recursiveGetChildById('pageLabel'):setText(string.format('Page %i of %i', currentPage, pages)) + + local prevPageButton = container.window:recursiveGetChildById('prevPageButton') + if currentPage == 1 then + prevPageButton:setEnabled(false) + else + prevPageButton:setEnabled(true) + prevPageButton.onClick = function() g_game.seekInContainer(container:getId(), container:getFirstIndex() - container:getCapacity()) end + end + + local nextPageButton = container.window:recursiveGetChildById('nextPageButton') + if currentPage >= pages then + nextPageButton:setEnabled(false) + else + nextPageButton:setEnabled(true) + nextPageButton.onClick = function() g_game.seekInContainer(container:getId(), container:getFirstIndex() + container:getCapacity()) end + end +end + +function onContainerOpen(container, previousContainer) + local containerWindow + if previousContainer then + containerWindow = previousContainer.window + previousContainer.window = nil + previousContainer.itemsPanel = nil + else + containerWindow = g_ui.createWidget('ContainerWindow', modules.game_interface.getContainerPanel()) + end + + containerWindow:setId('container' .. container:getId()) + if gameStart + 1000 < g_clock.millis() then + containerWindow:clearSettings() + end + + local containerPanel = containerWindow:getChildById('contentsPanel') + local containerItemWidget = containerWindow:getChildById('containerItemWidget') + containerWindow.onClose = function() + g_game.close(container) + containerWindow:hide() + end + containerWindow.onDrop = function(container, widget, mousePos) + if containerPanel:getChildByPos(mousePos) then + return false + end + local child = containerPanel:getChildByIndex(-1) + if child then + child:onDrop(widget, mousePos, true) + end + end + + -- this disables scrollbar auto hiding + local scrollbar = containerWindow:getChildById('miniwindowScrollBar') + scrollbar:mergeStyle({ ['$!on'] = { }}) + + local upButton = containerWindow:getChildById('upButton') + upButton.onClick = function() + g_game.openParent(container) + end + upButton:setVisible(container:hasParent()) + + local name = container:getName() + name = name:sub(1,1):upper() .. name:sub(2) + containerWindow:setText(name) + + containerItemWidget:setItem(container:getContainerItem()) + + containerPanel:destroyChildren() + for slot=0,container:getCapacity()-1 do + local itemWidget = g_ui.createWidget('Item', containerPanel) + itemWidget:setId('item' .. slot) + itemWidget:setItem(container:getItem(slot)) + itemWidget:setMargin(0) + itemWidget.position = container:getSlotPosition(slot) + + if not container:isUnlocked() then + itemWidget:setBorderColor('red') + end + end + + container.window = containerWindow + container.itemsPanel = containerPanel + + toggleContainerPages(containerWindow, container:hasPages()) + refreshContainerPages(container) + + local layout = containerPanel:getLayout() + local cellSize = layout:getCellSize() + containerWindow:setContentMinimumHeight(cellSize.height) + containerWindow:setContentMaximumHeight(cellSize.height*layout:getNumLines()) + + if not previousContainer then + local filledLines = math.max(math.ceil(container:getItemsCount() / layout:getNumColumns()), 1) + containerWindow:setContentHeight(filledLines*cellSize.height) + end + + containerWindow:setup() +end + +function onContainerClose(container) + destroy(container) +end + +function onContainerChangeSize(container, size) + if not container.window then return end + refreshContainerItems(container) +end + +function onContainerUpdateItem(container, slot, item, oldItem) + if not container.window then return end + local itemWidget = container.itemsPanel:getChildById('item' .. slot) + itemWidget:setItem(item) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.otmod new file mode 100644 index 0000000..6e3d686 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_containers/containers.otmod @@ -0,0 +1,9 @@ +Module + name: game_containers + description: Manage containers + author: edubart, baxnie + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [containers] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.lua new file mode 100644 index 0000000..374d1af --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.lua @@ -0,0 +1,225 @@ +local ProgressCallback = { + update = 1, + finish = 2 +} + +cooldownWindow = nil +cooldownButton = nil +contentsPanel = nil +cooldownPanel = nil +lastPlayer = nil + +cooldown = {} +cooldowns = {} +groupCooldown = {} + +function init() + connect(g_game, { onGameStart = online, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown }) + + cooldownButton = modules.client_topmenu.addRightGameToggleButton('cooldownButton', + tr('Cooldowns'), '/images/topbuttons/cooldowns', toggle, false, 5) + cooldownButton:setOn(true) + cooldownButton:hide() + + cooldownWindow = g_ui.loadUI('cooldown', modules.game_interface.getRightPanel()) + cooldownWindow:disableResize() + cooldownWindow:setup() + + contentsPanel = cooldownWindow:getChildById('contentsPanel') + cooldownPanel = contentsPanel:getChildById('cooldownPanel') + + -- preload cooldown images + for k,v in pairs(SpelllistSettings) do + g_textures.preload(v.iconFile) + end + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onSpellGroupCooldown = onSpellGroupCooldown, + onSpellCooldown = onSpellCooldown }) + + for key, val in pairs(cooldowns) do + removeCooldown(key) + end + cooldowns = {} + + cooldownWindow:destroy() + cooldownButton:destroy() +end + +function loadIcon(iconId) + local spell, profile, spellName = Spells.getSpellByIcon(iconId) + if not spellName then return end + if not profile then return end + + clientIconId = Spells.getClientId(spellName) + if not clientIconId then return end + + local icon = cooldownPanel:getChildById(iconId) + if not icon then + icon = g_ui.createWidget('SpellIcon') + icon:setId(iconId) + end + + local spellSettings = SpelllistSettings[profile] + if spellSettings then + icon:setImageSource(spellSettings.iconFile) + icon:setImageClip(Spells.getImageClip(clientIconId, profile)) + else + icon = nil + end + return icon +end + +function onMiniWindowClose() + cooldownButton:setOn(false) +end + +function toggle() + if cooldownButton:isOn() then + cooldownWindow:close() + cooldownButton:setOn(false) + else + cooldownWindow:open() + cooldownButton:setOn(true) + end +end + +function online() + if g_game.getFeature(GameSpellList) then + cooldownButton:show() + else + cooldownButton:hide() + cooldownWindow:close() + end + + if not lastPlayer or lastPlayer ~= g_game.getCharacterName() then + refresh() + lastPlayer = g_game.getCharacterName() + end +end + +function refresh() + cooldownPanel:destroyChildren() +end + +function removeCooldown(progressRect) + removeEvent(progressRect.event) + if progressRect.icon then + progressRect.icon:destroy() + progressRect.icon = nil + end + cooldowns[progressRect] = nil + progressRect = nil +end + +function turnOffCooldown(progressRect) + removeEvent(progressRect.event) + if progressRect.icon then + progressRect.icon:setOn(false) + progressRect.icon = nil + end + + -- create particles + --[[local particle = g_ui.createWidget('GroupCooldownParticles', progressRect) + particle:fill('parent') + scheduleEvent(function() particle:destroy() end, 1000) -- hack until onEffectEnd]] + + cooldowns[progressRect] = nil + progressRect = nil +end + +function initCooldown(progressRect, updateCallback, finishCallback) + progressRect:setPercent(0) + + progressRect.callback = {} + progressRect.callback[ProgressCallback.update] = updateCallback + progressRect.callback[ProgressCallback.finish] = finishCallback + + updateCallback() +end + +function updateCooldown(progressRect, duration) + progressRect:setPercent(progressRect:getPercent() + 10000/duration) + + if progressRect:getPercent() < 100 then + removeEvent(progressRect.event) + + progressRect.event = scheduleEvent(function() + if not progressRect.callback then return end + progressRect.callback[ProgressCallback.update]() + end, 100) + else + progressRect.callback[ProgressCallback.finish]() + end +end + +function isGroupCooldownIconActive(groupId) + return groupCooldown[groupId] +end + +function isCooldownIconActive(iconId) + return cooldown[iconId] +end + +function onSpellCooldown(iconId, duration) + local icon = loadIcon(iconId) + if not icon then + return + end + icon:setParent(cooldownPanel) + + local progressRect = icon:getChildById(iconId) + if not progressRect then + progressRect = g_ui.createWidget('SpellProgressRect', icon) + progressRect:setId(iconId) + progressRect.icon = icon + progressRect:fill('parent') + else + progressRect:setPercent(0) + end + progressRect:setTooltip(spellName) + + local updateFunc = function() + updateCooldown(progressRect, duration) + end + local finishFunc = function() + removeCooldown(progressRect) + cooldown[iconId] = false + end + initCooldown(progressRect, updateFunc, finishFunc) + cooldown[iconId] = true + cooldowns[progressRect] = true +end + +function onSpellGroupCooldown(groupId, duration) + if not SpellGroups[groupId] then return end + + local icon = contentsPanel:getChildById('groupIcon' .. SpellGroups[groupId]) + local progressRect = contentsPanel:getChildById('progressRect' .. SpellGroups[groupId]) + if icon then + icon:setOn(true) + removeEvent(icon.event) + end + + progressRect.icon = icon + if progressRect then + removeEvent(progressRect.event) + local updateFunc = function() + updateCooldown(progressRect, duration) + end + local finishFunc = function() + turnOffCooldown(progressRect) + groupCooldown[groupId] = false + end + initCooldown(progressRect, updateFunc, finishFunc) + groupCooldown[groupId] = true + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otmod new file mode 100644 index 0000000..69cb33f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otmod @@ -0,0 +1,9 @@ +Module + name: game_cooldown + description: Spellcooldowns + author: OTClient team + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ cooldown ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otui new file mode 100644 index 0000000..31f3647 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_cooldown/cooldown.otui @@ -0,0 +1,101 @@ +SpellGroupIcon < UIWidget + size: 22 22 + image-size: 22 22 + image-source: /images/game/spells/cooldowns + focusable: false + margin-top: 3 + +SpellIcon < UIWidget + size: 24 24 + image-size: 24 24 + focusable: false + + $!first: + margin-left: 1 + +SpellProgressRect < UIProgressRect + background: #585858AA + percent: 100 + focusable: false + +GroupCooldownParticles < UIParticles + effect: groupcooldown-effect + +MiniWindow + id: cooldownWindow + !text: tr('Spell Cooldowns') + height: 82 + icon: /images/topbuttons/cooldowns + @onClose: modules.game_cooldown.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + SpellGroupIcon + id: groupIconAttack + image-clip: 0 0 20 20 + anchors.top: parent.top + anchors.left: parent.left + margin-left: 2 + $on: + image-clip: 0 20 20 20 + + SpellProgressRect + id: progressRectAttack + anchors.fill: groupIconAttack + !tooltip: tr('Attack') + + SpellGroupIcon + id: groupIconHealing + image-clip: 20 0 20 20 + anchors.top: parent.top + anchors.left: groupIconAttack.right + margin-left: 3 + $on: + image-clip: 20 20 20 20 + + SpellProgressRect + id: progressRectHealing + anchors.fill: groupIconHealing + !tooltip: tr('Healing') + + SpellGroupIcon + id: groupIconSupport + image-clip: 40 0 20 20 + anchors.top: parent.top + anchors.left: groupIconHealing.right + margin-left: 3 + $on: + image-clip: 40 20 20 20 + + SpellProgressRect + id: progressRectSupport + anchors.fill: groupIconSupport + !tooltip: tr('Support') + + SpellGroupIcon + id: groupIconSpecial + image-clip: 60 0 20 20 + anchors.top: parent.top + anchors.left: groupIconSupport.right + margin-left: 3 + $on: + image-clip: 60 20 20 20 + + SpellProgressRect + id: progressRectSpecial + anchors.fill: groupIconSpecial + !tooltip: tr('Special') + + Panel + id: cooldownPanel + layout: + type: horizontalBox + height: 30 + margin-top: 3 + padding: 3 + anchors.top: groupIconSpecial.bottom + anchors.left: parent.left + anchors.right: parent.right + background-color: #00000022 + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.lua new file mode 100644 index 0000000..e414ffb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.lua @@ -0,0 +1,202 @@ +function init() + connect(g_game, { onClientVersionChange = updateFeatures }) +end + +function terminate() + disconnect(g_game, { onClientVersionChange = updateFeatures }) +end + +function updateFeatures(version) + g_game.resetFeatures() + if version <= 0 then + return + end + + -- you can add custom features here, list of them is in the modules\gamelib\const.lua + --g_game.enableFeature(GameExtendedOpcode) + --g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor + --g_game.enableFeature(GameSpritesAlphaChannel) + + g_game.enableFeature(GameNoDebug) + g_game.enableFeature(GameBotProtection) + + if(version >= 770) then + g_game.enableFeature(GameLooktypeU16) + g_game.enableFeature(GameMessageStatements) + g_game.enableFeature(GameLoginPacketEncryption) + end + + if(version >= 780) then + g_game.enableFeature(GamePlayerAddons) + g_game.enableFeature(GamePlayerStamina) + g_game.enableFeature(GameNewFluids) + g_game.enableFeature(GameMessageLevel) + g_game.enableFeature(GamePlayerStateU16) + g_game.enableFeature(GameNewOutfitProtocol) + end + + if(version >= 790) then + g_game.enableFeature(GameWritableDate) + end + + if(version >= 840) then + g_game.enableFeature(GameProtocolChecksum) + g_game.enableFeature(GameAccountNames) + g_game.enableFeature(GameDoubleFreeCapacity) + end + + if(version >= 841) then + g_game.enableFeature(GameChallengeOnLogin) + g_game.enableFeature(GameMessageSizeCheck) + g_game.enableFeature(GameTileAddThingWithStackpos) + end + + if(version >= 854) then + g_game.enableFeature(GameCreatureEmblems) + end + + if(version >= 860) then + g_game.enableFeature(GameAttackSeq) + end + + if(version >= 862) then + g_game.enableFeature(GamePenalityOnDeath) + end + + if(version >= 870) then + g_game.enableFeature(GameDoubleExperience) + g_game.enableFeature(GamePlayerMounts) + g_game.enableFeature(GameSpellList) + end + + if(version >= 910) then + g_game.enableFeature(GameNameOnNpcTrade) + g_game.enableFeature(GameTotalCapacity) + g_game.enableFeature(GameSkillsBase) + g_game.enableFeature(GamePlayerRegenerationTime) + g_game.enableFeature(GameChannelPlayerList) + g_game.enableFeature(GameEnvironmentEffect) + g_game.enableFeature(GameItemAnimationPhase) + end + + if(version >= 940) then + g_game.enableFeature(GamePlayerMarket) + end + + if(version >= 953) then + g_game.enableFeature(GamePurseSlot) + g_game.enableFeature(GameClientPing) + end + + if(version >= 960) then + g_game.enableFeature(GameSpritesU32) + g_game.enableFeature(GameOfflineTrainingTime) + end + + if(version >= 963) then + g_game.enableFeature(GameAdditionalVipInfo) + end + + if(version >= 972) then + g_game.enableFeature(GameDoublePlayerGoodsMoney) + end + + if(version >= 980) then + g_game.enableFeature(GamePreviewState) + g_game.enableFeature(GameClientVersion) + end + + if(version >= 981) then + g_game.enableFeature(GameLoginPending) + g_game.enableFeature(GameNewSpeedLaw) + end + + if(version >= 984) then + g_game.enableFeature(GameContainerPagination) + g_game.enableFeature(GameBrowseField) + end + + if(version >= 1000) then + g_game.enableFeature(GameThingMarks) + g_game.enableFeature(GamePVPMode) + end + + if(version >= 1035) then + g_game.enableFeature(GameDoubleSkills) + g_game.enableFeature(GameBaseSkillU16) + end + + if(version >= 1036) then + g_game.enableFeature(GameCreatureIcons) + g_game.enableFeature(GameHideNpcNames) + end + + if(version >= 1038) then + g_game.enableFeature(GamePremiumExpiration) + end + + if(version >= 1050) then + g_game.enableFeature(GameEnhancedAnimations) + end + + if(version >= 1053) then + g_game.enableFeature(GameUnjustifiedPoints) + end + + if(version >= 1054) then + g_game.enableFeature(GameExperienceBonus) + end + + if(version >= 1055) then + g_game.enableFeature(GameDeathType) + end + + if(version >= 1057) then + g_game.enableFeature(GameIdleAnimations) + end + + if(version >= 1061) then + g_game.enableFeature(GameOGLInformation) + end + + if(version >= 1071) then + g_game.enableFeature(GameContentRevision) + end + + if(version >= 1072) then + g_game.enableFeature(GameAuthenticator) + end + + if(version >= 1074) then + g_game.enableFeature(GameSessionKey) + end + + if(version >= 1080) then + g_game.enableFeature(GameIngameStore) + end + + if(version >= 1092) then + g_game.enableFeature(GameIngameStoreServiceType) + end + + if(version >= 1093) then + g_game.enableFeature(GameIngameStoreHighlights) + end + + if(version >= 1094) then + g_game.enableFeature(GameAdditionalSkills) + end + + if(version >= 1100) then + g_game.enableFeature(GamePrey) + end + + if(version >= 1200) then + g_game.enableFeature(GameSequencedPackets) + --g_game.enableFeature(GameSendWorldName) + g_game.enableFeature(GamePlayerStateU32) + g_game.enableFeature(GameTibia12Protocol) + end + + modules.game_things.load() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.otmod new file mode 100644 index 0000000..678cf26 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_features/features.otmod @@ -0,0 +1,8 @@ +Module + name: game_features + description: Manager game features + reloadable: false + sandboxed: true + scripts: [features] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.lua new file mode 100644 index 0000000..3ac4ea4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.lua @@ -0,0 +1,314 @@ +Icons = {} +Icons[PlayerStates.Poison] = { tooltip = tr('You are poisoned'), path = '/images/game/states/poisoned', id = 'condition_poisoned' } +Icons[PlayerStates.Burn] = { tooltip = tr('You are burning'), path = '/images/game/states/burning', id = 'condition_burning' } +Icons[PlayerStates.Energy] = { tooltip = tr('You are electrified'), path = '/images/game/states/electrified', id = 'condition_electrified' } +Icons[PlayerStates.Drunk] = { tooltip = tr('You are drunk'), path = '/images/game/states/drunk', id = 'condition_drunk' } +Icons[PlayerStates.ManaShield] = { tooltip = tr('You are protected by a magic shield'), path = '/images/game/states/magic_shield', id = 'condition_magic_shield' } +Icons[PlayerStates.Paralyze] = { tooltip = tr('You are paralysed'), path = '/images/game/states/slowed', id = 'condition_slowed' } +Icons[PlayerStates.Haste] = { tooltip = tr('You are hasted'), path = '/images/game/states/haste', id = 'condition_haste' } +Icons[PlayerStates.Swords] = { tooltip = tr('You may not logout during a fight'), path = '/images/game/states/logout_block', id = 'condition_logout_block' } +Icons[PlayerStates.Drowning] = { tooltip = tr('You are drowning'), path = '/images/game/states/drowning', id = 'condition_drowning' } +Icons[PlayerStates.Freezing] = { tooltip = tr('You are freezing'), path = '/images/game/states/freezing', id = 'condition_freezing' } +Icons[PlayerStates.Dazzled] = { tooltip = tr('You are dazzled'), path = '/images/game/states/dazzled', id = 'condition_dazzled' } +Icons[PlayerStates.Cursed] = { tooltip = tr('You are cursed'), path = '/images/game/states/cursed', id = 'condition_cursed' } +Icons[PlayerStates.PartyBuff] = { tooltip = tr('You are strengthened'), path = '/images/game/states/strengthened', id = 'condition_strengthened' } +Icons[PlayerStates.PzBlock] = { tooltip = tr('You may not logout or enter a protection zone'), path = '/images/game/states/protection_zone_block', id = 'condition_protection_zone_block' } +Icons[PlayerStates.Pz] = { tooltip = tr('You are within a protection zone'), path = '/images/game/states/protection_zone', id = 'condition_protection_zone' } +Icons[PlayerStates.Bleeding] = { tooltip = tr('You are bleeding'), path = '/images/game/states/bleeding', id = 'condition_bleeding' } +Icons[PlayerStates.Hungry] = { tooltip = tr('You are hungry'), path = '/images/game/states/hungry', id = 'condition_hungry' } + +healthInfoWindow = nil +healthBar = nil +manaBar = nil +experienceBar = nil +soulLabel = nil +capLabel = nil +healthTooltip = 'Your character health is %d out of %d.' +manaTooltip = 'Your character mana is %d out of %d.' +experienceTooltip = 'You have %d%% to advance to level %d.' + +overlay = nil +healthCircleFront = nil +manaCircleFront = nil +healthCircle = nil +manaCircle = nil +topHealthBar = nil +topManaBar = nil + +function init() + connect(LocalPlayer, { onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + + connect(g_game, { onGameEnd = offline }) + + healthInfoWindow = g_ui.loadUI('healthinfo', modules.game_interface.getRightPanel()) + healthInfoWindow:disableResize() + + if not healthInfoWindow.forceOpen then + healthInfoButton = modules.client_topmenu.addRightGameToggleButton('healthInfoButton', tr('Health Information'), '/images/topbuttons/healthinfo', toggle) + if g_app.isMobile() then + healthInfoButton:hide() + else + healthInfoButton:setOn(true) + end + end + + healthBar = healthInfoWindow:recursiveGetChildById('healthBar') + manaBar = healthInfoWindow:recursiveGetChildById('manaBar') + experienceBar = healthInfoWindow:recursiveGetChildById('experienceBar') + soulLabel = healthInfoWindow:recursiveGetChildById('soulLabel') + capLabel = healthInfoWindow:recursiveGetChildById('capLabel') + + overlay = g_ui.createWidget('HealthOverlay', modules.game_interface.getMapPanel()) + healthCircleFront = overlay:getChildById('healthCircleFront') + manaCircleFront = overlay:getChildById('manaCircleFront') + healthCircle = overlay:getChildById('healthCircle') + manaCircle = overlay:getChildById('manaCircle') + topHealthBar = overlay:getChildById('topHealthBar') + topManaBar = overlay:getChildById('topManaBar') + + connect(overlay, { onGeometryChange = onOverlayGeometryChange }) + + -- load condition icons + for k,v in pairs(Icons) do + g_textures.preload(v.path) + end + + if g_game.isOnline() then + local localPlayer = g_game.getLocalPlayer() + onHealthChange(localPlayer, localPlayer:getHealth(), localPlayer:getMaxHealth()) + onManaChange(localPlayer, localPlayer:getMana(), localPlayer:getMaxMana()) + onLevelChange(localPlayer, localPlayer:getLevel(), localPlayer:getLevelPercent()) + onStatesChange(localPlayer, localPlayer:getStates(), 0) + onSoulChange(localPlayer, localPlayer:getSoul()) + onFreeCapacityChange(localPlayer, localPlayer:getFreeCapacity()) + end + + + hideLabels() + hideExperience() + + healthInfoWindow:setup() + + if g_app.isMobile() then + healthInfoWindow:close() + healthInfoButton:setOn(false) + end +end + +function terminate() + disconnect(LocalPlayer, { onHealthChange = onHealthChange, + onManaChange = onManaChange, + onLevelChange = onLevelChange, + onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + + disconnect(g_game, { onGameEnd = offline }) + disconnect(overlay, { onGeometryChange = onOverlayGeometryChange }) + + healthInfoWindow:destroy() + if healthInfoButton then + healthInfoButton:destroy() + end + overlay:destroy() +end + +function toggle() + if not healthInfoButton then return end + if healthInfoButton:isOn() then + healthInfoWindow:close() + healthInfoButton:setOn(false) + else + healthInfoWindow:open() + healthInfoButton:setOn(true) + end +end + +function toggleIcon(bitChanged) + local content = healthInfoWindow:recursiveGetChildById('conditionPanel') + + local icon = content:getChildById(Icons[bitChanged].id) + if icon then + icon:destroy() + else + icon = loadIcon(bitChanged) + icon:setParent(content) + end +end + +function loadIcon(bitChanged) + local icon = g_ui.createWidget('ConditionWidget', content) + icon:setId(Icons[bitChanged].id) + icon:setImageSource(Icons[bitChanged].path) + icon:setTooltip(Icons[bitChanged].tooltip) + return icon +end + +function offline() + healthInfoWindow:recursiveGetChildById('conditionPanel'):destroyChildren() +end + +-- hooked events +function onMiniWindowClose() + if healthInfoButton then + healthInfoButton:setOn(false) + end +end + +function onHealthChange(localPlayer, health, maxHealth) + if health > maxHealth then + maxHealth = health + end + + healthBar:setText(comma_value(health) .. ' / ' .. comma_value(maxHealth)) + healthBar:setTooltip(tr(healthTooltip, health, maxHealth)) + healthBar:setValue(health, 0, maxHealth) + + topHealthBar:setText(comma_value(health) .. ' / ' .. comma_value(maxHealth)) + topHealthBar:setTooltip(tr(healthTooltip, health, maxHealth)) + topHealthBar:setValue(health, 0, maxHealth) + + local healthPercent = math.floor(g_game.getLocalPlayer():getHealthPercent()) + local Yhppc = math.floor(208 * (1 - (healthPercent / 100))) + local rect = { x = 0, y = Yhppc, width = 63, height = 208 - Yhppc + 1 } + healthCircleFront:setImageClip(rect) + healthCircleFront:setImageRect(rect) + + if healthPercent > 92 then + healthCircleFront:setImageColor("#00BC00FF") + elseif healthPercent > 60 then + healthCircleFront:setImageColor("#50A150FF") + elseif healthPercent > 30 then + healthCircleFront:setImageColor("#A1A100FF") + elseif healthPercent > 8 then + healthCircleFront:setImageColor("#BF0A0AFF") + elseif healthPercent > 3 then + healthCircleFront:setImageColor("#910F0FFF") + else + healthCircleFront:setImageColor("#850C0CFF") + end +end + +function onManaChange(localPlayer, mana, maxMana) + if mana > maxMana then + maxMana = mana + end + + manaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana)) + manaBar:setTooltip(tr(manaTooltip, mana, maxMana)) + manaBar:setValue(mana, 0, maxMana) + + topManaBar:setText(comma_value(mana) .. ' / ' .. comma_value(maxMana)) + topManaBar:setTooltip(tr(manaTooltip, mana, maxMana)) + topManaBar:setValue(mana, 0, maxMana) + + local Ymppc = math.floor(208 * (1 - (math.floor((maxMana - (maxMana - mana)) * 100 / maxMana) / 100))) + local rect = { x = 0, y = Ymppc, width = 63, height = 208 - Ymppc + 1 } + manaCircleFront:setImageClip(rect) + manaCircleFront:setImageRect(rect) +end + +function onLevelChange(localPlayer, value, percent) + experienceBar:setText(percent .. '%') + experienceBar:setTooltip(tr(experienceTooltip, percent, value+1)) + experienceBar:setPercent(percent) +end + +function onSoulChange(localPlayer, soul) + soulLabel:setText(tr('Soul') .. ': ' .. soul) +end + +function onFreeCapacityChange(player, freeCapacity) + capLabel:setText(tr('Cap') .. ': ' .. freeCapacity) +end + +function onStatesChange(localPlayer, now, old) + if now == old then return end + + local bitsChanged = bit32.bxor(now, old) + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > bitsChanged then break end + local bitChanged = bit32.band(bitsChanged, pow) + if bitChanged ~= 0 then + toggleIcon(bitChanged) + end + end +end + +-- personalization functions +function hideLabels() + local content = healthInfoWindow:recursiveGetChildById('conditionPanel') + local removeHeight = math.max(capLabel:getMarginRect().height, soulLabel:getMarginRect().height) + content:getMarginRect().height - 3 + capLabel:setOn(false) + soulLabel:setOn(false) + content:setVisible(false) + healthInfoWindow:setHeight(math.max(healthInfoWindow.minimizedHeight, healthInfoWindow:getHeight() - removeHeight)) +end + +function hideExperience() + local removeHeight = experienceBar:getMarginRect().height + experienceBar:setOn(false) + healthInfoWindow:setHeight(math.max(healthInfoWindow.minimizedHeight, healthInfoWindow:getHeight() - removeHeight)) +end + +function setHealthTooltip(tooltip) + healthTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + healthBar:setTooltip(tr(healthTooltip, localPlayer:getHealth(), localPlayer:getMaxHealth())) + end +end + +function setManaTooltip(tooltip) + manaTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + manaBar:setTooltip(tr(manaTooltip, localPlayer:getMana(), localPlayer:getMaxMana())) + end +end + +function setExperienceTooltip(tooltip) + experienceTooltip = tooltip + + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + experienceBar:setTooltip(tr(experienceTooltip, localPlayer:getLevelPercent(), localPlayer:getLevel()+1)) + end +end + +function onOverlayGeometryChange() + if g_app.isMobile() then + topHealthBar:setMarginTop(35) + topManaBar:setMarginTop(35) + local width = overlay:getWidth() + local margin = width / 3 + 10 + topHealthBar:setMarginLeft(margin) + topManaBar:setMarginRight(margin) + return + end + + local classic = g_settings.getBoolean("classicView") + local minMargin = 40 + if classic then + topHealthBar:setMarginTop(15) + topManaBar:setMarginTop(15) + else + topHealthBar:setMarginTop(45 - overlay:getParent():getMarginTop()) + topManaBar:setMarginTop(45 - overlay:getParent():getMarginTop()) + minMargin = 200 + end + + local height = overlay:getHeight() + local width = overlay:getWidth() + + topHealthBar:setMarginLeft(math.max(minMargin, (width - height + 50) / 2 + 2)) + topManaBar:setMarginRight(math.max(minMargin, (width - height + 50) / 2 + 2)) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otmod new file mode 100644 index 0000000..4c52ef0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otmod @@ -0,0 +1,9 @@ +Module + name: game_healthinfo + description: Displays health, mana points, soul points, and conditions + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ healthinfo ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otui new file mode 100644 index 0000000..8d55617 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_healthinfo/healthinfo.otui @@ -0,0 +1,5 @@ +HealthInfoWindow + id: healthInfoWindow + @onClose: modules.game_healthinfo.onMiniWindowClose() + &save: true + &autoOpen: 2 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_extra.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_extra.lua new file mode 100644 index 0000000..ac066de --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_extra.lua @@ -0,0 +1,119 @@ +extraHotkeys = {} + +function addExtraHotkey(name, description, callback) + table.insert(extraHotkeys, { + name = name:lower(), + description = tr(description), + callback = callback + }) + +end + +function setupExtraHotkeys(combobox) + addExtraHotkey("none", "None", nil) + addExtraHotkey("cancelAttack", "Stop attacking", function(repeated) + if not repeated then + g_game.attack(nil) + end + end) + addExtraHotkey("attackNext", "Attack next target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local nextChild = nil + local breakNext = false + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + nextChild = child + if breakNext then + break + end + if child.creature == attackedCreature then + breakNext = true + nextChild = battlePanel:getFirstChild() + end + end + if not breakNext then + nextChild = battlePanel:getFirstChild() + end + if nextChild and nextChild.creature ~= attackedCreature then + g_game.attack(nextChild.creature) + end + end) + + addExtraHotkey("attackPrevious", "Attack previous target from battle list", function(repeated) + if repeated or not modules.game_battle then + return + end + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local prevChild = nil + for i, child in ipairs(battlePanel:getChildren()) do + if not child.creature or not child:isOn() then + break + end + if child.creature == attackedCreature then + break + end + prevChild = child + end + if prevChild and prevChild.creature ~= attackedCreature then + g_game.attack(prevChild.creature) + end + end) + + addExtraHotkey("toogleWsad", "Enable/disable wsad walking", function(repeated) + if repeated or not modules.game_console then + return + end + if not modules.game_console.consoleToggleChat:isChecked() then + modules.game_console.disableChat(true) + else + modules.game_console.enableChat(true) + end + end) + + for index, actionDetails in ipairs(extraHotkeys) do + combobox:addOption(actionDetails.description) + end +end + +function executeExtraHotkey(action, repeated) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action and actionDetails.callback then + actionDetails.callback(repeated) + end + end +end + +function translateActionToActionComboboxIndex(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return index + end + end + return 1 +end + +function translateActionComboboxIndexToAction(index) + if index > 1 and index <= #extraHotkeys then + return extraHotkeys[index].name + end + return nil +end + +function getActionDescription(action) + action = action:lower() + for index, actionDetails in ipairs(extraHotkeys) do + if actionDetails.name == action then + return actionDetails.description + end + end + return "invalid action" +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.lua new file mode 100644 index 0000000..eab9953 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.lua @@ -0,0 +1,608 @@ +HOTKEY_MANAGER_USE = nil +HOTKEY_MANAGER_USEONSELF = 1 +HOTKEY_MANAGER_USEONTARGET = 2 +HOTKEY_MANAGER_USEWITH = 3 + +HotkeyColors = { + text = '#888888', + textAutoSend = '#FFFFFF', + itemUse = '#8888FF', + itemUseSelf = '#00FF00', + itemUseTarget = '#FF0000', + itemUseWith = '#F5B325', + extraAction = '#FFAA00' +} + +hotkeysManagerLoaded = false +hotkeysWindow = nil +configSelector = nil +hotkeysButton = nil +currentHotkeyLabel = nil +itemWidget = nil +addHotkeyButton = nil +removeHotkeyButton = nil +hotkeyText = nil +hotKeyTextLabel = nil +sendAutomatically = nil +defaultComboKeys = nil +perCharacter = true +mouseGrabberWidget = nil +currentHotkeys = nil +boundCombosCallback = {} +hotkeysList = {} +hotkeyConfigs = {} +currentConfig = 1 +configValueChanged = false + +-- public functions +function init() + if not g_app.isMobile() then + hotkeysButton = modules.client_topmenu.addLeftGameButton('hotkeysButton', tr('Hotkeys') .. ' (Ctrl+K)', '/images/topbuttons/hotkeys', toggle, false, 7) + end + g_keyboard.bindKeyDown('Ctrl+K', toggle) + hotkeysWindow = g_ui.displayUI('hotkeys_manager') + hotkeysWindow:setVisible(false) + + configSelector = hotkeysWindow:getChildById('configSelector') + currentHotkeys = hotkeysWindow:getChildById('currentHotkeys') + addHotkeyButton = hotkeysWindow:getChildById('addHotkeyButton') + removeHotkeyButton = hotkeysWindow:getChildById('removeHotkeyButton') + hotkeyText = hotkeysWindow:getChildById('hotkeyText') + hotKeyTextLabel = hotkeysWindow:getChildById('hotKeyTextLabel') + sendAutomatically = hotkeysWindow:getChildById('sendAutomatically') + + mouseGrabberWidget = g_ui.createWidget('UIWidget') + mouseGrabberWidget:setVisible(false) + mouseGrabberWidget:setFocusable(false) + mouseGrabberWidget.onMouseRelease = onChooseItemMouseRelease + + currentHotkeys.onChildFocusChange = function(self, hotkeyLabel) onSelectHotkeyLabel(hotkeyLabel) end + g_keyboard.bindKeyPress('Down', function() currentHotkeys:focusNextChild(KeyboardFocusReason) end, hotkeysWindow) + g_keyboard.bindKeyPress('Up', function() currentHotkeys:focusPreviousChild(KeyboardFocusReason) end, hotkeysWindow) + + if hotkeysWindow.action and setupExtraHotkeys then + setupExtraHotkeys(hotkeysWindow.action) + end + + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + + for i = 1, configSelector:getOptionsCount() do + hotkeyConfigs[i] = g_configs.create("/hotkeys_" .. i .. ".otml") + end + + load() +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + + g_keyboard.unbindKeyDown('Ctrl+K') + + unload() + + hotkeysWindow:destroy() + if hotkeysButton then + hotkeysButton:destroy() + end + mouseGrabberWidget:destroy() +end + +function online() + reload() + hide() +end + +function offline() + unload() + hide() +end + +function show() + if not g_game.isOnline() then + return + end + hotkeysWindow:show() + hotkeysWindow:raise() + hotkeysWindow:focus() +end + +function hide() + hotkeysWindow:hide() +end + +function toggle() + if not hotkeysWindow:isVisible() then + show() + else + hide() + end +end + +function ok() + save() + hide() +end + +function cancel() + reload() + hide() +end + +function load(forceDefaults) + hotkeysManagerLoaded = false + currentConfig = 1 + + local hotkeysNode = g_settings.getNode('hotkeys') or {} + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + if hotkeysNode[index] ~= nil and hotkeysNode[index] > 0 and hotkeysNode[index] <= #hotkeyConfigs then + currentConfig = hotkeysNode[index] + end + + configSelector:setCurrentIndex(currentConfig, true) + + local hotkeySettings = hotkeyConfigs[currentConfig]:getNode('hotkeys') + local hotkeys = {} + + if not table.empty(hotkeySettings) then hotkeys = hotkeySettings end + + hotkeyList = {} + if not forceDefaults then + if not table.empty(hotkeys) then + for keyCombo, setting in pairs(hotkeys) do + keyCombo = tostring(keyCombo) + addKeyCombo(keyCombo, setting) + hotkeyList[keyCombo] = setting + end + end + end + + if currentHotkeys:getChildCount() == 0 then + loadDefautComboKeys() + end + + configValueChanged = false + hotkeysManagerLoaded = true +end + +function unload() + local gameRootPanel = modules.game_interface.getRootPanel() + for keyCombo,callback in pairs(boundCombosCallback) do + g_keyboard.unbindKeyPress(keyCombo, callback, gameRootPanel) + end + boundCombosCallback = {} + currentHotkeys:destroyChildren() + currentHotkeyLabel = nil + updateHotkeyForm(true) + hotkeyList = {} +end + +function reset() + unload() + load(true) +end + +function reload() + unload() + load() +end + +function save() + if not configValueChanged then + return + end + + local hotkeySettings = hotkeyConfigs[currentConfig]:getNode('hotkeys') or {} + + table.clear(hotkeySettings) + + for _,child in pairs(currentHotkeys:getChildren()) do + hotkeySettings[child.keyCombo] = { + autoSend = child.autoSend, + itemId = child.itemId, + subType = child.subType, + useType = child.useType, + value = child.value, + action = child.action + } + end + + hotkeyList = hotkeySettings + hotkeyConfigs[currentConfig]:setNode('hotkeys', hotkeySettings) + hotkeyConfigs[currentConfig]:save() + + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + local hotkeysNode = g_settings.getNode('hotkeys') or {} + hotkeysNode[index] = currentConfig + g_settings.setNode('hotkeys', hotkeysNode) + g_settings.save() +end + +function onConfigChange() + if not configSelector then return end + local index = g_game.getCharacterName() .. "_" .. g_game.getClientVersion() + local hotkeysNode = g_settings.getNode('hotkeys') or {} + hotkeysNode[index] = configSelector.currentIndex + g_settings.setNode('hotkeys', hotkeysNode) + reload() +end + +function loadDefautComboKeys() + if not defaultComboKeys then + for i=1,12 do + addKeyCombo('F' .. i) + end + for i=1,4 do + addKeyCombo('Shift+F' .. i) + end + else + for keyCombo, keySettings in pairs(defaultComboKeys) do + addKeyCombo(keyCombo, keySettings) + end + end +end + +function setDefaultComboKeys(combo) + defaultComboKeys = combo +end + +function onChooseItemMouseRelease(self, mousePosition, mouseButton) + local item = nil + if mouseButton == MouseLeftButton then + local clickedWidget = modules.game_interface.getRootPanel():recursiveGetChildByPos(mousePosition, false) + if clickedWidget then + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + local thing = tile:getTopMoveThing() + if thing and thing:isItem() then + item = thing + end + end + elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then + item = clickedWidget:getItem() + end + end + end + + if item and currentHotkeyLabel then + currentHotkeyLabel.itemId = item:getId() + if item:isFluidContainer() then + currentHotkeyLabel.subType = item:getSubType() + end + if item:isMultiUse() then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH + else + currentHotkeyLabel.useType = HOTKEY_MANAGER_USE + end + currentHotkeyLabel.value = nil + currentHotkeyLabel.autoSend = false + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm(true) + end + + show() + + g_mouse.popCursor('target') + self:ungrabMouse() + return true +end + +function startChooseItem() + if g_ui.isMouseGrabbed() then return end + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') + hide() +end + +function clearObject() + currentHotkeyLabel.action = nil + currentHotkeyLabel.itemId = nil + currentHotkeyLabel.subType = nil + currentHotkeyLabel.useType = nil + currentHotkeyLabel.autoSend = nil + currentHotkeyLabel.value = nil + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm(true) +end + +function addHotkey() + local assignWindow = g_ui.createWidget('HotkeyAssignWindow', rootWidget) + assignWindow:grabKeyboard() + + local comboLabel = assignWindow:getChildById('comboPreview') + comboLabel.keyCombo = '' + assignWindow.onKeyDown = hotkeyCapture +end + +function addKeyCombo(keyCombo, keySettings, focus) + if keyCombo == nil or #keyCombo == 0 then return end + if not keyCombo then return end + local hotkeyLabel = currentHotkeys:getChildById(keyCombo) + if not hotkeyLabel then + hotkeyLabel = g_ui.createWidget('HotkeyListLabel') + hotkeyLabel:setId(keyCombo) + + local children = currentHotkeys:getChildren() + children[#children+1] = hotkeyLabel + table.sort(children, function(a,b) + if a:getId():len() < b:getId():len() then + return true + elseif a:getId():len() == b:getId():len() then + return a:getId() < b:getId() + else + return false + end + end) + for i=1,#children do + if children[i] == hotkeyLabel then + currentHotkeys:insertChild(i, hotkeyLabel) + break + end + end + + if keySettings then + currentHotkeyLabel = hotkeyLabel + hotkeyLabel.keyCombo = keyCombo + hotkeyLabel.autoSend = toboolean(keySettings.autoSend) + hotkeyLabel.action = keySettings.action + hotkeyLabel.itemId = tonumber(keySettings.itemId) + hotkeyLabel.subType = tonumber(keySettings.subType) + hotkeyLabel.useType = tonumber(keySettings.useType) + if keySettings.value then hotkeyLabel.value = tostring(keySettings.value) end + else + hotkeyLabel.keyCombo = keyCombo + hotkeyLabel.autoSend = false + hotkeyLabel.itemId = nil + hotkeyLabel.subType = nil + hotkeyLabel.useType = nil + hotkeyLabel.action = nil + hotkeyLabel.value = '' + end + + updateHotkeyLabel(hotkeyLabel) + + local gameRootPanel = modules.game_interface.getRootPanel() + if keyCombo:lower():find("ctrl") then + if boundCombosCallback[keyCombo] then + g_keyboard.unbindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel) + end + end + + boundCombosCallback[keyCombo] = function(k, c, ticks) prepareKeyCombo(keyCombo, ticks) end + g_keyboard.bindKeyPress(keyCombo, boundCombosCallback[keyCombo], gameRootPanel) + + if not keyCombo:lower():find("ctrl") then + local keyComboCtrl = "Ctrl+" .. keyCombo + if not boundCombosCallback[keyComboCtrl] then + boundCombosCallback[keyComboCtrl] = function(k, c, ticks) prepareKeyCombo(keyComboCtrl, ticks) end + g_keyboard.bindKeyPress(keyComboCtrl, boundCombosCallback[keyComboCtrl], gameRootPanel) + end + end + end + + if focus then + currentHotkeys:focusChild(hotkeyLabel) + currentHotkeys:ensureChildVisible(hotkeyLabel) + updateHotkeyForm(true) + end + configValueChanged = true +end + +function prepareKeyCombo(keyCombo, ticks) + local hotKey = hotkeyList[keyCombo] + if (keyCombo:lower():find("ctrl") and not hotKey) or (hotKey and hotKey.itemId == nil and (not hotKey.value or #hotKey.value == 0) and not hotKey.action) then + keyCombo = keyCombo:gsub("Ctrl%+", "") + keyCombo = keyCombo:gsub("ctrl%+", "") + hotKey = hotkeyList[keyCombo] + end + if not hotKey then + return + end + + if hotKey.itemId == nil and hotKey.action == nil then -- say + scheduleEvent(function() doKeyCombo(keyCombo, ticks >= 5) end, g_settings.getNumber('hotkeyDelay')) + else + doKeyCombo(keyCombo, ticks >= 5) + end +end + +function doKeyCombo(keyCombo, repeated) + if not g_game.isOnline() then return end + if modules.game_console and modules.game_console.isChatEnabled() then + if keyCombo:len() == 1 then + return + end + end + if modules.game_walking then + modules.game_walking.checkTurn() + end + + local hotKey = hotkeyList[keyCombo] + if not hotKey then return end + + local hotkeyDelay = 100 + if hotKey.hotkeyDelayTo == nil or g_clock.millis() > hotKey.hotkeyDelayTo + hotkeyDelay then + hotkeyDelay = 200 -- for first use + end + if hotKey.hotkeyDelayTo ~= nil and g_clock.millis() < hotKey.hotkeyDelayTo then + return + end + if hotKey.action then + executeExtraHotkey(hotKey.action, repeated) + elseif hotKey.itemId == nil then + if not hotKey.value or #hotKey.value == 0 then return end + if hotKey.autoSend then + modules.game_console.sendMessage(hotKey.value) + else + modules.game_console.setTextEditText(hotKey.value) + end + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USE then + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEONSELF then + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEONTARGET then + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + elseif hotKey.useType == HOTKEY_MANAGER_USEWITH then + hotKey.hotkeyDelayTo = g_clock.millis() + hotkeyDelay + end +end + +function updateHotkeyLabel(hotkeyLabel) + if not hotkeyLabel then return end + if hotkeyLabel.action ~= nil then + hotkeyLabel:setText(tr('%s: (Action: %s)', hotkeyLabel.keyCombo, getActionDescription(hotkeyLabel.action))) + hotkeyLabel:setColor(HotkeyColors.extraAction) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEONSELF then + hotkeyLabel:setText(tr('%s: (use object on yourself)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseSelf) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEONTARGET then + hotkeyLabel:setText(tr('%s: (use object on target)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseTarget) + elseif hotkeyLabel.useType == HOTKEY_MANAGER_USEWITH then + hotkeyLabel:setText(tr('%s: (use object with crosshair)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUseWith) + elseif hotkeyLabel.itemId ~= nil then + hotkeyLabel:setText(tr('%s: (use object)', hotkeyLabel.keyCombo)) + hotkeyLabel:setColor(HotkeyColors.itemUse) + else + local text = hotkeyLabel.keyCombo .. ': ' + if hotkeyLabel.value then + text = text .. hotkeyLabel.value + end + hotkeyLabel:setText(text) + if hotkeyLabel.autoSend then + hotkeyLabel:setColor(HotkeyColors.autoSend) + else + hotkeyLabel:setColor(HotkeyColors.text) + end + end +end + +function updateHotkeyForm(reset) + configValueChanged = true + if hotkeysWindow.action then + if currentHotkeyLabel then + hotkeysWindow.action:enable() + if currentHotkeyLabel.action then + hotkeysWindow.action:setCurrentIndex(translateActionToActionComboboxIndex(currentHotkeyLabel.action), true) + else + hotkeysWindow.action:setCurrentIndex(1, true) + end + else + hotkeysWindow.action:disable() + hotkeysWindow.action:setCurrentIndex(1, true) + end + end + local hasCustomAction = hotkeysWindow.action and hotkeysWindow.action.currentIndex > 1 + if currentHotkeyLabel and not hasCustomAction then + removeHotkeyButton:enable() + if currentHotkeyLabel.itemId ~= nil then + hotkeyText:clearText() + hotkeyText:disable() + hotKeyTextLabel:disable() + sendAutomatically:setChecked(false) + sendAutomatically:disable() + else + hotkeyText:enable() + hotkeyText:focus() + hotKeyTextLabel:enable() + if reset then + hotkeyText:setCursorPos(-1) + end + hotkeyText:setText(currentHotkeyLabel.value) + sendAutomatically:setChecked(currentHotkeyLabel.autoSend) + sendAutomatically:setEnabled(currentHotkeyLabel.value and #currentHotkeyLabel.value > 0) + end + else + removeHotkeyButton:disable() + hotkeyText:disable() + sendAutomatically:disable() + hotkeyText:clearText() + sendAutomatically:setChecked(false) + end +end + +function removeHotkey() + if currentHotkeyLabel == nil then return end + local gameRootPanel = modules.game_interface.getRootPanel() + configValueChanged = true + g_keyboard.unbindKeyPress(currentHotkeyLabel.keyCombo, boundCombosCallback[currentHotkeyLabel.keyCombo], gameRootPanel) + boundCombosCallback[currentHotkeyLabel.keyCombo] = nil + currentHotkeyLabel:destroy() + currentHotkeyLabel = nil +end + +function updateHotkeyAction() + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + configValueChanged = true + currentHotkeyLabel.action = translateActionComboboxIndexToAction(hotkeysWindow.action.currentIndex) + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onHotkeyTextChange(value) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + currentHotkeyLabel.value = value + if value == '' then + currentHotkeyLabel.autoSend = false + end + configValueChanged = true + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onSendAutomaticallyChange(autoSend) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + if not currentHotkeyLabel.value or #currentHotkeyLabel.value == 0 then return end + configValueChanged = true + currentHotkeyLabel.autoSend = autoSend + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onChangeUseType(useTypeWidget) + if not hotkeysManagerLoaded then return end + if currentHotkeyLabel == nil then return end + configValueChanged = true + if useTypeWidget == useOnSelf then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONSELF + elseif useTypeWidget == useOnTarget then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEONTARGET + elseif useTypeWidget == useWith then + currentHotkeyLabel.useType = HOTKEY_MANAGER_USEWITH + else + currentHotkeyLabel.useType = HOTKEY_MANAGER_USE + end + updateHotkeyLabel(currentHotkeyLabel) + updateHotkeyForm() +end + +function onSelectHotkeyLabel(hotkeyLabel) + currentHotkeyLabel = hotkeyLabel + updateHotkeyForm(true) +end + +function hotkeyCapture(assignWindow, keyCode, keyboardModifiers) + local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) + local comboPreview = assignWindow:getChildById('comboPreview') + comboPreview:setText(tr('Current hotkey to add: %s', keyCombo)) + comboPreview.keyCombo = keyCombo + comboPreview:resizeToText() + assignWindow:getChildById('addButton'):enable() + return true +end + +function hotkeyCaptureOk(assignWindow, keyCombo) + addKeyCombo(keyCombo, nil, true) + assignWindow:destroy() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otmod new file mode 100644 index 0000000..4f4e0b7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otmod @@ -0,0 +1,9 @@ +Module + name: game_hotkeys + description: Manage client hotkeys + author: andrefaramir, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ hotkeys_extra, hotkeys_manager ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otui new file mode 100644 index 0000000..230c0c2 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_hotkeys/hotkeys_manager.otui @@ -0,0 +1,207 @@ +HotkeyListLabel < UILabel + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + phantom: false + + $focus: + background-color: #ffffff22 + +MainWindow + id: hotkeysWindow + !text: tr('Hotkeys') + size: 370 475 + + @onEnter: modules.game_hotkeys.ok() + @onEscape: modules.game_hotkeys.cancel() + + ComboBox + id: configSelector + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + menu-scroll: true + menu-height: 125 + menu-scroll-step: 25 + text-offset: 5 2 + @onOptionChange: modules.game_hotkeys.onConfigChange() + @onSetup: | + self:addOption(tr("Hotkeys config #1")) + self:addOption(tr("Hotkeys config #2")) + self:addOption(tr("Hotkeys config #3")) + self:addOption(tr("Hotkeys config #4")) + self:addOption(tr("Hotkeys config #5")) + + + VerticalScrollBar + id: currentHotkeysScrollBar + height: 150 + anchors.top: prev.bottom + anchors.right: parent.right + margin-top: 5 + step: 14 + pixels-scroll: true + + TextList + id: currentHotkeys + vertical-scrollbar: currentHotkeysScrollBar + anchors.left: parent.left + anchors.right: prev.left + anchors.top: prev.top + anchors.bottom: prev.bottom + focusable: false + + Button + id: resetButton + width: 96 + !text: tr('Reset All') + anchors.left: parent.left + anchors.top: next.top + @onClick: modules.game_hotkeys.reset() + margin-right: 10 + + Button + id: addHotkeyButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.top: next.top + margin-right: 5 + @onClick: modules.game_hotkeys.addHotkey() + + Button + id: removeHotkeyButton + !text: tr('Remove') + width: 64 + enabled: false + anchors.right: parent.right + anchors.top: currentHotkeys.bottom + margin-top: 8 + @onClick: modules.game_hotkeys.removeHotkey() + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + Label + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 10 + !text: tr('Extra action:') + + ComboBox + id: action + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 5 + margin-top: -4 + enabled: false + @onOptionChange: modules.game_hotkeys.updateHotkeyAction() + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + Label + id: hotKeyTextLabel + !text: tr('Edit hotkey text:') + enable: false + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 6 + + TextEdit + id: hotkeyText + enabled: false + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 2 + @onTextChange: modules.game_hotkeys.onHotkeyTextChange(self:getText()) + + CheckBox + id: sendAutomatically + !text: tr('Send automatically') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + enabled:false + margin-top: 5 + @onCheckChange: modules.game_hotkeys.onSendAutomaticallyChange(self:isChecked()) + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 5 + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + @onClick: modules.game_hotkeys.ok() + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_hotkeys.cancel() + +HotkeyAssignWindow < MainWindow + id: assignWindow + !text: tr('Button Assign') + size: 360 150 + @onEscape: self:destroy() + + Label + !text: tr('Please, press the key you wish to add onto your hotkeys manager') + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + text-auto-resize: true + text-align: left + + Label + id: comboPreview + !text: tr('Current hotkey to add: %s', 'none') + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 10 + text-auto-resize: true + + HorizontalSeparator + id: separator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: addButton + !text: tr('Add') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_hotkeys.hotkeyCaptureOk(self:getParent(), self:getParent():getChildById('comboPreview').keyCombo) + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: self:getParent():destroy() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.lua new file mode 100644 index 0000000..a4704c6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.lua @@ -0,0 +1,313 @@ +local imbuingWindow +local bankGold = 0 +local inventoryGold = 0 +local itemImbuements = {} +local emptyImbue +local groupsCombo +local imbueLevelsCombo +local protectionBtn +local clearImbue +local selectedImbue +local imbueItems = {} +local protection = false +local clearConfirmWindow +local imbueConfirmWindow + +function init() + connect(g_game, { + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) + + imbuingWindow = g_ui.displayUI('imbuing') + emptyImbue = imbuingWindow.emptyImbue + groupsCombo = emptyImbue.groups + imbueLevelsCombo = emptyImbue.imbuement + protectionBtn = emptyImbue.protection + clearImbue = imbuingWindow.clearImbue + imbuingWindow:hide() + + groupsCombo.onOptionChange = function(widget) + imbueLevelsCombo:clear() + if itemImbuements ~= nil then + local selectedGroup = groupsCombo:getCurrentOption().text + for _,imbuement in ipairs(itemImbuements) do + if imbuement["group"] == selectedGroup then + emptyImbue.imbuement:addOption(imbuement["name"]) + end + end + imbueLevelsCombo.onOptionChange(imbueLevelsCombo) -- update options + end + end + + imbueLevelsCombo.onOptionChange = function(widget) + setProtection(false) + local selectedGroup = groupsCombo:getCurrentOption().text + for _,imbuement in ipairs(itemImbuements) do + if imbuement["group"] == selectedGroup then + if #imbuement["sources"] == widget.currentIndex then + selectedImbue = imbuement + for i,source in ipairs(imbuement["sources"]) do + for _,item in ipairs(imbueItems) do + if item:getId() == source["item"]:getId() then + if item:getCount() >= source["item"]:getCount() then + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_green") + emptyImbue.imbue:setEnabled(true) + emptyImbue.requiredItems:getChildByIndex(i).count:setColor("white") + end + if item:getCount() < source["item"]:getCount() then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.requiredItems:getChildByIndex(i).count:setColor("red") + end + emptyImbue.requiredItems:getChildByIndex(i).count:setText(item:getCount() .. "/" .. source["item"]:getCount()) + end + end + emptyImbue.requiredItems:getChildByIndex(i).item:setItemId(source["item"]:getId()) + emptyImbue.requiredItems:getChildByIndex(i).item:setTooltip("The imbuement requires " .. source["description"] .. ".") + end + for i = 3, widget.currentIndex + 1, -1 do + emptyImbue.requiredItems:getChildByIndex(i).count:setText("") + emptyImbue.requiredItems:getChildByIndex(i).item:setItemId(0) + emptyImbue.requiredItems:getChildByIndex(i).item:setTooltip("") + end + emptyImbue.protectionCost:setText(imbuement["protectionCost"]) + emptyImbue.cost:setText(imbuement["cost"]) + if not protection and (bankGold + inventoryGold) < imbuement["cost"] then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + if not protection and (bankGold + inventoryGold) >= imbuement["cost"] then + emptyImbue.cost:setColor("white") + end + if protection and (bankGold + inventoryGold) < (imbuement["cost"] + imbuement["protectionCost"]) then + emptyImbue.imbue:setEnabled(false) + emptyImbue.imbue:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + if protection and (bankGold + inventoryGold) >= (imbuement["cost"] + imbuement["protectionCost"]) then + emptyImbue.cost:setColor("white") + end + emptyImbue.successRate:setText(imbuement["successRate"] .. "%") + if selectedImbue["successRate"] > 50 then + emptyImbue.successRate:setColor("white") + else + emptyImbue.successRate:setColor("red") + end + emptyImbue.description:setText(imbuement["description"]) + end + end + end + end + + protectionBtn.onClick = function() + setProtection(not protection) + end +end + +function setProtection(value) + protection = value + if protection then + emptyImbue.cost:setText(selectedImbue["cost"] + selectedImbue["protectionCost"]) + emptyImbue.successRate:setText("100%") + emptyImbue.successRate:setColor("green") + protectionBtn:setImageClip(torect("66 0 66 66")) + else + if selectedImbue then + emptyImbue.cost:setText(selectedImbue["cost"]) + emptyImbue.successRate:setText(selectedImbue["successRate"] .. "%") + if selectedImbue["successRate"] > 50 then + emptyImbue.successRate:setColor("white") + else + emptyImbue.successRate:setColor("red") + end + end + protectionBtn:setImageClip(torect("0 0 66 66")) + end +end + +function terminate() + disconnect(g_game, { + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onImbuementWindow = onImbuementWindow, + onCloseImbuementWindow = onCloseImbuementWindow + }) + + imbuingWindow:destroy() +end + +function resetSlots() + emptyImbue:setVisible(false) + clearImbue:setVisible(false) + for i=1,3 do + imbuingWindow.itemInfo.slots:getChildByIndex(i):setText("Slot " .. i) + imbuingWindow.itemInfo.slots:getChildByIndex(i):setEnabled(false) + imbuingWindow.itemInfo.slots:getChildByIndex(i):setTooltip("Items can have up to three imbuements slots. This slot is not available for this item.") + imbuingWindow.itemInfo.slots:getChildByIndex(i).onClick = nil + end +end + +function selectSlot(widget, slotId, activeSlot) + if activeSlot then + emptyImbue:setVisible(false) + widget:setText(activeSlot[1]["name"]) + clearImbue.title:setText('Clear Imbuement "' .. activeSlot[1]["name"] .. '"') + clearImbue.groups:clear() + clearImbue.groups:addOption(activeSlot[1]["group"]) + clearImbue.imbuement:clear() + clearImbue.imbuement:addOption(activeSlot[1]["name"]) + clearImbue.description:setText(activeSlot[1]["description"]) + + hours = string.format("%02.f", math.floor(activeSlot[2]/3600)) + mins = string.format("%02.f", math.floor(activeSlot[2]/60 - (hours*60))) + clearImbue.time.timeRemaining:setText(hours..":"..mins.."h") + + clearImbue.cost:setText(activeSlot[3]) + if (bankGold + inventoryGold) < activeSlot[3] then + emptyImbue.clear:setEnabled(false) + emptyImbue.clear:setImageSource("/images/game/imbuing/imbue_empty") + emptyImbue.cost:setColor("red") + end + + local yesCallback = function() + g_game.clearImbuement(slotId) + widget:setText("Slot " .. (slotId + 1)) + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + local noCallback = function() + imbuingWindow:show() + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + + clearImbue.clear.onClick = function() + imbuingWindow:hide() + clearConfirmWindow = displayGeneralBox(tr('Confirm Clearing'), tr('Do you wish to spend ' .. activeSlot[3] .. ' gold coins to clear the imbuement "' .. activeSlot[1]["name"] .. '" from your item?'), { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + end + + clearImbue:setVisible(true) + else + emptyImbue:setVisible(true) + clearImbue:setVisible(false) + + local yesCallback = function() + g_game.applyImbuement(slotId, selectedImbue["id"], protection) + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + widget:setText(selectedImbue["name"]) + imbuingWindow:show() + end + local noCallback = function() + imbuingWindow:show() + if clearConfirmWindow then + clearConfirmWindow:destroy() + clearConfirmWindow=nil + end + end + + emptyImbue.imbue.onClick = function() + imbuingWindow:hide() + local cost = selectedImbue["cost"] + local successRate = selectedImbue["successRate"] + if protection then + cost = cost + selectedImbue["protectionCost"] + successRate = "100" + end + clearConfirmWindow = displayGeneralBox(tr('Confirm Imbuing Attempt'), 'You are about to imbue your item with "' .. selectedImbue["name"] .. '".\nYour chance to succeed is ' .. successRate .. '%. It will consume the required astral sources and '.. cost ..' gold coins.\nDo you wish to proceed?', { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + end + end +end + +function onImbuementWindow(itemId, slots, activeSlots, imbuements, needItems) + if not itemId then + return + end + resetSlots() + imbueItems = table.copy(needItems) + imbuingWindow.itemInfo.item:setItemId(itemId) + + for i=1, slots do + local slot = imbuingWindow.itemInfo.slots:getChildByIndex(i) + slot.onClick = function(widget) + selectSlot(widget, i - 1) + end + slot:setTooltip("Use this slot to imbue your item. Depending on the item you can have up to three different imbuements.") + slot:setEnabled(true) + + if slot:getId() == "slot0" then + selectSlot(slot, i - 1) + end + end + + for i, slot in pairs(activeSlots) do + local activeSlotBtn = imbuingWindow.itemInfo.slots:getChildById("slot" .. i) + activeSlotBtn.onClick = function(widget) + selectSlot(widget, i, slot) + end + if activeSlotBtn:getId() == "slot0" then + selectSlot(activeSlotBtn, i, slot) + end + end + + if imbuements ~= nil then + groupsCombo:clear() + imbueLevelsCombo:clear() + itemImbuements = table.copy(imbuements) + for _,imbuement in ipairs(itemImbuements) do + if not groupsCombo:isOption(imbuement["group"]) then + groupsCombo:addOption(imbuement["group"]) + end + end + end + show() +end + +function onResourceBalance(type, balance) + if type == 0 then + bankGold = balance + elseif type == 1 then + inventoryGold = balance + end + if type == 0 or type == 1 then + imbuingWindow.balance:setText(tr("Balance") .. ":\n" .. (bankGold + inventoryGold)) + end +end + +function onCloseImbuementWindow() + resetSlots() +end + +function hide() + g_game.closeImbuingWindow() + imbuingWindow:hide() +end + +function show() + imbuingWindow:show() + imbuingWindow:raise() + imbuingWindow:focus() +end + +function toggle() + if imbuingWindow:isVisible() then + return hide() + end + show() +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otmod new file mode 100644 index 0000000..036a849 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otmod @@ -0,0 +1,9 @@ +Module + name: game_imbuing + description: imbuing + author: Vincent#1766 on discord + website: http://otclient.ovh + sandboxed: true + scripts: [ imbuing ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otui new file mode 100644 index 0000000..9ae7505 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_imbuing/imbuing.otui @@ -0,0 +1,346 @@ +Slot < Button + width: 70 + height: 66 + anchors.verticalCenter: parent.verticalCenter + enabled: false + text-wrap: true + !tooltip: tr('Items can have up to three imbuements slots. This slot is not available for this item.') + +RequiredItem < Panel + width: 66 + height: 90 + + UIItem + id: item + height: 66 + width: 66 + anchors.left: parent.left + anchors.top: parent.top + + FlatLabel + id: count + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + +ItemInformation < Panel + height: 100 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Item Information") + + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.VerticalCenter: parent.VerticalCenter + margin-left: 10 + margin-top: 5 + height: 64 + width: 64 + + Panel + id: slots + width: 240 + height: 66 + margin-top: 5 + anchors.VerticalCenter: parent.VerticalCenter + anchors.top: prev.top + anchors.right: parent.right + + Slot + id: slot0 + !text: tr("Slot 1") + text-align: center + anchors.left: parent.left + + Slot + id: slot1 + !text: tr("Slot 2") + text-align: center + margin-left: 10 + anchors.left: prev.right + + Slot + id: slot2 + !text: tr("Slot 3") + text-align: center + margin-left: 10 + anchors.left: prev.right + + Label + id: selectSlot + margin-right: 15 + anchors.right: slots.left + anchors.VerticalCenter: parent.VerticalCenter + !text: tr("Select Slot:") + +EmptyImbue < Panel + height: 240 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Imbue Empty Slot") + + ComboBox + id: groups + anchors.top: title.bottom + anchors.left: parent.left + anchors.right: parent.HorizontalCenter + margin-right: 3 + margin-top: 5 + isdisabled: true + + ComboBox + id: imbuement + anchors.top: prev.top + anchors.left: groups.right + anchors.right: parent.right + margin-left: 3 + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + height: 85 + + Label + id: info + anchors.bottom: prev.bottom + anchors.left: parent.left + !text: tr('Requires the following astral sources:') + + Label + id: successRate + anchors.top: info.top + anchors.right: parent.right + width: 35 + text-align: right + + Label + id: successRateTitle + anchors.top: info.top + anchors.right: successRate.left + margin-right: 15 + !text: tr('Success Rate:') + + Panel + id: requiredItems + width: 210 + height: 90 + anchors.left: parent.left + anchors.bottom: parent.bottom + + RequiredItem + id: item1 + anchors.left: parent.left + + RequiredItem + id: item2 + margin-left: 10 + anchors.left: prev.right + + RequiredItem + id: item3 + margin-left: 10 + anchors.left: prev.right + + UIButton + id: protection + width: 66 + height: 66 + anchors.top: prev.top + anchors.left: prev.right + image-source: /images/game/imbuing/100percent + image-clip: 0 0 66 66 + margin-left: 15 + !tooltip: ("Bribe the fates! Click here to raise your chance to 100%. For guaranteed success use gold.") + + FlatLabel + id: protectionCost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + + UIWidget + id: horizontalArrow + anchors.left: prev.right + anchors.verticalCenter: requiredItems.verticalCenter + margin-left: 45 + width: 12 + height: 21 + image-source: /images/ui/arrow_horizontal + image-rect: 0 0 12 21 + image-clip: 12 0 12 21 + phantom: false + + UIButton + id: imbue + width: 128 + height: 66 + anchors.top: requiredItems.top + anchors.right: parent.right + image-source: /images/game/imbuing/imbue_empty + image-clip: 0 0 128 66 + margin-left: 15 + !tooltip: tr("Click here to carry out the selected imbuement. This will consume the required astral sources and gold.") + + $pressed: + image-clip: 0 66 128 66 + + FlatLabel + id: cost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + +ClearImbue < Panel + height: 240 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Clear Imbuement") + + ComboBox + id: groups + anchors.top: title.bottom + anchors.left: parent.left + anchors.right: parent.HorizontalCenter + margin-right: 3 + margin-top: 5 + enabled: false + + ComboBox + id: imbuement + anchors.top: prev.top + anchors.left: groups.right + anchors.right: parent.right + margin-left: 3 + enabled: false + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 10 + height: 85 + + Label + id: info + anchors.bottom: prev.bottom + anchors.left: parent.left + !text: tr('Time remaining:') + + Label + id: clearImbuementTitle + anchors.top: info.top + anchors.right: parent.right + !text: tr('Clear imbuement:') + + Panel + id: time + width: 210 + height: 90 + anchors.left: parent.left + anchors.bottom: parent.bottom + + FlatLabel + id: timeRemaining + size: 86 25 + margin-bottom: 20 + text-align: center + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + UIButton + id: clear + width: 128 + height: 66 + anchors.top: time.top + anchors.right: parent.right + image-source: /images/game/imbuing/clear + image-clip: 0 0 128 66 + margin-left: 15 + !tooltip: tr("Your needs have changed? Click here to clear the imbuement from your item for a fee.") + + $pressed: + image-clip: 0 66 128 66 + + FlatLabel + id: cost + margin-top: 5 + text-align: center + anchors.left: prev.left + anchors.right: prev.right + anchors.top: prev.bottom + + +MainWindow + id: imbuingWindow + !text: tr('Imbue Item') + size: 550 430 + background-color: #AAAAAA + @onEscape: modules.game_imbuing.hide() + + ItemInformation + id: itemInfo + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + + EmptyImbue + id: emptyImbue + anchors.left: parent.left + anchors.top: prev.bottom + anchors.right: parent.right + margin-top: 5 + + ClearImbue + id: clearImbue + anchors.left: parent.left + anchors.top: emptyImbue.top + anchors.right: parent.right + + Button + id: close + !text: tr('Close') + width: 50 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_imbuing.hide() + + Label + id: balance + height: 25 + anchors.right: prev.left + anchors.left: parent.left + anchors.bottom: parent.bottom diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.lua new file mode 100644 index 0000000..ca22838 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.lua @@ -0,0 +1,1173 @@ +gameRootPanel = nil +gameMapPanel = nil +gameRightPanels = nil +gameLeftPanels = nil +gameBottomPanel = nil +gameActionPanel = nil +gameLeftActions = nil +logoutButton = nil +mouseGrabberWidget = nil +countWindow = nil +logoutWindow = nil +exitWindow = nil +bottomSplitter = nil +limitedZoom = false +hookedMenuOptions = {} +lastDirTime = g_clock.millis() + +function init() + g_ui.importStyle('styles/countwindow') + + connect(g_game, { + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginAdvice = onLoginAdvice, + }, true) + + -- Call load AFTER game window has been created and + -- resized to a stable state, otherwise the saved + -- settings can get overridden by false onGeometryChange + -- events + connect(g_app, { + onRun = load, + onExit = save + }) + + gameRootPanel = g_ui.displayUI('gameinterface') + gameRootPanel:hide() + gameRootPanel:lower() + gameRootPanel.onGeometryChange = updateStretchShrink + + mouseGrabberWidget = gameRootPanel:getChildById('mouseGrabber') + mouseGrabberWidget.onMouseRelease = onMouseGrabberRelease + mouseGrabberWidget.onTouchRelease = mouseGrabberWidget.onMouseRelease + + bottomSplitter = gameRootPanel:getChildById('bottomSplitter') + gameMapPanel = gameRootPanel:getChildById('gameMapPanel') + gameRightPanels = gameRootPanel:getChildById('gameRightPanels') + gameLeftPanels = gameRootPanel:getChildById('gameLeftPanels') + gameBottomPanel = gameRootPanel:getChildById('gameBottomPanel') + gameActionPanel = gameRootPanel:getChildById('gameActionPanel') + gameLeftActions = gameRootPanel:getChildById('gameLeftActions') + connect(gameLeftPanel, { onVisibilityChange = onLeftPanelVisibilityChange }) + + logoutButton = modules.client_topmenu.addLeftButton('logoutButton', tr('Exit'), + '/images/topbuttons/logout', tryLogout, true) + + + gameRightPanels:addChild(g_ui.createWidget('GameSidePanel')) + + setupLeftActions() + refreshViewMode() + + bindKeys() + + connect(gameMapPanel, { onGeometryChange = updateSize, onVisibleDimensionChange = updateSize }) + connect(g_game, { onMapChangeAwareRange = updateSize }) + + if g_game.isOnline() then + show() + end +end + +function bindKeys() + gameRootPanel:setAutoRepeatDelay(10) + + local lastAction = 0 + g_keyboard.bindKeyPress('Escape', function() + if lastAction + 50 > g_clock.millis() then return end + lastAction = g_clock.millis() + g_game.cancelAttackAndFollow() + end, gameRootPanel) + g_keyboard.bindKeyPress('Ctrl+=', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomIn() end, gameRootPanel) + g_keyboard.bindKeyPress('Ctrl+-', function() if g_game.getFeature(GameNoDebug) then return end gameMapPanel:zoomOut() end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+Q', function() tryLogout(false) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+L', function() tryLogout(false) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+W', function() g_map.cleanTexts() modules.game_textmessage.clearMessages() end, gameRootPanel) +end + +function terminate() + hide() + + hookedMenuOptions = {} + markThing = nil + + + disconnect(g_game, { + onGameStart = onGameStart, + onGameEnd = onGameEnd, + onLoginAdvice = onLoginAdvice + }) + + disconnect(gameMapPanel, { onGeometryChange = updateSize }) + connect(gameMapPanel, { onGeometryChange = updateSize, onVisibleDimensionChange = updateSize }) + + logoutButton:destroy() + gameRootPanel:destroy() +end + +function onGameStart() + refreshViewMode() + show() + + -- open tibia has delay in auto walking + if not g_game.isOfficialTibia() then + g_game.enableFeature(GameForceFirstAutoWalkStep) + else + g_game.disableFeature(GameForceFirstAutoWalkStep) + end +end + +function onGameEnd() + hide() + modules.client_topmenu.getTopMenu():setImageColor('white') +end + +function show() + connect(g_app, { onClose = tryExit }) + modules.client_background.hide() + gameRootPanel:show() + gameRootPanel:focus() + gameMapPanel:followCreature(g_game.getLocalPlayer()) + + updateStretchShrink() + logoutButton:setTooltip(tr('Logout')) + + addEvent(function() + if not limitedZoom or g_game.isGM() then + gameMapPanel:setMaxZoomOut(513) + gameMapPanel:setLimitVisibleRange(false) + else + gameMapPanel:setMaxZoomOut(15) + gameMapPanel:setLimitVisibleRange(true) + end + end) +end + +function hide() + disconnect(g_app, { onClose = tryExit }) + logoutButton:setTooltip(tr('Exit')) + + if logoutWindow then + logoutWindow:destroy() + logoutWindow = nil + end + if exitWindow then + exitWindow:destroy() + exitWindow = nil + end + if countWindow then + countWindow:destroy() + countWindow = nil + end + gameRootPanel:hide() + gameMapPanel:setShader("") + modules.client_background.show() +end + +function save() + local settings = {} + settings.splitterMarginBottom = bottomSplitter:getMarginBottom() + g_settings.setNode('game_interface', settings) +end + +function load() + local settings = g_settings.getNode('game_interface') + if settings then + if settings.splitterMarginBottom then + bottomSplitter:setMarginBottom(settings.splitterMarginBottom) + end + end +end + +function onLoginAdvice(message) + displayInfoBox(tr("For Your Information"), message) +end + +function forceExit() + g_game.cancelLogin() + scheduleEvent(exit, 10) + return true +end + +function tryExit() + if exitWindow then + return true + end + + local exitFunc = function() g_game.safeLogout() forceExit() end + local logoutFunc = function() g_game.safeLogout() exitWindow:destroy() exitWindow = nil end + local cancelFunc = function() exitWindow:destroy() exitWindow = nil end + + exitWindow = displayGeneralBox(tr('Exit'), tr("If you shut down the program, your character might stay in the game.\nClick on 'Logout' to ensure that you character leaves the game properly.\nClick on 'Exit' if you want to exit the program without logging out your character."), + { { text=tr('Force Exit'), callback=exitFunc }, + { text=tr('Logout'), callback=logoutFunc }, + { text=tr('Cancel'), callback=cancelFunc }, + anchor=AnchorHorizontalCenter }, logoutFunc, cancelFunc) + + return true +end + +function tryLogout(prompt) + if type(prompt) ~= "boolean" then + prompt = true + end + if not g_game.isOnline() then + exit() + return + end + + if logoutWindow then + return + end + + local msg, yesCallback + if not g_game.isConnectionOk() then + msg = 'Your connection is failing, if you logout now your character will be still online, do you want to force logout?' + + yesCallback = function() + g_game.forceLogout() + if logoutWindow then + logoutWindow:destroy() + logoutWindow=nil + end + end + else + msg = 'Are you sure you want to logout?' + + yesCallback = function() + g_game.safeLogout() + if logoutWindow then + logoutWindow:destroy() + logoutWindow=nil + end + end + end + + local noCallback = function() + logoutWindow:destroy() + logoutWindow=nil + end + + if prompt then + logoutWindow = displayGeneralBox(tr('Logout'), tr(msg), { + { text=tr('Yes'), callback=yesCallback }, + { text=tr('No'), callback=noCallback }, + anchor=AnchorHorizontalCenter}, yesCallback, noCallback) + else + yesCallback() + end +end + +function updateStretchShrink() + if modules.client_options.getOption('dontStretchShrink') and not alternativeView then + gameMapPanel:setVisibleDimension({ width = 15, height = 11 }) + + -- Set gameMapPanel size to height = 11 * 32 + 2 + bottomSplitter:setMarginBottom(bottomSplitter:getMarginBottom() + (gameMapPanel:getHeight() - 32 * 11) - 10) + end +end + +function onMouseGrabberRelease(self, mousePosition, mouseButton) + if mouseButton == MouseTouch then return end + if selectedThing == nil then return false end + if mouseButton == MouseLeftButton then + local clickedWidget = gameRootPanel:recursiveGetChildByPos(mousePosition, false) + if clickedWidget then + if selectedType == 'use' then + onUseWith(clickedWidget, mousePosition) + elseif selectedType == 'trade' then + onTradeWith(clickedWidget, mousePosition) + end + end + end + + selectedThing = nil + g_mouse.popCursor('target') + self:ungrabMouse() + gameMapPanel:blockNextMouseRelease(true) + return true +end + +function onUseWith(clickedWidget, mousePosition) + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + if selectedThing:isFluidContainer() or selectedThing:isMultiUse() then + if selectedThing:getId() == 3180 or selectedThing:getId() == 3156 then + -- special version for mwall + g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype) + else + g_game.useWith(selectedThing, tile:getTopMultiUseThingEx(clickedWidget:getPositionOffset(mousePosition)), selectedSubtype) + end + else + g_game.useWith(selectedThing, tile:getTopUseThing(), selectedSubtype) + end + end + elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then + g_game.useWith(selectedThing, clickedWidget:getItem(), selectedSubtype) + elseif clickedWidget:getClassName() == 'UICreatureButton' then + local creature = clickedWidget:getCreature() + if creature then + if creature:isMonster() then + g_game.useWith(selectedThing, creature, selectedSubtype) + else + modules.game_textmessage.displayFailureMessage(tr("Sorry, you can't shoot players on the battle window.")) + end + end + end +end + +function onTradeWith(clickedWidget, mousePosition) + if clickedWidget:getClassName() == 'UIGameMap' then + local tile = clickedWidget:getTile(mousePosition) + if tile then + g_game.requestTrade(selectedThing, tile:getTopCreatureEx(clickedWidget:getPositionOffset(mousePosition))) + end + elseif clickedWidget:getClassName() == 'UICreatureButton' then + local creature = clickedWidget:getCreature() + if creature then + g_game.requestTrade(selectedThing, creature) + end + end +end + +function startUseWith(thing, subType) + gameMapPanel:blockNextMouseRelease() + if not thing then return end + if g_ui.isMouseGrabbed() then + if selectedThing then + selectedThing = thing + selectedType = 'use' + end + return + end + selectedType = 'use' + selectedThing = thing + selectedSubtype = subType or 0 + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') +end + +function startTradeWith(thing) + if not thing then return end + if g_ui.isMouseGrabbed() then + if selectedThing then + selectedThing = thing + selectedType = 'trade' + end + return + end + selectedType = 'trade' + selectedThing = thing + mouseGrabberWidget:grabMouse() + g_mouse.pushCursor('target') +end + +function isMenuHookCategoryEmpty(category) + if category then + for _,opt in pairs(category) do + if opt then return false end + end + end + return true +end + +function addMenuHook(category, name, callback, condition, shortcut) + if not hookedMenuOptions[category] then + hookedMenuOptions[category] = {} + end + hookedMenuOptions[category][name] = { + callback = callback, + condition = condition, + shortcut = shortcut + } +end + +function removeMenuHook(category, name) + if not name then + hookedMenuOptions[category] = {} + else + hookedMenuOptions[category][name] = nil + end +end + +function createThingMenu(menuPosition, lookThing, useThing, creatureThing) + if not g_game.isOnline() then return end + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + + local classic = modules.client_options.getOption('classicControl') + local shortcut = nil + + if not classic and not g_app.isMobile() then shortcut = '(Shift)' else shortcut = nil end + if lookThing then + menu:addOption(tr('Look'), function() g_game.look(lookThing) end, shortcut) + end + + if not classic and not g_app.isMobile() then shortcut = '(Ctrl)' else shortcut = nil end + if useThing then + if useThing:isContainer() then + if useThing:getParentContainer() then + menu:addOption(tr('Open'), function() g_game.open(useThing, useThing:getParentContainer()) end, shortcut) + menu:addOption(tr('Open in new window'), function() g_game.open(useThing) end) + else + menu:addOption(tr('Open'), function() g_game.open(useThing) end, shortcut) + end + else + if useThing:isMultiUse() then + menu:addOption(tr('Use with ...'), function() startUseWith(useThing) end, shortcut) + else + menu:addOption(tr('Use'), function() g_game.use(useThing) end, shortcut) + end + end + + if useThing:isRotateable() then + menu:addOption(tr('Rotate'), function() g_game.rotate(useThing) end) + end + if useThing:isWrapable() then + menu:addOption(tr('Wrap'), function() g_game.wrap(useThing) end) + end + if useThing:isUnwrapable() then + menu:addOption(tr('Unwrap'), function() g_game.wrap(useThing) end) + end + + if g_game.getFeature(GameBrowseField) and useThing:getPosition().x ~= 0xffff then + menu:addOption(tr('Browse Field'), function() g_game.browseField(useThing:getPosition()) end) + end + end + + if lookThing and not lookThing:isCreature() and not lookThing:isNotMoveable() and lookThing:isPickupable() then + menu:addSeparator() + menu:addOption(tr('Trade with ...'), function() startTradeWith(lookThing) end) + end + + if lookThing then + local parentContainer = lookThing:getParentContainer() + if parentContainer and parentContainer:hasParent() then + menu:addOption(tr('Move up'), function() g_game.moveToParentContainer(lookThing, lookThing:getCount()) end) + end + end + + if creatureThing then + local localPlayer = g_game.getLocalPlayer() + menu:addSeparator() + + if creatureThing:isLocalPlayer() then + menu:addOption(tr('Set Outfit'), function() g_game.requestOutfit() end) + + if g_game.getFeature(GamePlayerMounts) then + if not localPlayer:isMounted() then + menu:addOption(tr('Mount'), function() localPlayer:mount() end) + else + menu:addOption(tr('Dismount'), function() localPlayer:dismount() end) + end + end + + if g_game.getFeature(GamePrey) and modules.game_prey then + menu:addOption(tr('Open Prey Dialog'), function() modules.game_prey.show() end) + end + + if creatureThing:isPartyMember() then + if creatureThing:isPartyLeader() then + if creatureThing:isPartySharedExperienceActive() then + menu:addOption(tr('Disable Shared Experience'), function() g_game.partyShareExperience(false) end) + else + menu:addOption(tr('Enable Shared Experience'), function() g_game.partyShareExperience(true) end) + end + end + menu:addOption(tr('Leave Party'), function() g_game.partyLeave() end) + end + + else + local localPosition = localPlayer:getPosition() + if not classic and not g_app.isMobile() then shortcut = '(Alt)' else shortcut = nil end + if creatureThing:getPosition().z == localPosition.z then + if g_game.getAttackingCreature() ~= creatureThing then + menu:addOption(tr('Attack'), function() g_game.attack(creatureThing) end, shortcut) + else + menu:addOption(tr('Stop Attack'), function() g_game.cancelAttack() end, shortcut) + end + + if g_game.getFollowingCreature() ~= creatureThing then + menu:addOption(tr('Follow'), function() g_game.follow(creatureThing) end) + else + menu:addOption(tr('Stop Follow'), function() g_game.cancelFollow() end) + end + end + + if creatureThing:isPlayer() then + menu:addSeparator() + local creatureName = creatureThing:getName() + menu:addOption(tr('Message to %s', creatureName), function() g_game.openPrivateChannel(creatureName) end) + if modules.game_console.getOwnPrivateTab() then + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(creatureName) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(creatureName) end) -- [TODO] must be removed after message's popup labels been implemented + end + if not localPlayer:hasVip(creatureName) then + menu:addOption(tr('Add to VIP list'), function() g_game.addVip(creatureName) end) + end + + if modules.game_console.isIgnored(creatureName) then + menu:addOption(tr('Unignore') .. ' ' .. creatureName, function() modules.game_console.removeIgnoredPlayer(creatureName) end) + else + menu:addOption(tr('Ignore') .. ' ' .. creatureName, function() modules.game_console.addIgnoredPlayer(creatureName) end) + end + + local localPlayerShield = localPlayer:getShield() + local creatureShield = creatureThing:getShield() + + if localPlayerShield == ShieldNone or localPlayerShield == ShieldWhiteBlue then + if creatureShield == ShieldWhiteYellow then + menu:addOption(tr('Join %s\'s Party', creatureThing:getName()), function() g_game.partyJoin(creatureThing:getId()) end) + else + menu:addOption(tr('Invite to Party'), function() g_game.partyInvite(creatureThing:getId()) end) + end + elseif localPlayerShield == ShieldWhiteYellow then + if creatureShield == ShieldWhiteBlue then + menu:addOption(tr('Revoke %s\'s Invitation', creatureThing:getName()), function() g_game.partyRevokeInvitation(creatureThing:getId()) end) + end + elseif localPlayerShield == ShieldYellow or localPlayerShield == ShieldYellowSharedExp or localPlayerShield == ShieldYellowNoSharedExpBlink or localPlayerShield == ShieldYellowNoSharedExp then + if creatureShield == ShieldWhiteBlue then + menu:addOption(tr('Revoke %s\'s Invitation', creatureThing:getName()), function() g_game.partyRevokeInvitation(creatureThing:getId()) end) + elseif creatureShield == ShieldBlue or creatureShield == ShieldBlueSharedExp or creatureShield == ShieldBlueNoSharedExpBlink or creatureShield == ShieldBlueNoSharedExp then + menu:addOption(tr('Pass Leadership to %s', creatureThing:getName()), function() g_game.partyPassLeadership(creatureThing:getId()) end) + else + menu:addOption(tr('Invite to Party'), function() g_game.partyInvite(creatureThing:getId()) end) + end + end + end + end + + if modules.game_ruleviolation.hasWindowAccess() and creatureThing:isPlayer() then + menu:addSeparator() + menu:addOption(tr('Rule Violation'), function() modules.game_ruleviolation.show(creatureThing:getName()) end) + end + + menu:addSeparator() + menu:addOption(tr('Copy Name'), function() g_window.setClipboardText(creatureThing:getName()) end) + end + + -- hooked menu options + for _,category in pairs(hookedMenuOptions) do + if not isMenuHookCategoryEmpty(category) then + menu:addSeparator() + for name,opt in pairs(category) do + if opt and opt.condition(menuPosition, lookThing, useThing, creatureThing) then + menu:addOption(name, function() opt.callback(menuPosition, + lookThing, useThing, creatureThing) end, opt.shortcut) + end + end + end + end + + if g_game.getFeature(GameBot) and useThing and useThing:isItem() then + menu:addSeparator() + if useThing:getSubType() > 1 then + menu:addOption("ID: " .. useThing:getId() .. " SubType: " .. useThing:getSubType(), function() end) + else + menu:addOption("ID: " .. useThing:getId(), function() end) + end + end + + menu:display(menuPosition) +end + +function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature, marking) + local keyboardModifiers = g_keyboard.getModifiers() + + if g_app.isMobile() then + if mouseButton == MouseRightButton then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + end + if mouseButton ~= MouseLeftButton and mouseButton ~= MouseTouch2 and mouseButton ~= MouseTouch3 then + return false + end + local action = getLeftAction() + if action == "look" then + if lookThing then + resetLeftActions() + g_game.look(lookThing) + return true + end + return true + elseif action == "use" then + if useThing then + resetLeftActions() + if useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + else + g_game.open(useThing) + end + return true + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + end + return true + elseif action == "attack" then + if attackCreature and attackCreature ~= player then + resetLeftActions() + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + resetLeftActions() + g_game.attack(creatureThing) + return true + end + return true + elseif action == "follow" then + if attackCreature and attackCreature ~= player then + resetLeftActions() + g_game.follow(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + resetLeftActions() + g_game.follow(creatureThing) + return true + end + return true + elseif not autoWalkPos and useThing then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + end + elseif not modules.client_options.getOption('classicControl') then + if keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + elseif lookThing and keyboardModifiers == KeyboardShiftModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.look(lookThing) + return true + elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + if useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + else + g_game.open(useThing) + end + return true + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + return true + elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(creatureThing) + return true + end + else -- classic control + if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then + local player = g_game.getLocalPlayer() + if attackCreature and attackCreature ~= player then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing ~= player and creatureThing:getPosition().z == autoWalkPos.z then + g_game.attack(creatureThing) + return true + elseif useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) + return true + else + g_game.open(useThing) + return true + end + elseif useThing:isMultiUse() then + startUseWith(useThing) + return true + else + g_game.use(useThing) + return true + end + return true + elseif lookThing and keyboardModifiers == KeyboardShiftModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.look(lookThing) + return true + elseif lookThing and ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + g_game.look(lookThing) + return true + elseif useThing and keyboardModifiers == KeyboardCtrlModifier and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + createThingMenu(menuPosition, lookThing, useThing, creatureThing) + return true + elseif attackCreature and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(attackCreature) + return true + elseif creatureThing and creatureThing:getPosition().z == autoWalkPos.z and g_keyboard.isAltPressed() and (mouseButton == MouseLeftButton or mouseButton == MouseRightButton) then + g_game.attack(creatureThing) + return true + end + end + + local player = g_game.getLocalPlayer() + player:stopAutoWalk() + + if autoWalkPos and keyboardModifiers == KeyboardNoModifier and (mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3) then + local autoWalkTile = g_map.getTile(autoWalkPos) + if autoWalkTile and not autoWalkTile:isWalkable(true) then + modules.game_textmessage.displayFailureMessage(tr('Sorry, not possible.')) + return false + end + player:autoWalk(autoWalkPos) + return true + end + + return false +end + +function moveStackableItem(item, toPos) + if countWindow then + return + end + if g_keyboard.isCtrlPressed() then + g_game.move(item, toPos, item:getCount()) + return + elseif g_keyboard.isShiftPressed() then + g_game.move(item, toPos, 1) + return + end + local count = item:getCount() + + countWindow = g_ui.createWidget('CountWindow', rootWidget) + local itembox = countWindow:getChildById('item') + local scrollbar = countWindow:getChildById('countScrollBar') + itembox:setItemId(item:getId()) + itembox:setItemCount(count) + scrollbar:setMaximum(count) + scrollbar:setMinimum(1) + scrollbar:setValue(count) + + local spinbox = countWindow:getChildById('spinBox') + spinbox:setMaximum(count) + spinbox:setMinimum(0) + spinbox:setValue(0) + spinbox:hideButtons() + spinbox:focus() + spinbox.firstEdit = true + + local spinBoxValueChange = function(self, value) + spinbox.firstEdit = false + scrollbar:setValue(value) + end + spinbox.onValueChange = spinBoxValueChange + + local check = function() + if spinbox.firstEdit then + spinbox:setValue(spinbox:getMaximum()) + spinbox.firstEdit = false + end + end + local okButton = countWindow:getChildById('buttonOk') + local moveFunc = function() + g_game.move(item, toPos, itembox:getItemCount()) + okButton:getParent():destroy() + countWindow = nil + end + local cancelButton = countWindow:getChildById('buttonCancel') + local cancelFunc = function() + cancelButton:getParent():destroy() + countWindow = nil + end + + + g_keyboard.bindKeyPress("Up", function() check() spinbox:up() end, spinbox) + g_keyboard.bindKeyPress("Down", function() check() spinbox:down() end, spinbox) + g_keyboard.bindKeyPress("Right", function() check() spinbox:up() end, spinbox) + g_keyboard.bindKeyPress("Left", function() check() spinbox:down() end, spinbox) + g_keyboard.bindKeyPress("PageUp", function() check() spinbox:setValue(spinbox:getValue()+10) end, spinbox) + g_keyboard.bindKeyPress("PageDown", function() check() spinbox:setValue(spinbox:getValue()-10) end, spinbox) + g_keyboard.bindKeyPress("Enter", function() moveFunc() end, spinbox) + + scrollbar.onValueChange = function(self, value) + itembox:setItemCount(value) + spinbox.onValueChange = nil + spinbox:setValue(value) + spinbox.onValueChange = spinBoxValueChange + end + countWindow.onEnter = moveFunc + countWindow.onEscape = cancelFunc + + okButton.onClick = moveFunc + cancelButton.onClick = cancelFunc +end + +function getRootPanel() + return gameRootPanel +end + +function getMapPanel() + return gameMapPanel +end + +function getRightPanel() + if gameRightPanels:getChildCount() == 0 then + addRightPanel() + end + return gameRightPanels:getChildByIndex(-1) +end + +function getLeftPanel() + if gameLeftPanels:getChildCount() >= 1 then + return gameLeftPanels:getChildByIndex(-1) + end + return getRightPanel() +end + +function getContainerPanel() + local containerPanel = g_settings.getNumber("containerPanel") + if containerPanel >= 5 then + containerPanel = containerPanel - 4 + return gameRightPanels:getChildByIndex(math.min(containerPanel, gameRightPanels:getChildCount())) + end + if gameLeftPanels:getChildCount() == 0 then + return getRightPanel() + end + return gameLeftPanels:getChildByIndex(math.min(containerPanel, gameLeftPanels:getChildCount())) +end + +local function addRightPanel() + if gameRightPanels:getChildCount() >= 4 then + return + end + local panel = g_ui.createWidget('GameSidePanel') + panel:setId("rightPanel" .. (gameRightPanels:getChildCount() + 1)) + gameRightPanels:insertChild(1, panel) +end + +local function addLeftPanel() + if gameLeftPanels:getChildCount() >= 4 then + return + end + local panel = g_ui.createWidget('GameSidePanel') + panel:setId("leftPanel" .. (gameLeftPanels:getChildCount() + 1)) + gameLeftPanels:addChild(panel) +end + +local function removeRightPanel() + if gameRightPanels:getChildCount() <= 1 then + return + end + local panel = gameRightPanels:getChildByIndex(1) + panel:moveTo(gameRightPanels:getChildByIndex(2)) + gameRightPanels:removeChild(panel) +end + +local function removeLeftPanel() + if gameLeftPanels:getChildCount() == 0 then + return + end + local panel = gameLeftPanels:getChildByIndex(-1) + if gameLeftPanels:getChildCount() >= 2 then + panel:moveTo(gameLeftPanels:getChildByIndex(-2)) + else + panel:moveTo(gameRightPanels:getChildByIndex(1)) + end + gameLeftPanels:removeChild(panel) +end + +function getBottomPanel() + return gameBottomPanel +end + +function getActionPanel() + return gameActionPanel +end + +function refreshViewMode() + local classic = g_settings.getBoolean("classicView") and not g_app.isMobile() + local rightPanels = g_settings.getNumber("rightPanels") - gameRightPanels:getChildCount() + local leftPanels = g_settings.getNumber("leftPanels") - 1 - gameLeftPanels:getChildCount() + + while rightPanels ~= 0 do + if rightPanels > 0 then + addRightPanel() + rightPanels = rightPanels - 1 + else + removeRightPanel() + rightPanels = rightPanels + 1 + end + end + while leftPanels ~= 0 do + if leftPanels > 0 then + addLeftPanel() + leftPanels = leftPanels - 1 + else + removeLeftPanel() + leftPanels = leftPanels + 1 + end + end + + if not g_game.isOnline() then + return + end + + local minimumWidth = (g_settings.getNumber("rightPanels") + g_settings.getNumber("leftPanels") - 1) * 200 + 200 + minimumWidth = math.max(minimumWidth, g_resources.getLayout() == "mobile" and 640 or 800) + g_window.setMinimumSize({ width = minimumWidth, height = (g_resources.getLayout() == "mobile" and 360 or 600)}) + if g_window.getWidth() < minimumWidth then + local oldPos = g_window.getPosition() + local size = { width = minimumWidth, height = g_window.getHeight() } + g_window.resize(size) + g_window.move(oldPos) + end + + for i=1,gameRightPanels:getChildCount()+gameLeftPanels:getChildCount() do + local panel + if i > gameRightPanels:getChildCount() then + panel = gameLeftPanels:getChildByIndex(i - gameRightPanels:getChildCount()) + else + panel = gameRightPanels:getChildByIndex(i) + end + if classic then + panel:setImageColor('white') + else + panel:setImageColor('alpha') + end + end + + if classic then + gameRightPanels:setMarginTop(0) + gameLeftPanels:setMarginTop(0) + gameMapPanel:setMarginLeft(0) + gameMapPanel:setMarginRight(0) + gameMapPanel:setMarginTop(0) + end + + gameMapPanel:setVisibleDimension({ width = 15, height = 11 }) + + if classic then + g_game.changeMapAwareRange(19, 15) + gameMapPanel:addAnchor(AnchorLeft, 'gameLeftPanels', AnchorRight) + gameMapPanel:addAnchor(AnchorRight, 'gameRightPanels', AnchorLeft) + gameMapPanel:addAnchor(AnchorBottom, 'gameActionPanel', AnchorTop) + gameMapPanel:setKeepAspectRatio(true) + gameMapPanel:setLimitVisibleRange(false) + gameMapPanel:setZoom(11) + gameMapPanel:setOn(false) -- frame + + modules.client_topmenu.getTopMenu():setImageColor('white') + + if modules.game_console then + modules.game_console.switchMode(false) + end + else + g_game.changeMapAwareRange(31, 21) + gameMapPanel:fill('parent') + gameMapPanel:setKeepAspectRatio(false) + gameMapPanel:setLimitVisibleRange(false) + gameMapPanel:setOn(true) + if g_app.isMobile() then + gameMapPanel:setZoom(11) + else + gameMapPanel:setZoom(15) + end + + modules.client_topmenu.getTopMenu():setImageColor('#ffffff66') + if g_app.isMobile() then + gameMapPanel:setMarginTop(-32) + end + if modules.game_console then + modules.game_console.switchMode(true) + end + end + if modules.game_actionbar then + modules.game_actionbar.switchMode(not classic) + end + + if g_settings.getBoolean("cacheMap") then + g_game.enableFeature(GameBiggerMapCache) + end + + updateSize() +end + +function limitZoom() + limitedZoom = true +end + +function updateSize() + if g_app.isMobile() then return end + + local classic = g_settings.getBoolean("classicView") + local height = gameMapPanel:getHeight() + local width = gameMapPanel:getWidth() + + if not classic then + local rheight = gameRootPanel:getHeight() + local rwidth = gameRootPanel:getWidth() + + local dimenstion = gameMapPanel:getVisibleDimension() + local zoom = gameMapPanel:getZoom() + local awareRange = g_map.getAwareRange() + local dheight = dimenstion.height + local dwidth = dimenstion.width + local tileSize = rheight / dheight + local maxWidth = tileSize * (awareRange.width + 1) + if g_game.getFeature(GameChangeMapAwareRange) and g_game.getFeature(GameNewWalking) then + maxWidth = tileSize * (awareRange.width - 1) + end + gameMapPanel:setMarginTop(-tileSize) + if modules.game_stats then + modules.game_stats.ui:setMarginTop(tileSize) + end + if g_settings.getBoolean("cacheMap") then + gameMapPanel:setMarginLeft(0) + gameMapPanel:setMarginRight(0) + else + local margin = math.max(0, math.floor((rwidth - maxWidth) / 2)) + gameMapPanel:setMarginLeft(margin) + gameMapPanel:setMarginRight(margin) + end + + if modules.game_bot then + for i, child in ipairs(gameMapPanel:getChildren()) do + if child.botIcon and child.onGeometryChange then + child.onGeometryChange(child) + end + end + end + else + if modules.game_stats then + modules.game_stats.ui:setMarginTop(0) + end + end + + --[[ + local maxWidth = math.floor(height * 2) + local extraMargin = 0 + if width >= maxWidth then + extraMargin = math.ceil((width - maxWidth) / 2) + end + local bottomMaxWidth = 1200 -- something broken, it's not pixels + local bottomMargin = 0 + if width > bottomMaxWidth then + bottomMargin = math.ceil((width - bottomMaxWidth) / 2) + end + gameMapPanel:setMarginLeft(extraMargin) + gameMapPanel:setMarginRight(extraMargin) ]] +end + +function setupLeftActions() + if not g_app.isMobile() then return end + for _, widget in ipairs(gameLeftActions:getChildren()) do + widget.image:setChecked(false) + widget.lastClicked = 0 + widget.onClick = function() + if widget.image:isChecked() then + widget.image:setChecked(false) + if widget.doubleClickAction and widget.lastClicked + 200 > g_clock.millis() then + widget.doubleClickAction() + end + return + end + resetLeftActions() + widget.image:setChecked(true) + widget.lastClicked = g_clock.millis() + end + end + if gameLeftActions.use then + gameLeftActions.use.doubleClickAction = function() + local player = g_game.getLocalPlayer() + local dir = player:getDirection() + local usePos = player:getPrewalkingPosition(true) + if dir == North then + usePos.y = usePos.y - 1 + elseif dir == East then + usePos.x = usePos.x + 1 + elseif dir == South then + usePos.y = usePos.y + 1 + elseif dir == West then + usePos.x = usePos.x - 1 + end + local tile = g_map.getTile(usePos) + if not tile then return end + local thing = tile:getTopUseThing() + if thing then + g_game.use(thing) + end + end + end + if gameLeftActions.attack then + gameLeftActions.attack.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.attack(child.creature) + else + g_game.attack(nil) + end + end + end + if gameLeftActions.follow then + gameLeftActions.follow.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or not child:isOn()) then + child = nil + end + if child then + g_game.follow(child.creature) + else + g_game.follow(nil) + end + end + end + if gameLeftActions.look then + gameLeftActions.look.doubleClickAction = function() + local battlePanel = modules.game_battle.battlePanel + local attackedCreature = g_game.getAttackingCreature() + local child = battlePanel:getFirstChild() + if child and (not child.creature or child:isHidden()) then + child = nil + end + if child then + g_game.look(child.creature) + end + end + end + if not gameLeftActions.chat then return end + gameLeftActions.chat.onClick = function() + if gameBottomPanel:getHeight() <= 5 then + gameBottomPanel:setHeight(90) + else + gameBottomPanel:setHeight(0) + end + end +end + +function resetLeftActions() + for _, widget in ipairs(gameLeftActions:getChildren()) do + widget.image:setChecked(false) + widget.lastClicked = 0 + end +end + +function getLeftAction() + for _, widget in ipairs(gameLeftActions:getChildren()) do + if widget.image:isChecked() then + return widget:getId() + end + end + return "" +end + +function isChatVisible() + return gameBottomPanel:getHeight() >= 5 +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.otui new file mode 100644 index 0000000..533ceb5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/gameinterface.otui @@ -0,0 +1,161 @@ +GameSidePanel < UIMiniWindowContainer + image-source: /images/ui/panel_side + image-border: 4 + padding: 3 + padding-top: 0 + width: 198 + focusable: false + on: true + layout: + type: verticalBox + $mobile: + padding: 0 + width: 200 + + +GameMapPanel < UIGameMap + padding: 4 + image-source: /images/ui/panel_map + image-border: 4 + + $on: + padding: 0 + +GameAction < UIButton + size: 64 64 + phantom: false + + UIButton + id: image + size: 48 48 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + phantom: true + $checked: + opacity: 1.0 + background: #00FF0033 + $!checked: + opacity: 0.6 + background: alpha + + +UIWidget + id: gameRootPanel + anchors.fill: parent + + GameMapPanel + id: gameMapPanel + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.top: parent.top + anchors.bottom: gameBottomPanel.top + focusable: false + + Panel + id: gameLeftActions + focusable: false + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 64 + + $!mobile: + visible: false + width: 0 + + layout: + type: verticalBox + fit-children: true + + GameAction + id: use + @onSetup: self.image:setImageSource("/images/game/mobile/use") + GameAction + id: attack + @onSetup: self.image:setImageSource("/images/game/mobile/attack") + GameAction + id: follow + @onSetup: self.image:setImageSource("/images/game/mobile/follow") + GameAction + id: look + @onSetup: self.image:setImageSource("/images/game/mobile/look") + GameAction + id: chat + @onSetup: self.image:setImageSource("/images/game/mobile/chat") + + Panel + id: gameLeftPanels + focusable: false + anchors.top: parent.top + anchors.bottom: parent.bottom + $!mobile: + anchors.left: parent.left + $mobile: + anchors.left: gameLeftActions.right + + layout: + type: horizontalBox + fit-children: true + spacing: -1 + + Panel + id: gameRightPanels + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + focusable: false + layout: + type: horizontalBox + fit-children: true + spacing: -1 + + Splitter + id: bottomSplitter + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: parent.bottom + height: 5 + relative-margin: bottom + margin-bottom: 150 + @canUpdateMargin: function(self, newMargin) if modules.client_options.getOption('dontStretchShrink') then return self:getMarginBottom() end return math.max(math.min(newMargin, self:getParent():getHeight() - 150), 80) end + @onGeometryChange: function(self) self:setMarginBottom(math.min(math.max(self:getParent():getHeight() - 150, 80), self:getMarginBottom())) end + + $mobile: + visible: false + + Panel + id: gameActionPanel + phantom: true + focusable: false + + $!mobile: + anchors.left: bottomSplitter.left + anchors.right: bottomSplitter.right + anchors.top: bottomSplitter.top + margin-top: 3 + + $mobile: + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: gameBottomPanel.top + + layout: + type: verticalBox + fit-children: true + + Panel + id: gameBottomPanel + $!mobile: + anchors.left: gameActionPanel.left + anchors.right: gameActionPanel.right + anchors.top: gameActionPanel.bottom + anchors.bottom: parent.bottom + + $mobile: + anchors.left: gameLeftPanels.right + anchors.right: gameRightPanels.left + anchors.bottom: parent.bottom + + UIWidget + id: mouseGrabber + focusable: false + visible: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/interface.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/interface.otmod new file mode 100644 index 0000000..5a649be --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/interface.otmod @@ -0,0 +1,44 @@ +Module + name: game_interface + description: Create the game interface, where the ingame stuff starts + author: OTClient team + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ widgets/uigamemap, gameinterface ] + load-later: + - game_buttons + - game_hotkeys + - game_questlog + - game_textmessage + - game_console + - game_outfit + - game_healthinfo + - game_skills + - game_inventory + - game_containers + - game_viplist + - game_battle + - game_minimap + - game_npctrade + - game_textwindow + - game_playertrade + - game_bugreport + - game_playerdeath + - game_playermount + - game_ruleviolation + - game_market + - game_spelllist + - game_cooldown + - game_modaldialog + - game_unjustifiedpoints + - game_walking + - game_shop + - game_itemselector + - client_textedit + - game_actionbar + - game_prey + - game_imbuing + - game_stats + - game_shaders + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/styles/countwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/styles/countwindow.otui new file mode 100644 index 0000000..6cef134 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/styles/countwindow.otui @@ -0,0 +1,53 @@ +CountWindow < MainWindow + id: countWindow + !text: tr('Move Stackable Item') + size: 196 90 + + SpinBox + id: spinBox + anchors.left: parent.left + anchors.top: parent.top + width: 1 + height: 1 + phantom: true + margin-top: 2 + focusable: true + + Item + id: item + anchors.left: parent.left + anchors.top: parent.top + margin-top: 2 + margin-left: -4 + focusable: false + virtual: true + + HorizontalScrollBar + id: countScrollBar + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 10 + margin-top: -2 + focusable: false + + Button + id: buttonCancel + !text: tr('Cancel') + height: 20 + anchors.left: countScrollBar.horizontalCenter + anchors.right: countScrollBar.right + anchors.top: countScrollBar.bottom + margin-top: 7 + focusable: false + + Button + id: buttonOk + !text: tr('Ok') + height: 20 + anchors.right: countScrollBar.horizontalCenter + anchors.left: countScrollBar.left + anchors.top: countScrollBar.bottom + margin-top: 7 + margin-right: 6 + focusable: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/widgets/uigamemap.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/widgets/uigamemap.lua new file mode 100644 index 0000000..67d9bf9 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_interface/widgets/uigamemap.lua @@ -0,0 +1,196 @@ +UIGameMap = extends(UIMap, "UIGameMap") + +function UIGameMap.create() + local gameMap = UIGameMap.internalCreate() + gameMap:setKeepAspectRatio(true) + gameMap:setVisibleDimension({width = 15, height = 11}) + gameMap:setDrawLights(true) + gameMap.markedThing = nil + gameMap.blockNextRelease = 0 + gameMap:updateMarkedCreature() + return gameMap +end + +function UIGameMap:onDestroy() + if self.updateMarkedCreatureEvent then + removeEvent(self.updateMarkedCreatureEvent) + end +end + +function UIGameMap:markThing(thing, color) + if self.markedThing == thing then + return + end + if self.markedThing then + self.markedThing:setMarked('') + end + + self.markedThing = thing + if self.markedThing and g_settings.getBoolean('highlightThingsUnderCursor') then + self.markedThing:setMarked(color) + end +end + +function UIGameMap:onDragEnter(mousePos) + local tile = self:getTile(mousePos) + if not tile then return false end + + local thing = tile:getTopMoveThing() + if not thing then return false end + + self.currentDragThing = thing + + g_mouse.pushCursor('target') + self.allowNextRelease = false + return true +end + +function UIGameMap:onDragLeave(droppedWidget, mousePos) + self.currentDragThing = nil + self.hoveredWho = nil + g_mouse.popCursor('target') + return true +end + +function UIGameMap:onDrop(widget, mousePos) + if not self:canAcceptDrop(widget, mousePos) then return false end + + local tile = self:getTile(mousePos) + if not tile then return false end + + local thing = widget.currentDragThing + local toPos = tile:getPosition() + + local thingPos = thing:getPosition() + if thingPos.x == toPos.x and thingPos.y == toPos.y and thingPos.z == toPos.z then return false end + + if thing:isItem() and thing:getCount() > 1 then + modules.game_interface.moveStackableItem(thing, toPos) + else + g_game.move(thing, toPos, 1) + end + + return true +end + +function UIGameMap:onMouseMove(mousePos, mouseMoved) + self.mousePos = mousePos + return false +end + +function UIGameMap:onDragMove(mousePos, mouseMoved) + self.mousePos = mousePos + return false +end + +function UIGameMap:updateMarkedCreature() + self.updateMarkedCreatureEvent = scheduleEvent(function() self:updateMarkedCreature() end, 100) + if self.mousePos and g_game.isOnline() then + self.markingMouseRelease = true + self:onMouseRelease(self.mousePos, MouseRightButton) + self.markingMouseRelease = false + end +end + +function UIGameMap:onMousePress() + if not self:isDragging() and self.blockNextRelease < g_clock.millis() then + self.allowNextRelease = true + self.markingMouseRelease = false + end +end + +function UIGameMap:blockNextMouseRelease(postAction) + self.allowNextRelease = false + if postAction then + self.blockNextRelease = g_clock.millis() + 150 + else + self.blockNextRelease = g_clock.millis() + 250 + end +end + +function UIGameMap:onMouseRelease(mousePosition, mouseButton) + if not self.allowNextRelease and not self.markingMouseRelease then + return true + end + local autoWalkPos = self:getPosition(mousePosition) + local positionOffset = self:getPositionOffset(mousePosition) + + -- happens when clicking outside of map boundaries + if not autoWalkPos then + if self.markingMouseRelease then + self:markThing(nil) + end + return false + end + + local localPlayerPos = g_game.getLocalPlayer():getPosition() + if autoWalkPos.z ~= localPlayerPos.z then + local dz = autoWalkPos.z - localPlayerPos.z + autoWalkPos.x = autoWalkPos.x + dz + autoWalkPos.y = autoWalkPos.y + dz + autoWalkPos.z = localPlayerPos.z + end + + local lookThing + local useThing + local creatureThing + local multiUseThing + local attackCreature + + local tile = self:getTile(mousePosition) + if tile then + lookThing = tile:getTopLookThingEx(positionOffset) + useThing = tile:getTopUseThing() + creatureThing = tile:getTopCreatureEx(positionOffset) + end + + local autoWalkTile = g_map.getTile(autoWalkPos) + if autoWalkTile then + attackCreature = autoWalkTile:getTopCreatureEx(positionOffset) + end + + if self.markingMouseRelease then + if attackCreature then + self:markThing(attackCreature, 'yellow') + elseif creatureThing then + self:markThing(creatureThing, 'yellow') + elseif useThing and not useThing:isGround() then + self:markThing(useThing, 'yellow') + elseif lookThing and not lookThing:isGround() then + self:markThing(lookThing, 'yellow') + else + self:markThing(nil, '') + end + return + end + + local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, attackCreature, self.markingMouseRelease) + if ret then + self.allowNextRelease = false + end + + return ret +end + +function UIGameMap:onTouchRelease(mousePosition, mouseButton) + if mouseButton ~= MouseTouch then + return self:onMouseRelease(mousePosition, mouseButton) + end +end + +function UIGameMap:canAcceptDrop(widget, mousePos) + if not widget or not widget.currentDragThing then return false end + + local children = rootWidget:recursiveGetChildrenByPos(mousePos) + for i=1,#children do + local child = children[i] + if child == self then + return true + elseif not child:isPhantom() then + return false + end + end + + error('Widget ' .. self:getId() .. ' not in drop list.') + return false +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.lua new file mode 100644 index 0000000..b50a680 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.lua @@ -0,0 +1,487 @@ +Icons = {} +Icons[PlayerStates.Poison] = { tooltip = tr('You are poisoned'), path = '/images/game/states/poisoned', id = 'condition_poisoned' } +Icons[PlayerStates.Burn] = { tooltip = tr('You are burning'), path = '/images/game/states/burning', id = 'condition_burning' } +Icons[PlayerStates.Energy] = { tooltip = tr('You are electrified'), path = '/images/game/states/electrified', id = 'condition_electrified' } +Icons[PlayerStates.Drunk] = { tooltip = tr('You are drunk'), path = '/images/game/states/drunk', id = 'condition_drunk' } +Icons[PlayerStates.ManaShield] = { tooltip = tr('You are protected by a magic shield'), path = '/images/game/states/magic_shield', id = 'condition_magic_shield' } +Icons[PlayerStates.Paralyze] = { tooltip = tr('You are paralysed'), path = '/images/game/states/slowed', id = 'condition_slowed' } +Icons[PlayerStates.Haste] = { tooltip = tr('You are hasted'), path = '/images/game/states/haste', id = 'condition_haste' } +Icons[PlayerStates.Swords] = { tooltip = tr('You may not logout during a fight'), path = '/images/game/states/logout_block', id = 'condition_logout_block' } +Icons[PlayerStates.Drowning] = { tooltip = tr('You are drowning'), path = '/images/game/states/drowning', id = 'condition_drowning' } +Icons[PlayerStates.Freezing] = { tooltip = tr('You are freezing'), path = '/images/game/states/freezing', id = 'condition_freezing' } +Icons[PlayerStates.Dazzled] = { tooltip = tr('You are dazzled'), path = '/images/game/states/dazzled', id = 'condition_dazzled' } +Icons[PlayerStates.Cursed] = { tooltip = tr('You are cursed'), path = '/images/game/states/cursed', id = 'condition_cursed' } +Icons[PlayerStates.PartyBuff] = { tooltip = tr('You are strengthened'), path = '/images/game/states/strengthened', id = 'condition_strengthened' } +Icons[PlayerStates.PzBlock] = { tooltip = tr('You may not logout or enter a protection zone'), path = '/images/game/states/protection_zone_block', id = 'condition_protection_zone_block' } +Icons[PlayerStates.Pz] = { tooltip = tr('You are within a protection zone'), path = '/images/game/states/protection_zone', id = 'condition_protection_zone' } +Icons[PlayerStates.Bleeding] = { tooltip = tr('You are bleeding'), path = '/images/game/states/bleeding', id = 'condition_bleeding' } +Icons[PlayerStates.Hungry] = { tooltip = tr('You are hungry'), path = '/images/game/states/hungry', id = 'condition_hungry' } + +InventorySlotStyles = { + [InventorySlotHead] = "HeadSlot", + [InventorySlotNeck] = "NeckSlot", + [InventorySlotBack] = "BackSlot", + [InventorySlotBody] = "BodySlot", + [InventorySlotRight] = "RightSlot", + [InventorySlotLeft] = "LeftSlot", + [InventorySlotLeg] = "LegSlot", + [InventorySlotFeet] = "FeetSlot", + [InventorySlotFinger] = "FingerSlot", + [InventorySlotAmmo] = "AmmoSlot" +} + +inventoryWindow = nil +inventoryPanel = nil +inventoryButton = nil +purseButton = nil + +combatControlsWindow = nil +fightOffensiveBox = nil +fightBalancedBox = nil +fightDefensiveBox = nil +chaseModeButton = nil +safeFightButton = nil +mountButton = nil +fightModeRadioGroup = nil +buttonPvp = nil + +soulLabel = nil +capLabel = nil +conditionPanel = nil + +function init() + connect(LocalPlayer, { + onInventoryChange = onInventoryChange, + onBlessingsChange = onBlessingsChange + }) + connect(g_game, { onGameStart = refresh }) + + g_keyboard.bindKeyDown('Ctrl+I', toggle) + + + inventoryWindow = g_ui.loadUI('inventory', modules.game_interface.getRightPanel()) + inventoryWindow:disableResize() + inventoryPanel = inventoryWindow:getChildById('contentsPanel'):getChildById('inventoryPanel') + if not inventoryWindow.forceOpen then + inventoryButton = modules.client_topmenu.addRightGameToggleButton('inventoryButton', tr('Inventory') .. ' (Ctrl+I)', '/images/topbuttons/inventory', toggle) + inventoryButton:setOn(true) + end + + purseButton = inventoryWindow:recursiveGetChildById('purseButton') + purseButton.onClick = function() + local purse = g_game.getLocalPlayer():getInventoryItem(InventorySlotPurse) + if purse then + g_game.use(purse) + end + end + + -- controls + fightOffensiveBox = inventoryWindow:recursiveGetChildById('fightOffensiveBox') + fightBalancedBox = inventoryWindow:recursiveGetChildById('fightBalancedBox') + fightDefensiveBox = inventoryWindow:recursiveGetChildById('fightDefensiveBox') + + chaseModeButton = inventoryWindow:recursiveGetChildById('chaseModeBox') + safeFightButton = inventoryWindow:recursiveGetChildById('safeFightBox') + buttonPvp = inventoryWindow:recursiveGetChildById('buttonPvp') + + mountButton = inventoryWindow:recursiveGetChildById('mountButton') + mountButton.onClick = onMountButtonClick + + whiteDoveBox = inventoryWindow:recursiveGetChildById('whiteDoveBox') + whiteHandBox = inventoryWindow:recursiveGetChildById('whiteHandBox') + yellowHandBox = inventoryWindow:recursiveGetChildById('yellowHandBox') + redFistBox = inventoryWindow:recursiveGetChildById('redFistBox') + + fightModeRadioGroup = UIRadioGroup.create() + fightModeRadioGroup:addWidget(fightOffensiveBox) + fightModeRadioGroup:addWidget(fightBalancedBox) + fightModeRadioGroup:addWidget(fightDefensiveBox) + + connect(fightModeRadioGroup, { onSelectionChange = onSetFightMode }) + connect(chaseModeButton, { onCheckChange = onSetChaseMode }) + connect(safeFightButton, { onCheckChange = onSetSafeFight }) + if buttonPvp then + connect(buttonPvp, { onClick = onSetSafeFight2 }) + end + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + onFightModeChange = update, + onChaseModeChange = update, + onSafeFightChange = update, + onPVPModeChange = update, + onWalk = check, + onAutoWalk = check + }) + + connect(LocalPlayer, { onOutfitChange = onOutfitChange }) + + if g_game.isOnline() then + online() + end +-- controls end + +-- status + soulLabel = inventoryWindow:recursiveGetChildById('soulLabel') + capLabel = inventoryWindow:recursiveGetChildById('capLabel') + conditionPanel = inventoryWindow:recursiveGetChildById('conditionPanel') + + + connect(LocalPlayer, { onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) +-- status end + + refresh() + inventoryWindow:setup() +end + +function terminate() + disconnect(LocalPlayer, { + onInventoryChange = onInventoryChange, + onBlessingsChange = onBlessingsChange + }) + disconnect(g_game, { onGameStart = refresh }) + + g_keyboard.unbindKeyDown('Ctrl+I') + + -- controls + if g_game.isOnline() then + offline() + end + + fightModeRadioGroup:destroy() + + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + onFightModeChange = update, + onChaseModeChange = update, + onSafeFightChange = update, + onPVPModeChange = update, + onWalk = check, + onAutoWalk = check + }) + + disconnect(LocalPlayer, { onOutfitChange = onOutfitChange }) + -- controls end + -- status + disconnect(LocalPlayer, { onStatesChange = onStatesChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange }) + -- status end + + inventoryWindow:destroy() + if inventoryButton then + inventoryButton:destroy() + end +end + +function toggleAdventurerStyle(hasBlessing) + for slot = InventorySlotFirst, InventorySlotLast do + local itemWidget = inventoryPanel:getChildById('slot' .. slot) + if itemWidget then + itemWidget:setOn(hasBlessing) + end + end +end + +function refresh() + local player = g_game.getLocalPlayer() + for i = InventorySlotFirst, InventorySlotPurse do + if g_game.isOnline() then + onInventoryChange(player, i, player:getInventoryItem(i)) + else + onInventoryChange(player, i, nil) + end + toggleAdventurerStyle(player and Bit.hasBit(player:getBlessings(), Blessings.Adventurer) or false) + end + if player then + onSoulChange(player, player:getSoul()) + onFreeCapacityChange(player, player:getFreeCapacity()) + onStatesChange(player, player:getStates(), 0) + end + + purseButton:setVisible(g_game.getFeature(GamePurseSlot)) +end + +function toggle() + if not inventoryButton then + return + end + if inventoryButton:isOn() then + inventoryWindow:close() + inventoryButton:setOn(false) + else + inventoryWindow:open() + inventoryButton:setOn(true) + end +end + +function onMiniWindowClose() + if not inventoryButton then + return + end + inventoryButton:setOn(false) +end + +-- hooked events +function onInventoryChange(player, slot, item, oldItem) + if slot > InventorySlotPurse then return end + + if slot == InventorySlotPurse then + if g_game.getFeature(GamePurseSlot) then + --purseButton:setEnabled(item and true or false) + end + return + end + + local itemWidget = inventoryPanel:getChildById('slot' .. slot) + if item then + itemWidget:setStyle('InventoryItem') + itemWidget:setItem(item) + else + itemWidget:setStyle(InventorySlotStyles[slot]) + itemWidget:setItem(nil) + end +end + +function onBlessingsChange(player, blessings, oldBlessings) + local hasAdventurerBlessing = Bit.hasBit(blessings, Blessings.Adventurer) + if hasAdventurerBlessing ~= Bit.hasBit(oldBlessings, Blessings.Adventurer) then + toggleAdventurerStyle(hasAdventurerBlessing) + end +end + + +-- controls +function update() + local fightMode = g_game.getFightMode() + if fightMode == FightOffensive then + fightModeRadioGroup:selectWidget(fightOffensiveBox) + elseif fightMode == FightBalanced then + fightModeRadioGroup:selectWidget(fightBalancedBox) + else + fightModeRadioGroup:selectWidget(fightDefensiveBox) + end + + local chaseMode = g_game.getChaseMode() + chaseModeButton:setChecked(chaseMode == ChaseOpponent) + + local safeFight = g_game.isSafeFight() + safeFightButton:setChecked(not safeFight) + if buttonPvp then + if safeFight then + buttonPvp:setOn(false) + else + buttonPvp:setOn(true) + end + end + + if g_game.getFeature(GamePVPMode) then + local pvpMode = g_game.getPVPMode() + local pvpWidget = getPVPBoxByMode(pvpMode) + end +end + +function check() + if modules.client_options.getOption('autoChaseOverride') then + if g_game.isAttacking() and g_game.getChaseMode() == ChaseOpponent then + g_game.setChaseMode(DontChase) + end + end +end + +function online() + local player = g_game.getLocalPlayer() + if player then + local char = g_game.getCharacterName() + + local lastCombatControls = g_settings.getNode('LastCombatControls') + + if not table.empty(lastCombatControls) then + if lastCombatControls[char] then + g_game.setFightMode(lastCombatControls[char].fightMode) + g_game.setChaseMode(lastCombatControls[char].chaseMode) + g_game.setSafeFight(lastCombatControls[char].safeFight) + if lastCombatControls[char].pvpMode then + g_game.setPVPMode(lastCombatControls[char].pvpMode) + end + end + end + + if g_game.getFeature(GamePlayerMounts) then + mountButton:setVisible(true) + mountButton:setChecked(player:isMounted()) + else + mountButton:setVisible(false) + end + end + + update() +end + +function offline() + local lastCombatControls = g_settings.getNode('LastCombatControls') + if not lastCombatControls then + lastCombatControls = {} + end + + conditionPanel:destroyChildren() + + local player = g_game.getLocalPlayer() + if player then + local char = g_game.getCharacterName() + lastCombatControls[char] = { + fightMode = g_game.getFightMode(), + chaseMode = g_game.getChaseMode(), + safeFight = g_game.isSafeFight() + } + + if g_game.getFeature(GamePVPMode) then + lastCombatControls[char].pvpMode = g_game.getPVPMode() + end + + -- save last combat control settings + g_settings.setNode('LastCombatControls', lastCombatControls) + end +end + +function onSetFightMode(self, selectedFightButton) + if selectedFightButton == nil then return end + local buttonId = selectedFightButton:getId() + local fightMode + if buttonId == 'fightOffensiveBox' then + fightMode = FightOffensive + elseif buttonId == 'fightBalancedBox' then + fightMode = FightBalanced + else + fightMode = FightDefensive + end + g_game.setFightMode(fightMode) +end + +function onSetChaseMode(self, checked) + local chaseMode + if checked then + chaseMode = ChaseOpponent + else + chaseMode = DontChase + end + g_game.setChaseMode(chaseMode) +end + +function onSetSafeFight(self, checked) + g_game.setSafeFight(not checked) + if buttonPvp then + if not checked then + buttonPvp:setOn(false) + else + buttonPvp:setOn(true) + end + end +end + +function onSetSafeFight2(self) + onSetSafeFight(self, not safeFightButton:isChecked()) +end + +function onSetPVPMode(self, selectedPVPButton) + if selectedPVPButton == nil then + return + end + + local buttonId = selectedPVPButton:getId() + local pvpMode = PVPWhiteDove + if buttonId == 'whiteDoveBox' then + pvpMode = PVPWhiteDove + elseif buttonId == 'whiteHandBox' then + pvpMode = PVPWhiteHand + elseif buttonId == 'yellowHandBox' then + pvpMode = PVPYellowHand + elseif buttonId == 'redFistBox' then + pvpMode = PVPRedFist + end + + g_game.setPVPMode(pvpMode) +end + +function onMountButtonClick(self, mousePos) + local player = g_game.getLocalPlayer() + if player then + player:toggleMount() + end +end + +function onOutfitChange(localPlayer, outfit, oldOutfit) + if outfit.mount == oldOutfit.mount then + return + end + + mountButton:setChecked(outfit.mount ~= nil and outfit.mount > 0) +end + +function getPVPBoxByMode(mode) + local widget = nil + if mode == PVPWhiteDove then + widget = whiteDoveBox + elseif mode == PVPWhiteHand then + widget = whiteHandBox + elseif mode == PVPYellowHand then + widget = yellowHandBox + elseif mode == PVPRedFist then + widget = redFistBox + end + return widget +end + +-- status +function toggleIcon(bitChanged) + local icon = conditionPanel:getChildById(Icons[bitChanged].id) + if icon then + icon:destroy() + else + icon = loadIcon(bitChanged) + icon:setParent(conditionPanel) + end +end + +function loadIcon(bitChanged) + local icon = g_ui.createWidget('ConditionWidget', conditionPanel) + icon:setId(Icons[bitChanged].id) + icon:setImageSource(Icons[bitChanged].path) + icon:setTooltip(Icons[bitChanged].tooltip) + return icon +end + +function onSoulChange(localPlayer, soul) + if not soul then return end + soulLabel:setText(tr('Soul') .. ':\n' .. soul) +end + +function onFreeCapacityChange(player, freeCapacity) + if not freeCapacity then return end + if freeCapacity > 99 then + freeCapacity = math.floor(freeCapacity * 10) / 10 + end + if freeCapacity > 999 then + freeCapacity = math.floor(freeCapacity) + end + if freeCapacity > 99999 then + freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k" + end + capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity) +end + +function onStatesChange(localPlayer, now, old) + if now == old then return end + local bitsChanged = bit32.bxor(now, old) + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > bitsChanged then break end + local bitChanged = bit32.band(bitsChanged, pow) + if bitChanged ~= 0 then + toggleIcon(bitChanged) + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otmod new file mode 100644 index 0000000..5ee5be7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otmod @@ -0,0 +1,9 @@ +Module + name: game_inventory + description: View local player equipments window + author: baxnie, edubart, BeniS, otclientv8 + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ inventory ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otui new file mode 100644 index 0000000..dd01c02 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_inventory/inventory.otui @@ -0,0 +1,6 @@ +InventoryWindow + id: inventoryWindow + !text: tr('Inventory') + @onClose: modules.game_inventory.onMiniWindowClose() + &save: true + &autoOpen: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.lua new file mode 100644 index 0000000..33f9437 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.lua @@ -0,0 +1,74 @@ +local activeWindow + +function init() + g_ui.importStyle('itemselector') + + connect(g_game, { onGameEnd = destroyWindow }) +end + +function terminate() + disconnect(g_game, { onGameEnd = destroyWindow }) + + destroyWindow() +end + +function destroyWindow() + if activeWindow then + activeWindow:destroy() + activeWindow = nil + end +end + +function show(itemWidget) + if not itemWidget then + return + end + if activeWindow then + destroyWindow() + end + local window = g_ui.createWidget('ItemSelectorWindow', rootWidget) + + local destroy = function() + window:destroy() + if window == activeWindow then + activeWindow = nil + end + end + local doneFunc = function() + itemWidget:setItem(Item.create(window.item:getItemId(), window.item:getItemCount())) + destroy() + end + local clearFunc = function() + window.item:setItemId(0) + window.item:setItemCount(0) + doneFunc() + end + + window.clearButton.onClick = clearFunc + window.okButton.onClick = doneFunc + window.cancelButton.onClick = destroy + window.onEnter = doneFunc + window.onEscape = destroy + + window.item:setItem(Item.create(itemWidget:getItemId(), itemWidget:getItemCount())) + + window.itemId:setValue(itemWidget:getItemId()) + if itemWidget:getItemCount() > 1 then + window.itemCount:setValue(itemWidget:getItemCount()) + end + + window.itemId.onValueChange = function(widget, value) + window.item:setItemId(value) + end + window.itemCount.onValueChange = function(widget, value) + window.item:setItemCount(value) + end + + activeWindow = window + activeWindow:raise() + activeWindow:focus() +end + +function hide() + destroyWindow() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otmod new file mode 100644 index 0000000..d8f92b7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otmod @@ -0,0 +1,10 @@ +Module + name: game_itemselector + description: Allow to select item + author: OTClientV8 + website: https://github.com/OTCv8/otclientv8 + sandboxed: true + dependencies: [ game_interface ] + scripts: [ itemselector ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otui new file mode 100644 index 0000000..0e114c6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_itemselector/itemselector.otui @@ -0,0 +1,72 @@ +ItemSelectorWindow < MainWindow + id: itemSelector + size: 260 120 + !text: tr("Select item") + + Item + id: item + virtual: true + size: 32 32 + margin-top: 10 + anchors.top: parent.top + anchors.left: parent.left + + SpinBox + id: itemId + anchors.top: parent.top + anchors.left: prev.right + margin-top: 15 + margin-left: 5 + padding-left: 5 + width: 70 + minimum: 0 + maximum: 999999 + focusable: true + + Label + anchors.top: parent.top + anchors.left: prev.left + anchors.right: prev.right + text-align: center + !text: tr("Item ID") + + SpinBox + id: itemCount + anchors.top: parent.top + anchors.left: prev.right + margin-top: 15 + margin-left: 5 + padding-left: 5 + width: 120 + minimum: 1 + maximum: 100 + focusable: true + + Label + anchors.top: parent.top + anchors.left: prev.left + anchors.right: prev.right + text-align: center + !text: tr("Count / SubType") + + Button + id: clearButton + !text: tr('Clear') + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 60 + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.lua new file mode 100644 index 0000000..5e55f76 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.lua @@ -0,0 +1,1433 @@ +--[[ + Finalizing Market: + Note: Feel free to work on any area and submit + it as a pull request from your git fork. + + BeniS's Skype: benjiz69 + + List: + * Add offer management: + - Current Offers + - Offer History + + * Clean up the interface building + - Add a new market interface file to handle building? + + * Extend information features + - Hover over offers for purchase information (balance after transaction, etc) + ]] + +Market = {} + +local protocol = runinsandbox('marketprotocol') + +marketWindow = nil +mainTabBar = nil +displaysTabBar = nil +offersTabBar = nil +selectionTabBar = nil + +marketOffersPanel = nil +browsePanel = nil +overviewPanel = nil +itemOffersPanel = nil +itemDetailsPanel = nil +itemStatsPanel = nil +myOffersPanel = nil +currentOffersPanel = nil +myCurrentOffersTab = nil +myOfferHistoryTab = nil +offerHistoryPanel = nil +itemsPanel = nil +selectedOffer = {} +selectedMyOffer = {} + +nameLabel = nil +feeLabel = nil +balanceLabel = nil +totalPriceEdit = nil +piecePriceEdit = nil +amountEdit = nil +searchEdit = nil +radioItemSet = nil +selectedItem = nil +offerTypeList = nil +categoryList = nil +subCategoryList = nil +slotFilterList = nil +createOfferButton = nil +buyButton = nil +sellButton = nil +anonymous = nil +filterButtons = {} + +buyOfferTable = nil +sellOfferTable = nil +detailsTable = nil +buyStatsTable = nil +sellStatsTable = nil + +buyCancelButton = nil +sellCancelButton = nil +buyMyOfferTable = nil +sellMyOfferTable = nil +myOfferHistoryTabel = nil + +offerExhaust = {} +marketOffers = {} +marketItems = {} +marketItemNames = {} +information = {} +currentItems = {} +lastCreatedOffer = 0 +fee = 0 +averagePrice = 0 +tibiaCoins = 0 + +loaded = false + +local function isItemValid(item, category, searchFilter) + if not item or not item.marketData then + return false + end + + if not category then + category = MarketCategory.All + end + if item.marketData.category ~= category and category ~= MarketCategory.All then + return false + end + + -- filter item + local slotFilter = false + if slotFilterList:isEnabled() then + slotFilter = getMarketSlotFilterId(slotFilterList:getCurrentOption().text) + end + local marketData = item.marketData + + local filterVocation = filterButtons[MarketFilters.Vocation]:isChecked() + local filterLevel = filterButtons[MarketFilters.Level]:isChecked() + local filterDepot = filterButtons[MarketFilters.Depot]:isChecked() + + if slotFilter then + if slotFilter ~= 255 and item.thingType:getClothSlot() ~= slotFilter then + return false + end + end + local player = g_game.getLocalPlayer() + if filterLevel and marketData.requiredLevel and player:getLevel() < marketData.requiredLevel then + return false + end + if filterVocation and marketData.restrictVocation and marketData.restrictVocation > 0 then + local voc = Bit.bit(information.vocation) + if not Bit.hasBit(marketData.restrictVocation, voc) then + return false + end + end + if filterDepot and Market.getDepotCount(item.marketData.tradeAs) <= 0 then + return false + end + if searchFilter then + return marketData.name:lower():find(searchFilter) + end + return true +end + +local function clearItems() + currentItems = {} + Market.refreshItemsWidget() +end + +local function clearOffers() + marketOffers[MarketAction.Buy] = {} + marketOffers[MarketAction.Sell] = {} + buyOfferTable:clearData() + sellOfferTable:clearData() +end + +local function clearMyOffers() + marketOffers[MarketAction.Buy] = {} + marketOffers[MarketAction.Sell] = {} + buyMyOfferTable:clearData() + sellMyOfferTable:clearData() + myOfferHistoryTabel:clearData() +end + +local function clearFilters() + for _, filter in pairs(filterButtons) do + if filter and filter:isChecked() ~= filter.default then + filter:setChecked(filter.default) + end + end +end + +local function clearFee() + feeLabel:setText('') + fee = 20 +end + +local function refreshTypeList() + offerTypeList:clearOptions() + offerTypeList:addOption('Buy') + + if Market.isItemSelected() then + if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then + offerTypeList:addOption('Sell') + end + end +end + +local function addOffer(offer, offerType) + if not offer then + return false + end + local id = offer:getId() + local player = offer:getPlayer() + local amount = offer:getAmount() + local price = offer:getPrice() + local timestamp = offer:getTimeStamp() + local itemName = marketItemNames[offer:getItem():getId()] + if not itemName then + itemName = offer:getItem():getMarketData().name + end + + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + + buyMyOfferTable:toggleSorting(false) + sellMyOfferTable:toggleSorting(false) + + if amount < 1 then return false end + if offerType == MarketAction.Buy then + if offer.warn then + buyOfferTable:setColumnStyle('OfferTableWarningColumn', true) + end + + local row = nil + if offer.var == MarketRequest.MyOffers then + row = buyMyOfferTable:addRow({ + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + else + row = buyOfferTable:addRow({ + {text = player}, + {text = amount}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " ")} + }) + end + row.ref = id + + if offer.warn then + row:setTooltip(tr('This offer is 25%% below the average market price')) + buyOfferTable:setColumnStyle('OfferTableColumn', true) + end + else + if offer.warn then + sellOfferTable:setColumnStyle('OfferTableWarningColumn', true) + end + + local row = nil + if offer.var == MarketRequest.MyOffers then + row = sellMyOfferTable:addRow({ + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + else + row = sellOfferTable:addRow({ + {text = player}, + {text = amount}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + end + row.ref = id + + if offer.warn then + row:setTooltip(tr('This offer is 25%% above the average market price')) + sellOfferTable:setColumnStyle('OfferTableColumn', true) + end + end + + buyOfferTable:toggleSorting(false) + sellOfferTable:toggleSorting(false) + buyOfferTable:sort() + sellOfferTable:sort() + + buyMyOfferTable:toggleSorting(false) + sellMyOfferTable:toggleSorting(false) + buyMyOfferTable:sort() + sellMyOfferTable:sort() + + return true +end + +local function mergeOffer(offer) + if not offer then + return false + end + + local id = offer:getId() + local offerType = offer:getType() + local amount = offer:getAmount() + local replaced = false + + if offerType == MarketAction.Buy then + if averagePrice > 0 then + offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4) + end + + for i = 1, #marketOffers[MarketAction.Buy] do + local o = marketOffers[MarketAction.Buy][i] + -- replace existing offer + if o:isEqual(id) then + marketOffers[MarketAction.Buy][i] = offer + replaced = true + end + end + if not replaced then + table.insert(marketOffers[MarketAction.Buy], offer) + end + else + if averagePrice > 0 then + offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4) + end + + for i = 1, #marketOffers[MarketAction.Sell] do + local o = marketOffers[MarketAction.Sell][i] + -- replace existing offer + if o:isEqual(id) then + marketOffers[MarketAction.Sell][i] = offer + replaced = true + end + end + if not replaced then + table.insert(marketOffers[MarketAction.Sell], offer) + end + end + return true +end + +local function updateOffers(offers) + if not buyOfferTable or not sellOfferTable then + return + end + + balanceLabel:setColor('#bbbbbb') + selectedOffer[MarketAction.Buy] = nil + selectedOffer[MarketAction.Sell] = nil + + selectedMyOffer[MarketAction.Buy] = nil + selectedMyOffer[MarketAction.Sell] = nil + + -- clear existing offer data + buyOfferTable:clearData() + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) + sellOfferTable:clearData() + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) + + sellButton:setEnabled(false) + buyButton:setEnabled(false) + + buyCancelButton:setEnabled(false) + sellCancelButton:setEnabled(false) + + for _, offer in pairs(offers) do + mergeOffer(offer) + end + for type, offers in pairs(marketOffers) do + for i = 1, #offers do + addOffer(offers[i], type) + end + end +end + +local function updateHistoryOffers(offers) + myOfferHistoryTabel:toggleSorting(false) + for _, offer in ipairs(offers) do + local offerType = offer:getType() + local id = offer:getId() + local player = offer:getPlayer() + local amount = offer:getAmount() + local price = offer:getPrice() + local timestamp = offer:getTimeStamp() + local itemName = marketItemNames[offer:getItem():getId()] + if not itemName then + itemName = offer:getItem():getMarketData().name + end + + local offerTypeName = "?" + if offerType == MarketAction.Buy then + offerTypeName = "Buy" + elseif offerType == MarketAction.Sell then + offerTypeName = "Sell" + end + + local row = myOfferHistoryTabel:addRow({ + {text = offerTypeName}, + {text = itemName}, + {text = comma_value(price*amount), sortvalue = price*amount}, + {text = comma_value(price), sortvalue = price}, + {text = amount}, + {text = string.gsub(os.date('%H:%M %d/%m/%y', timestamp), " ", " "), sortvalue = timestamp} + }) + end + myOfferHistoryTabel:toggleSorting(false) + myOfferHistoryTabel:sort() +end + +local function updateDetails(itemId, descriptions, purchaseStats, saleStats) + if not selectedItem then + return + end + + -- update item details + detailsTable:clearData() + for k, desc in pairs(descriptions) do + local columns = { + {text = getMarketDescriptionName(desc[1])..':'}, + {text = desc[2]} + } + detailsTable:addRow(columns) + end + + -- update sale item statistics + sellStatsTable:clearData() + if table.empty(saleStats) then + sellStatsTable:addRow({{text = 'No information'}}) + else + local offerAmount = 0 + local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 + for _, stat in pairs(saleStats) do + if not stat:isNull() then + offerAmount = offerAmount + 1 + transactions = transactions + stat:getTransactions() + totalPrice = totalPrice + stat:getTotalPrice() + local newHigh = stat:getHighestPrice() + if newHigh > highestPrice then + highestPrice = newHigh + end + local newLow = stat:getLowestPrice() + -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft + if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then + lowestPrice = newLow + end + end + end + + if offerAmount >= 5 and transactions >= 10 then + averagePrice = math.round(totalPrice / transactions) + else + averagePrice = 0 + end + + sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}}) + sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) + + if totalPrice > 0 and transactions > 0 then + sellStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) + else + sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) + end + + sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) + end + + -- update buy item statistics + buyStatsTable:clearData() + if table.empty(purchaseStats) then + buyStatsTable:addRow({{text = 'No information'}}) + else + local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 + for _, stat in pairs(purchaseStats) do + if not stat:isNull() then + transactions = transactions + stat:getTransactions() + totalPrice = totalPrice + stat:getTotalPrice() + local newHigh = stat:getHighestPrice() + if newHigh > highestPrice then + highestPrice = newHigh + end + local newLow = stat:getLowestPrice() + -- ?? getting '0xffffffff' result from lowest price in 9.60 cipsoft + if (lowestPrice == 0 or newLow < lowestPrice) and newLow ~= 0xffffffff then + lowestPrice = newLow + end + end + end + + buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}}) + buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}}) + + if totalPrice > 0 and transactions > 0 then + buyStatsTable:addRow({{text = 'Average Price:'}, + {text = math.floor(totalPrice/transactions)}}) + else + buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}}) + end + + buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}}) + end +end + +local function updateSelectedItem(widget) + selectedItem.item = widget.item + selectedItem.ref = widget + + Market.resetCreateOffer() + if Market.isItemSelected() then + selectedItem:setItem(selectedItem.item.displayItem) + nameLabel:setText(selectedItem.item.marketData.name) + clearOffers() + + Market.enableCreateOffer(true) -- update offer types + MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- send browsed msg + else + Market.clearSelectedItem() + end +end + +local function updateBalance(balance) + local balance = tonumber(balance) + if not balance then + return + end + + if balance < 0 then balance = 0 end + information.balance = balance + + balanceLabel:setText('Balance: '.. comma_value(balance) ..' gold') + balanceLabel:resizeToText() +end + +local function updateFee(price, amount) + fee = math.ceil(price / 100 * amount) + if fee < 20 then + fee = 20 + elseif fee > 1000 then + fee = 1000 + end + feeLabel:setText('Fee: '.. comma_value(fee)) + feeLabel:resizeToText() +end + +local function destroyAmountWindow() + if amountWindow then + amountWindow:destroy() + amountWindow = nil + end +end + +local function cancelMyOffer(actionType) + local offer = selectedMyOffer[actionType] + MarketProtocol.sendMarketCancelOffer(offer:getTimeStamp(), offer:getCounter()) + Market.refreshMyOffers() +end + +local function openAmountWindow(callback, actionType, actionText) + if not Market.isOfferSelected(actionType) then + return + end + + amountWindow = g_ui.createWidget('AmountWindow', rootWidget) + amountWindow:lock() + + local offer = selectedOffer[actionType] + local item = offer:getItem() + + local maximum = offer:getAmount() + if actionType == MarketAction.Sell then + local depot = Market.getDepotCount(item:getId()) + if maximum > depot then + maximum = depot + end + else + maximum = math.min(maximum, math.floor(information.balance / offer:getPrice())) + end + + if item:isStackable() then + maximum = math.min(maximum, MarketMaxAmountStackable) + else + maximum = math.min(maximum, MarketMaxAmount) + end + + local itembox = amountWindow:getChildById('item') + itembox:setItemId(item:getId()) + + local scrollbar = amountWindow:getChildById('amountScrollBar') + scrollbar:setText(comma_value(offer:getPrice()) ..'gp') + + scrollbar.onValueChange = function(widget, value) + widget:setText(comma_value(value*offer:getPrice())..'gp') + itembox:setText(comma_value(value)) + end + scrollbar:setRange(1, maximum) + scrollbar:setValue(1) + + local okButton = amountWindow:getChildById('buttonOk') + if actionText then + okButton:setText(actionText) + end + + local okFunc = function() + local counter = offer:getCounter() + local timestamp = offer:getTimeStamp() + callback(scrollbar:getValue(), timestamp, counter) + destroyAmountWindow() + end + + local cancelButton = amountWindow:getChildById('buttonCancel') + local cancelFunc = function() + destroyAmountWindow() + end + + amountWindow.onEnter = okFunc + amountWindow.onEscape = cancelFunc + + okButton.onClick = okFunc + cancelButton.onClick = cancelFunc +end + +local function onSelectSellOffer(table, selectedRow, previousSelectedRow) + updateBalance() + for _, offer in pairs(marketOffers[MarketAction.Sell]) do + if offer:isEqual(selectedRow.ref) then + selectedOffer[MarketAction.Buy] = offer + end + end + + local offer = selectedOffer[MarketAction.Buy] + if offer then + local price = offer:getPrice() + if price > information.balance then + balanceLabel:setColor('#b22222') -- red + buyButton:setEnabled(false) + else + local slice = (information.balance / 2) + if (price/slice) * 100 <= 40 then + color = '#008b00' -- green + elseif (price/slice) * 100 <= 70 then + color = '#eec900' -- yellow + else + color = '#ee9a00' -- orange + end + balanceLabel:setColor(color) + buyButton:setEnabled(true) + end + end +end + +local function onSelectBuyOffer(table, selectedRow, previousSelectedRow) + updateBalance() + for _, offer in pairs(marketOffers[MarketAction.Buy]) do + if offer:isEqual(selectedRow.ref) then + selectedOffer[MarketAction.Sell] = offer + if Market.getDepotCount(offer:getItem():getId()) > 0 then + sellButton:setEnabled(true) + else + sellButton:setEnabled(false) + end + end + end +end + +local function onSelectMyBuyOffer(table, selectedRow, previousSelectedRow) + for _, offer in pairs(marketOffers[MarketAction.Buy]) do + if offer:isEqual(selectedRow.ref) then + selectedMyOffer[MarketAction.Buy] = offer + buyCancelButton:setEnabled(true) + end + end +end + +local function onSelectMySellOffer(table, selectedRow, previousSelectedRow) + for _, offer in pairs(marketOffers[MarketAction.Sell]) do + if offer:isEqual(selectedRow.ref) then + selectedMyOffer[MarketAction.Sell] = offer + sellCancelButton:setEnabled(true) + end + end +end + +local function onChangeCategory(combobox, option) + local id = getMarketCategoryId(option) + if id == MarketCategory.MetaWeapons then + -- enable and load weapons filter/items + subCategoryList:setEnabled(true) + slotFilterList:setEnabled(true) + local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text) + Market.loadMarketItems(subId) + else + subCategoryList:setEnabled(false) + slotFilterList:setEnabled(false) + Market.loadMarketItems(id) -- load standard filter + end +end + +local function onChangeSubCategory(combobox, option) + Market.loadMarketItems(getMarketCategoryId(option)) + slotFilterList:clearOptions() + + local subId = getMarketCategoryId(subCategoryList:getCurrentOption().text) + local slots = MarketCategoryWeapons[subId].slots + for _, slot in pairs(slots) do + if table.haskey(MarketSlotFilters, slot) then + slotFilterList:addOption(MarketSlotFilters[slot]) + end + end + slotFilterList:setEnabled(true) +end + +local function onChangeSlotFilter(combobox, option) + Market.updateCurrentItems() +end + +local function onChangeOfferType(combobox, option) + local item = selectedItem.item + local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount + + if option == 'Sell' then + maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs)) + amountEdit:setMaximum(maximum) + else + amountEdit:setMaximum(maximum) + end +end + +local function onTotalPriceChange() + local amount = amountEdit:getValue() + local totalPrice = totalPriceEdit:getValue() + local piecePrice = math.floor(totalPrice/amount) + + piecePriceEdit:setValue(piecePrice, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onPiecePriceChange() + local amount = amountEdit:getValue() + local totalPrice = totalPriceEdit:getValue() + local piecePrice = piecePriceEdit:getValue() + + totalPriceEdit:setValue(piecePrice*amount, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onAmountChange() + local amount = amountEdit:getValue() + local piecePrice = piecePriceEdit:getValue() + local totalPrice = piecePrice * amount + + totalPriceEdit:setValue(piecePrice*amount, true) + if Market.isItemSelected() then + updateFee(piecePrice, amount) + end +end + +local function onMarketMessage(messageMode, message) + Market.displayMessage(message) +end + +local function initMarketItems(items) + for c = MarketCategory.First, MarketCategory.Last do + marketItems[c] = {} + end + marketItemNames = {} + + -- save a list of items which are already added + local itemSet = {} + + -- parse items send by server + if items then + for _, entry in ipairs(items) do + local item = Item.create(entry.id) + local thingType = g_things.getThingType(entry.id, ThingCategoryItem) + if item and thingType and not marketItemNames[entry.id] then + -- create new marketItem block + local marketItem = { + displayItem = item, + thingType = thingType, + marketData = { + name = entry.name, + category = entry.category, + requiredLevel = 0, + restrictVocation = 0, + showAs = entry.id, + tradeAs = entry.id + } + } + + -- add new market item + if marketItems[entry.category] ~= nil then + table.insert(marketItems[entry.category], marketItem) + marketItemNames[entry.id] = entry.name + end + end + end + Market.updateCategories() + return + end + + -- populate all market items + local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0) + for i = 1, #types do + local itemType = types[i] + + local item = Item.create(itemType:getId()) + if item then + local marketData = itemType:getMarketData() + if not table.empty(marketData) and not itemSet[marketData.tradeAs] then + -- Some items use a different sprite in Market + item:setId(marketData.showAs) + + -- create new marketItem block + local marketItem = { + displayItem = item, + thingType = itemType, + marketData = marketData + } + + -- add new market item + if marketItems[marketData.category] ~= nil then + table.insert(marketItems[marketData.category], marketItem) + itemSet[marketData.tradeAs] = true + end + end + end + end +end + +local function initInterface() + -- TODO: clean this up + -- setup main tabs + mainTabBar = marketWindow:getChildById('mainTabBar') + mainTabBar:setContentWidget(marketWindow:getChildById('mainTabContent')) + + -- setup 'Market Offer' section tabs + marketOffersPanel = g_ui.loadUI('ui/marketoffers') + mainTabBar:addTab(tr('Market Offers'), marketOffersPanel) + + selectionTabBar = marketOffersPanel:getChildById('leftTabBar') + selectionTabBar:setContentWidget(marketOffersPanel:getChildById('leftTabContent')) + + browsePanel = g_ui.loadUI('ui/marketoffers/browse') + selectionTabBar:addTab(tr('Browse'), browsePanel) + + -- Currently not used + -- "Reserved for more functionality later" + --overviewPanel = g_ui.loadUI('ui/marketoffers/overview') + --selectionTabBar:addTab(tr('Overview'), overviewPanel) + + displaysTabBar = marketOffersPanel:getChildById('rightTabBar') + displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent')) + + itemStatsPanel = g_ui.loadUI('ui/marketoffers/itemstats') + displaysTabBar:addTab(tr('Statistics'), itemStatsPanel) + + itemDetailsPanel = g_ui.loadUI('ui/marketoffers/itemdetails') + displaysTabBar:addTab(tr('Details'), itemDetailsPanel) + + itemOffersPanel = g_ui.loadUI('ui/marketoffers/itemoffers') + displaysTabBar:addTab(tr('Offers'), itemOffersPanel) + displaysTabBar:selectTab(displaysTabBar:getTab(tr('Offers'))) + + -- setup 'My Offer' section tabs + myOffersPanel = g_ui.loadUI('ui/myoffers') + local myOffersTab = mainTabBar:addTab(tr('My Offers'), myOffersPanel) + + offersTabBar = myOffersPanel:getChildById('offersTabBar') + offersTabBar:setContentWidget(myOffersPanel:getChildById('offersTabContent')) + + currentOffersPanel = g_ui.loadUI('ui/myoffers/currentoffers') + myCurrentOffersTab = offersTabBar:addTab(tr('Current Offers'), currentOffersPanel) + + offerHistoryPanel = g_ui.loadUI('ui/myoffers/offerhistory') + myOfferHistoryTab = offersTabBar:addTab(tr('Offer History'), offerHistoryPanel) + + balanceLabel = marketWindow:getChildById('balanceLabel') + + mainTabBar.onTabChange = function(widget, tab) + if tab == myOffersTab then + local ctab = offersTabBar:getCurrentTab() + if ctab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif ctab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + else + Market.refreshOffers() + end + end + + offersTabBar.onTabChange = function(widget, tab) + if tab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif tab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + end + + -- setup offers + buyButton = itemOffersPanel:getChildById('buyButton') + buyButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Buy, 'Buy') end + + sellButton = itemOffersPanel:getChildById('sellButton') + sellButton.onClick = function() openAmountWindow(Market.acceptMarketOffer, MarketAction.Sell, 'Sell') end + + -- setup selected item + nameLabel = marketOffersPanel:getChildById('nameLabel') + selectedItem = marketOffersPanel:getChildById('selectedItem') + + -- setup create new offer + totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit') + piecePriceEdit = marketOffersPanel:getChildById('piecePriceEdit') + amountEdit = marketOffersPanel:getChildById('amountEdit') + feeLabel = marketOffersPanel:getChildById('feeLabel') + totalPriceEdit.onValueChange = onTotalPriceChange + piecePriceEdit.onValueChange = onPiecePriceChange + amountEdit.onValueChange = onAmountChange + + offerTypeList = marketOffersPanel:getChildById('offerTypeComboBox') + offerTypeList.onOptionChange = onChangeOfferType + + anonymous = marketOffersPanel:getChildById('anonymousCheckBox') + createOfferButton = marketOffersPanel:getChildById('createOfferButton') + createOfferButton.onClick = Market.createNewOffer + Market.enableCreateOffer(false) + + -- setup filters + filterButtons[MarketFilters.Vocation] = browsePanel:getChildById('filterVocation') + filterButtons[MarketFilters.Level] = browsePanel:getChildById('filterLevel') + filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot') + filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll') + + -- set filter default values + clearFilters() + + -- hook filters + for _, filter in pairs(filterButtons) do + filter.onCheckChange = Market.updateCurrentItems + end + + searchEdit = browsePanel:getChildById('searchEdit') + categoryList = browsePanel:getChildById('categoryComboBox') + subCategoryList = browsePanel:getChildById('subCategoryComboBox') + slotFilterList = browsePanel:getChildById('slotComboBox') + + slotFilterList:addOption(MarketSlotFilters[255]) + slotFilterList:setEnabled(false) + + Market.updateCategories() + + -- hook item filters + categoryList.onOptionChange = onChangeCategory + subCategoryList.onOptionChange = onChangeSubCategory + slotFilterList.onOptionChange = onChangeSlotFilter + + -- setup tables + buyOfferTable = itemOffersPanel:recursiveGetChildById('buyingTable') + sellOfferTable = itemOffersPanel:recursiveGetChildById('sellingTable') + detailsTable = itemDetailsPanel:recursiveGetChildById('detailsTable') + buyStatsTable = itemStatsPanel:recursiveGetChildById('buyStatsTable') + sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') + buyOfferTable.onSelectionChange = onSelectBuyOffer + sellOfferTable.onSelectionChange = onSelectSellOffer + + -- setup my offers + buyMyOfferTable = currentOffersPanel:recursiveGetChildById('myBuyingTable') + sellMyOfferTable = currentOffersPanel:recursiveGetChildById('mySellingTable') + myOfferHistoryTabel = offerHistoryPanel:recursiveGetChildById('myHistoryTable') + + buyMyOfferTable.onSelectionChange = onSelectMyBuyOffer + sellMyOfferTable.onSelectionChange = onSelectMySellOffer + + buyCancelButton = currentOffersPanel:getChildById('buyCancelButton') + buyCancelButton.onClick = function() cancelMyOffer(MarketAction.Buy) end + + sellCancelButton = currentOffersPanel:getChildById('sellCancelButton') + sellCancelButton.onClick = function() cancelMyOffer(MarketAction.Sell) end + + + buyStatsTable:setColumnWidth({120, 270}) + sellStatsTable:setColumnWidth({120, 270}) + detailsTable:setColumnWidth({80, 330}) + + buyOfferTable:setSorting(4, TABLE_SORTING_DESC) + sellOfferTable:setSorting(4, TABLE_SORTING_ASC) + + buyMyOfferTable:setSorting(3, TABLE_SORTING_DESC) + sellMyOfferTable:setSorting(3, TABLE_SORTING_DESC) + myOfferHistoryTabel:setSorting(6, TABLE_SORTING_DESC) +end + +function init() + g_ui.importStyle('market') + g_ui.importStyle('ui/general/markettabs') + g_ui.importStyle('ui/general/marketbuttons') + g_ui.importStyle('ui/general/marketcombobox') + g_ui.importStyle('ui/general/amountwindow') + + offerExhaust[MarketAction.Sell] = 10 + offerExhaust[MarketAction.Buy] = 20 + + registerMessageMode(MessageModes.Market, onMarketMessage) + + protocol.initProtocol() + connect(g_game, { onGameEnd = Market.reset }) + connect(g_game, { onGameEnd = Market.close }) + connect(g_game, { onGameStart = Market.updateCategories }) + connect(g_game, { onCoinBalance = Market.onCoinBalance }) + + marketWindow = g_ui.createWidget('MarketWindow', rootWidget) + marketWindow:hide() + + initInterface() -- build interface +end + +function terminate() + Market.close() + + unregisterMessageMode(MessageModes.Market, onMarketMessage) + + protocol.terminateProtocol() + disconnect(g_game, { onGameEnd = Market.reset }) + disconnect(g_game, { onGameEnd = Market.close }) + disconnect(g_game, { onGameStart = Market.updateCategories }) + disconnect(g_game, { onCoinBalance = Market.onCoinBalance }) + + destroyAmountWindow() + marketWindow:destroy() + + Market = nil +end + +function Market.reset() + balanceLabel:setColor('#bbbbbb') + categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + searchEdit:setText('') + clearFilters() + clearMyOffers() + if not table.empty(information) then + Market.updateCurrentItems() + end +end + +function Market.updateCategories() + categoryList:clearOptions() + subCategoryList:clearOptions() + + local categories = {} + local addedCategories = {} + for _, c in ipairs(g_things.getMarketCategories()) do + table.insert(categories, getMarketCategoryName(c) or "Unknown") + addedCategories[c] = true + end + for c, items in ipairs(marketItems) do + if #items > 0 and not addedCategories[c] then + table.insert(categories, getMarketCategoryName(c) or "Unknown") + addedCategories[c] = true + end + end + + table.sort(categories) + for _, c in ipairs(categories) do + categoryList:addOption(c) + end + + for i = MarketCategory.Ammunition, MarketCategory.WandsRods do + subCategoryList:addOption(getMarketCategoryName(i)) + end + + categoryList:addOption(getMarketCategoryName(255)) -- meta weapons + categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) + subCategoryList:setEnabled(false) +end + +function Market.displayMessage(message) + if marketWindow:isHidden() then return end + + local infoBox = displayInfoBox(tr('Market Error'), message) + infoBox:lock() +end + +function Market.clearSelectedItem() + if Market.isItemSelected() then + Market.resetCreateOffer(true) + offerTypeList:clearOptions() + offerTypeList:setText('Please Select') + offerTypeList:setEnabled(false) + + clearOffers() + radioItemSet:selectWidget(nil) + nameLabel:setText('No item selected.') + selectedItem:setItem(nil) + selectedItem.item = nil + selectedItem.ref:setChecked(false) + selectedItem.ref = nil + + detailsTable:clearData() + buyStatsTable:clearData() + sellStatsTable:clearData() + + Market.enableCreateOffer(false) + end +end + +function Market.isItemSelected() + return selectedItem and selectedItem.item +end + +function Market.isOfferSelected(type) + return selectedOffer[type] and not selectedOffer[type]:isNull() +end + +function Market.getDepotCount(itemId) + if not information.depotItems then + return 0 + end + return information.depotItems[itemId] or 0 +end + +function Market.enableCreateOffer(enable) + offerTypeList:setEnabled(enable) + totalPriceEdit:setEnabled(enable) + piecePriceEdit:setEnabled(enable) + amountEdit:setEnabled(enable) + anonymous:setEnabled(enable) + createOfferButton:setEnabled(enable) + + local prevAmountButton = marketOffersPanel:recursiveGetChildById('prevAmountButton') + local nextAmountButton = marketOffersPanel:recursiveGetChildById('nextAmountButton') + + prevAmountButton:setEnabled(enable) + nextAmountButton:setEnabled(enable) +end + +function Market.close(notify) + if notify == nil then notify = true end + if not marketWindow:isHidden() then + marketWindow:hide() + marketWindow:unlock() + modules.game_interface.getRootPanel():focus() + Market.clearSelectedItem() + Market.reset() + if notify then + MarketProtocol.sendMarketLeave() + end + end +end + +function Market.incrementAmount() + amountEdit:setValue(amountEdit:getValue() + 1) +end + +function Market.decrementAmount() + amountEdit:setValue(amountEdit:getValue() - 1) +end + +function Market.updateCurrentItems() + if not categoryList or not categoryList:getCurrentOption() then + return + end + local id = getMarketCategoryId(categoryList:getCurrentOption().text) + if id == MarketCategory.MetaWeapons then + id = getMarketCategoryId(subCategoryList:getCurrentOption().text) + end + Market.loadMarketItems(id) +end + +function Market.resetCreateOffer(resetFee) + piecePriceEdit:setValue(1) + totalPriceEdit:setValue(1) + amountEdit:setValue(1) + refreshTypeList() + + if resetFee then + clearFee() + else + updateFee(0, 0) + end +end + +function Market.refreshItemsWidget(selectItem) + local selectItem = selectItem or 0 + itemsPanel = browsePanel:recursiveGetChildById('itemsPanel') + + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + Market.clearSelectedItem() + itemsPanel:destroyChildren() + + if radioItemSet then + radioItemSet:destroy() + end + radioItemSet = UIRadioGroup.create() + + local select = nil + for i = 1, #currentItems do + local item = currentItems[i] + local itemBox = g_ui.createWidget('MarketItemBox', itemsPanel) + itemBox.onCheckChange = Market.onItemBoxChecked + itemBox.item = item + + if selectItem > 0 and item.marketData.tradeAs == selectItem then + select = itemBox + selectItem = 0 + end + + local itemWidget = itemBox:getChildById('item') + itemWidget:setItem(item.displayItem) + + local amount = Market.getDepotCount(item.marketData.tradeAs) + if amount > 0 then + itemWidget:setText(comma_value(amount)) + itemBox:setTooltip('You have '.. amount ..' in your depot.') + end + + radioItemSet:addWidget(itemBox) + end + + if select then + radioItemSet:selectWidget(select, false) + end + + layout:enableUpdates() + layout:update() +end + +function Market.refreshOffers() + if Market.isItemSelected() then + Market.onItemBoxChecked(selectedItem.ref) + else + local ctab = offersTabBar:getCurrentTab() + if ctab == myCurrentOffersTab then + Market.refreshMyOffers() + elseif ctab == myOfferHistoryTab then + Market.refreshMyOffersHistory() + end + end +end + +function Market.refreshMyOffers() + clearMyOffers() + MarketProtocol.sendMarketBrowseMyOffers() +end + +function Market.refreshMyOffersHistory() + clearMyOffers() + MarketProtocol.sendMarketBrowseMyHistory() +end + + +function Market.loadMarketItems(category) + clearItems() + + -- check search filter + local searchFilter = searchEdit:getText() + if searchFilter and searchFilter:len() > 2 then + if filterButtons[MarketFilters.SearchAll]:isChecked() then + category = MarketCategory.All + end + end + + if not marketItems[category] and category ~= MarketCategory.All then + return + end + + if category == MarketCategory.All then + -- loop all categories + for category = MarketCategory.First, MarketCategory.Last do + if marketItems[category] then + for i = 1, #marketItems[category] do + local item = marketItems[category][i] + if isItemValid(item, category, searchFilter) then + table.insert(currentItems, item) + end + end + end + end + else + -- loop specific category + for i = 1, #marketItems[category] do + local item = marketItems[category][i] + if isItemValid(item, category, searchFilter) then + table.insert(currentItems, item) + end + end + end + + Market.refreshItemsWidget() +end + +function Market.createNewOffer() + local type = offerTypeList:getCurrentOption().text + if type == 'Sell' then + type = MarketAction.Sell + else + type = MarketAction.Buy + end + + if not Market.isItemSelected() then + return + end + + local spriteId = selectedItem.item.marketData.tradeAs + + local piecePrice = piecePriceEdit:getValue() + local amount = amountEdit:getValue() + local anonymous = anonymous:isChecked() and 1 or 0 + + -- error checking + local errorMsg = '' + if type == MarketAction.Buy then + if information.balance < ((piecePrice * amount) + fee) then + errorMsg = errorMsg..'Not enough balance to create this offer.\n' + end + elseif type == MarketAction.Sell then + if information.balance < fee then + errorMsg = errorMsg..'Not enough balance to create this offer.\n' + end + if Market.getDepotCount(spriteId) < amount then + errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n' + end + end + + if piecePrice > piecePriceEdit.maximum then + errorMsg = errorMsg..'Price is too high.\n' + elseif piecePrice < piecePriceEdit.minimum then + errorMsg = errorMsg..'Price is too low.\n' + end + + if amount > amountEdit.maximum then + errorMsg = errorMsg..'Amount is too high.\n' + elseif amount < amountEdit.minimum then + errorMsg = errorMsg..'Amount is too low.\n' + end + + if amount * piecePrice > MarketMaxPrice then + errorMsg = errorMsg..'Total price is too high.\n' + end + + if information.totalOffers >= MarketMaxOffers then + errorMsg = errorMsg..'You cannot create more offers.\n' + end + + local timeCheck = os.time() - lastCreatedOffer + if timeCheck < offerExhaust[type] then + local waitTime = math.ceil(offerExhaust[type] - timeCheck) + errorMsg = errorMsg..'You must wait '.. waitTime ..' seconds before creating a new offer.\n' + end + + if errorMsg ~= '' then + Market.displayMessage(errorMsg) + return + end + + MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, piecePrice, anonymous) + lastCreatedOffer = os.time() + Market.resetCreateOffer() +end + +function Market.acceptMarketOffer(amount, timestamp, counter) + if timestamp > 0 and amount > 0 then + MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount) + Market.refreshOffers() + end +end + +function Market.onItemBoxChecked(widget) + if widget:isChecked() then + updateSelectedItem(widget) + end +end + +-- protocol callback functions + +function Market.onMarketEnter(depotItems, offers, balance, vocation, items) + if not loaded or (items and #items > 0) then + initMarketItems(items) + loaded = true + end + + updateBalance(balance) + averagePrice = 0 + + information.totalOffers = offers + local player = g_game.getLocalPlayer() + if player then + information.player = player + end + if vocation == -1 then + if player then + information.vocation = player:getVocation() + end + else + -- vocation must be compatible with < 950 + information.vocation = vocation + end + + -- set list of depot items + information.depotItems = depotItems + + for i = 1, #marketItems[MarketCategory.TibiaCoins] do + local item = marketItems[MarketCategory.TibiaCoins][i].displayItem + depotItems[item:getId()] = tibiaCoins + end + + -- update the items widget to match depot items + if Market.isItemSelected() then + local spriteId = selectedItem.item.marketData.tradeAs + MarketProtocol.silent(true) -- disable protocol messages + Market.refreshItemsWidget(spriteId) + MarketProtocol.silent(false) -- enable protocol messages + else + Market.refreshItemsWidget() + end + + if table.empty(currentItems) then + Market.loadMarketItems(MarketCategory.First) + end + + if g_game.isOnline() then + marketWindow:lock() + marketWindow:show() + end +end + +function Market.onMarketLeave() + Market.close(false) +end + +function Market.onMarketDetail(itemId, descriptions, purchaseStats, saleStats) + updateDetails(itemId, descriptions, purchaseStats, saleStats) +end + +function Market.onMarketBrowse(offers, offersType) + if offersType == MarketRequest.MyHistory then + updateHistoryOffers(offers) + else + updateOffers(offers) + end +end + +function Market.onCoinBalance(coins, transferableCoins) + tibiaCoins = coins + if not marketItems[MarketCategory.TibiaCoins] then return end + for i = 1, #marketItems[MarketCategory.TibiaCoins] do + local item = marketItems[MarketCategory.TibiaCoins][i].displayItem + depotItems[item:getId()] = tibiaCoins + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otmod new file mode 100644 index 0000000..4d6248a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otmod @@ -0,0 +1,9 @@ +Module + name: game_market + description: Global item market system + author: BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ offerstatistic, marketoffer, marketprotocol, market ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otui new file mode 100644 index 0000000..ae45044 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/market.otui @@ -0,0 +1,62 @@ +MarketWindow < MainWindow + id: marketWindow + !text: tr('Market') + size: 700 530 + + @onEscape: Market.close() + + // Main Panel Window + + MarketTabBar + id: mainTabBar + width: 164 + height: 25 + anchors.top: parent.top + anchors.left: parent.left + + Panel + id: mainTabContent + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + padding: 3 + border-width: 1 + border-color: #000000 + margin-bottom: 20 + + Label + id: balanceLabel + !text: tr('Balance') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.right: parent.right + + Button + id: closeButton + !text: tr('Close') + anchors.top: mainTabContent.bottom + anchors.horizontalCenter: mainTabContent.horizontalCenter + margin-top: 5 + width: 110 + @onClick: Market.close() + + Button + id: refreshOffersButton + !text: tr('Refresh Offers') + anchors.top: mainTabContent.bottom + anchors.right: mainTabContent.right + margin-top: 5 + width: 110 + @onClick: Market.refreshOffers() + + Button + id: resetButton + !text: tr('Reset Market') + !tooltip: tr('Reset selection, filters & search') + anchors.top: mainTabContent.bottom + anchors.left: mainTabContent.left + margin-top: 5 + width: 110 + @onClick: Market.reset() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketoffer.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketoffer.lua new file mode 100644 index 0000000..b2dcbfa --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketoffer.lua @@ -0,0 +1,158 @@ +MarketOffer = {} +MarketOffer.__index = MarketOffer + +local OFFER_TIMESTAMP = 1 +local OFFER_COUNTER = 2 + +MarketOffer.new = function(offerId, t, item, amount, price, playerName, state, var) + local offer = { + id = {}, + type = nil, + item = 0, + amount = 0, + price = 0, + player = '', + state = 0, + var = nil + } + + if not offerId or type(offerId) ~= 'table' then + g_logger.error('MarketOffer.new - invalid offer id provided.') + end + offer.id = offerId + + t = tonumber(t) + if t ~= MarketAction.Buy and t ~= MarketAction.Sell then + g_logger.error('MarketOffer.new - invalid type provided.') + end + offer.type = t + + if not item then + g_logger.error('MarketOffer.new - invalid item provided.') + end + offer.item = item + + offer.amount = amount + offer.price = price + offer.player = playerName + + state = tonumber(state) + if state ~= MarketOfferState.Active and state ~= MarketOfferState.Cancelled + and state ~= MarketOfferState.Expired and state ~= MarketOfferState.Accepted then + g_logger.error('MarketOffer.new - invalid state provided.') + end + offer.state = state + offer.var = var + + setmetatable(offer, MarketOffer) + return offer +end + +function MarketOffer:isEqual(id) + return self.id[OFFER_TIMESTAMP] == id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] == id[OFFER_COUNTER] +end + +function MarketOffer:isLessThan(id) + return self.id[OFFER_TIMESTAMP] <= id[OFFER_TIMESTAMP] and self.id[OFFER_COUNTER] < id[OFFER_COUNTER] +end + +function MarketOffer:isNull() + return table.empty(self.id) +end + +-- Sets/Gets + +function MarketOffer:setId(id) + if not id or type(id) ~= 'table' then + g_logger.error('MarketOffer.setId - invalid id provided.') + end + self.id = id +end + +function MarketOffer:getId() + return self.id +end + +function MarketOffer:setType(t) + if not t or type(t) ~= 'number' then + g_logger.error('MarketOffer.setItem - invalid type provided.') + end + self.type = type +end + +function MarketOffer:getType() + return self.type +end + +function MarketOffer:setItem(item) + if not item or type(item) ~= 'userdata' then + g_logger.error('MarketOffer.setItem - invalid item id provided.') + end + self.item = item +end + +function MarketOffer:getItem() + return self.item +end + +function MarketOffer:setAmount(amount) + if not amount or type(amount) ~= 'number' then + g_logger.error('MarketOffer.setAmount - invalid amount provided.') + end + self.amount = amount +end + +function MarketOffer:getAmount() + return self.amount +end + +function MarketOffer:setPrice(price) + if not price or type(price) ~= 'number' then + g_logger.error('MarketOffer.setPrice - invalid price provided.') + end + self.price = price +end + +function MarketOffer:getPrice() + return self.price +end + +function MarketOffer:getTotalPrice() + return self.price * self.amount +end + +function MarketOffer:setPlayer(player) + if not player or type(player) ~= 'number' then + g_logger.error('MarketOffer.setPlayer - invalid player provided.') + end + self.player = player +end + +function MarketOffer:getPlayer() + return self.player +end + +function MarketOffer:setState(state) + if not state or type(state) ~= 'number' then + g_logger.error('MarketOffer.setState - invalid state provided.') + end + self.state = state +end + +function MarketOffer:getState() + return self.state +end + +function MarketOffer:getTimeStamp() + if table.empty(self.id) or #self.id < OFFER_TIMESTAMP then + return + end + return self.id[OFFER_TIMESTAMP] +end + +function MarketOffer:getCounter() + if table.empty(self.id) or #self.id < OFFER_COUNTER then + return + end + return self.id[OFFER_COUNTER] +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketprotocol.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketprotocol.lua new file mode 100644 index 0000000..7fcbbe1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/marketprotocol.lua @@ -0,0 +1,278 @@ +MarketProtocol = {} + +-- private functions + +local silent +local protocol +local statistics = runinsandbox('offerstatistic') + +local function send(msg) + if protocol and not silent then + protocol:send(msg) + end +end + +local function readMarketOffer(msg, action, var) + local timestamp = msg:getU32() + local counter = msg:getU16() + + local itemId = 0 + if var == MarketRequest.MyOffers or var == MarketRequest.MyHistory then + itemId = msg:getU16() + else + itemId = var + end + + local amount = msg:getU16() + local price = msg:getU32() + local playerName + local state = MarketOfferState.Active + if var == MarketRequest.MyHistory then + state = msg:getU8() + elseif var == MarketRequest.MyOffers then + else + playerName = msg:getString() + end + + return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state, var) +end + +-- parsing protocols +local function parseMarketEnter(protocol, msg) + local items + if g_game.getClientVersion() < 944 then + items = {} + local itemsCount = msg:getU16() + for i = 1, itemsCount do + local itemId = msg:getU16() + local category = msg:getU8() + local name = msg:getString() + table.insert(items, { + id = itemId, + category = category, + name = name + }) + end + end + + local balance = 0 + if g_game.getClientVersion() <= 1250 or not g_game.getFeature(GameTibia12Protocol) then + if g_game.getClientVersion() >= 981 or g_game.getClientVersion() < 944 then + balance = msg:getU64() + else + balance = msg:getU32() + end + end + + local vocation = -1 + if g_game.getClientVersion() >= 944 and g_game.getClientVersion() < 950 then + vocation = msg:getU8() -- get vocation id + end + local offers = msg:getU8() + + local depotItems = {} + local depotCount = msg:getU16() + for i = 1, depotCount do + local itemId = msg:getU16() -- item id + local itemCount = msg:getU16() -- item count + + depotItems[itemId] = itemCount + end + + signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation, items) + return true +end + +local function parseMarketLeave(protocol, msg) + Market.onMarketLeave() + return true +end + +local function parseMarketDetail(protocol, msg) + local itemId = msg:getU16() + + local descriptions = {} + for i = MarketItemDescription.First, MarketItemDescription.Last do + if msg:peekU16() ~= 0x00 then + table.insert(descriptions, {i, msg:getString()}) -- item descriptions + else + msg:getU16() + end + end + + if g_game.getClientVersion() >= 1100 then -- imbuements + if msg:peekU16() ~= 0x00 then + table.insert(descriptions, {MarketItemDescription.Last + 1, msg:getString()}) + else + msg:getU16() + end + end + + local time = (os.time() / 1000) * statistics.SECONDS_PER_DAY; + + local purchaseStats = {} + local count = msg:getU8() + for i=1, count do + local transactions = msg:getU32() -- transaction count + local totalPrice = msg:getU32() -- total price + local highestPrice = msg:getU32() -- highest price + local lowestPrice = msg:getU32() -- lowest price + + local tmp = time - statistics.SECONDS_PER_DAY + table.insert(purchaseStats, OfferStatistic.new(tmp, MarketAction.Buy, transactions, totalPrice, highestPrice, lowestPrice)) + end + + local saleStats = {} + count = msg:getU8() + for i=1, count do + local transactions = msg:getU32() -- transaction count + local totalPrice = msg:getU32() -- total price + local highestPrice = msg:getU32() -- highest price + local lowestPrice = msg:getU32() -- lowest price + + local tmp = time - statistics.SECONDS_PER_DAY + table.insert(saleStats, OfferStatistic.new(tmp, MarketAction.Sell, transactions, totalPrice, highestPrice, lowestPrice)) + end + + signalcall(Market.onMarketDetail, itemId, descriptions, purchaseStats, saleStats) + return true +end + +local function parseMarketBrowse(protocol, msg) + local var = msg:getU16() + local offers = {} + + local buyOfferCount = msg:getU32() + for i = 1, buyOfferCount do + table.insert(offers, readMarketOffer(msg, MarketAction.Buy, var)) + end + + local sellOfferCount = msg:getU32() + for i = 1, sellOfferCount do + table.insert(offers, readMarketOffer(msg, MarketAction.Sell, var)) + end + + signalcall(Market.onMarketBrowse, offers, var) + return true +end + +-- public functions +function initProtocol() + connect(g_game, { onGameStart = MarketProtocol.registerProtocol, + onGameEnd = MarketProtocol.unregisterProtocol }) + + -- reloading module + if g_game.isOnline() then + MarketProtocol.registerProtocol() + end + + MarketProtocol.silent(false) +end + +function terminateProtocol() + disconnect(g_game, { onGameStart = MarketProtocol.registerProtocol, + onGameEnd = MarketProtocol.unregisterProtocol }) + + -- reloading module + MarketProtocol.unregisterProtocol() + MarketProtocol = nil +end + +function MarketProtocol.updateProtocol(_protocol) + protocol = _protocol +end + +function MarketProtocol.registerProtocol() + if g_game.getFeature(GamePlayerMarket) then + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) + ProtocolGame.registerOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + end + MarketProtocol.updateProtocol(g_game.getProtocolGame()) +end + +function MarketProtocol.unregisterProtocol() + if g_game.getFeature(GamePlayerMarket) then + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketEnter, parseMarketEnter) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketLeave, parseMarketLeave) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketDetail, parseMarketDetail) + ProtocolGame.unregisterOpcode(GameServerOpcodes.GameServerMarketBrowse, parseMarketBrowse) + end + MarketProtocol.updateProtocol(nil) +end + +function MarketProtocol.silent(mode) + silent = mode +end + +-- sending protocols + +function MarketProtocol.sendMarketLeave() + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketLeave) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketLeave does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketBrowse(browseId) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketBrowse) + msg:addU16(browseId) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketBrowse does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketBrowseMyOffers() + MarketProtocol.sendMarketBrowse(MarketRequest.MyOffers) +end + +function MarketProtocol.sendMarketBrowseMyHistory() + MarketProtocol.sendMarketBrowse(MarketRequest.MyHistory) +end + +function MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, price, anonymous) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketCreate) + msg:addU8(type) + msg:addU16(spriteId) + msg:addU16(amount) + msg:addU32(price) + msg:addU8(anonymous) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketCreateOffer does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketCancelOffer(timestamp, counter) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketCancel) + msg:addU32(timestamp) + msg:addU16(counter) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketCancelOffer does not support the current protocol.') + end +end + +function MarketProtocol.sendMarketAcceptOffer(timestamp, counter, amount) + if g_game.getFeature(GamePlayerMarket) then + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientMarketAccept) + msg:addU32(timestamp) + msg:addU16(counter) + msg:addU16(amount) + send(msg) + else + g_logger.error('MarketProtocol.sendMarketAcceptOffer does not support the current protocol.') + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/offerstatistic.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/offerstatistic.lua new file mode 100644 index 0000000..859679b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/offerstatistic.lua @@ -0,0 +1,101 @@ +OfferStatistic = {} +OfferStatistic.__index = OfferStatistic + +SECONDS_PER_DAY = 86400 + +OfferStatistic.new = function(timestamp, t, transactions, totalPrice, highestPrice, lowestPrice) + local stat = { + time = 0, + type = nil, + transactions = 0, + totalPrice = 0, + highestPrice = 0, + lowestPrice = 0 + } + stat.time = math.floor(timestamp / SECONDS_PER_DAY) * SECONDS_PER_DAY + + if t ~= MarketAction.Buy and t ~= MarketAction.Sell then + g_logger.error('OfferStatistic.new - invalid type provided.') + end + stat.type = t + + stat.transactions = transactions + stat.totalPrice = totalPrice + stat.highestPrice = highestPrice + stat.lowestPrice = lowestPrice + + setmetatable(stat, OfferStatistic) + return stat +end + +function OfferStatistic:isNull() + return self.time == 0 or not self.type +end + +-- Sets/Gets + +function OfferStatistic:setTime(time) + if not time or type(time) ~= 'number' then + g_logger.error('OfferStatistic.setTime - invalid time provided.') + end + self.time = time +end + +function OfferStatistic:getTime() + return self.time +end + +function OfferStatistic:setType(t) + if not t or type(t) ~= 'number' then + g_logger.error('OfferStatistic.setType - invalid type provided.') + end + self.type = t +end + +function OfferStatistic:getType() + return self.type +end + +function OfferStatistic:setTransactions(transactions) + if not transactions or type(transactions) ~= 'number' then + g_logger.error('OfferStatistic.setTransactions - invalid transactions provided.') + end + self.transactions = transactions +end + +function OfferStatistic:getTransactions() + return self.transactions +end + +function OfferStatistic:setTotalPrice(amount) + if not totalPrice or type(totalPrice) ~= 'number' then + g_logger.error('OfferStatistic.setTotalPrice - invalid total price provided.') + end + self.totalPrice = totalPrice +end + +function OfferStatistic:getTotalPrice() + return self.totalPrice +end + +function OfferStatistic:setHighestPrice(highestPrice) + if not highestPrice or type(highestPrice) ~= 'number' then + g_logger.error('OfferStatistic.setHighestPrice - invalid highestPrice provided.') + end + self.highestPrice = highestPrice +end + +function OfferStatistic:getHighestPrice() + return self.highestPrice +end + +function OfferStatistic:setLowestPrice(lowestPrice) + if not lowestPrice or type(lowestPrice) ~= 'number' then + g_logger.error('OfferStatistic.setLowestPrice - invalid lowestPrice provided.') + end + self.lowestPrice = lowestPrice +end + +function OfferStatistic:getLowestPrice() + return self.lowestPrice +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/amountwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/amountwindow.otui new file mode 100644 index 0000000..c4a2d25 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/amountwindow.otui @@ -0,0 +1,44 @@ +AmountWindow < MainWindow + id: amountWindow + !text: tr('Amount') + size: 270 90 + + Item + id: item + text-offset: 0 22 + text-align: right + anchors.left: parent.left + anchors.top: parent.top + margin-top: 2 + margin-left: -4 + focusable: false + virtual: true + + HorizontalScrollBar + id: amountScrollBar + anchors.left: prev.right + anchors.right: parent.right + anchors.top: prev.top + margin-left: 10 + margin-top: -2 + + Button + id: buttonCancel + !text: tr('Cancel') + height: 20 + anchors.left: amountScrollBar.horizontalCenter + anchors.right: amountScrollBar.right + anchors.top: amountScrollBar.bottom + margin-top: 7 + focusable: false + + Button + id: buttonOk + !text: tr('Ok') + height: 20 + anchors.right: amountScrollBar.horizontalCenter + anchors.left: amountScrollBar.left + anchors.top: amountScrollBar.bottom + margin-top: 7 + margin-right: 6 + focusable: false diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketbuttons.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketbuttons.otui new file mode 100644 index 0000000..4533070 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketbuttons.otui @@ -0,0 +1,13 @@ +MarketButtonBox < ButtonBoxRounded + font: verdana-11px-rounded + color: #f55e5ebb + size: 106 22 + text-offset: 0 2 + text-align: center + + $checked: + color: white + + $disabled: + color: #666666ff + image-color: #ffffff88 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketcombobox.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketcombobox.otui new file mode 100644 index 0000000..8009483 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/marketcombobox.otui @@ -0,0 +1,18 @@ +MarketComboBoxPopupMenuButton < ComboBoxPopupMenuButton + height: 18 + font: verdana-11px-rounded + text-offset: 2 2 + +MarketComboBoxPopupMenuSeparator < UIWidget + image-source: /images/combobox_rounded + image-repeated: true + image-clip: 1 59 89 1 + height: 1 + phantom: true + +MarketComboBoxPopupMenu < ComboBoxPopupMenu + +MarketComboBox < ComboBox + font: verdana-11px-rounded + size: 86 20 + text-offset: 3 2 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/markettabs.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/markettabs.otui new file mode 100644 index 0000000..dbc6ca1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/general/markettabs.otui @@ -0,0 +1,44 @@ +MarketTabBar < TabBar +MarketTabBarPanel < TabBarPanel +MarketTabBarButton < TabBarButton + size: 20 25 + font: verdana-11px-rounded + text-offset: 0 2 + + $!first: + anchors.left: prev.right + margin-left: 0 + + $hover !checked: + color: #ffffff + + $checked: + color: #ffffff + + $on !checked: + color: #f55e5e + +MarketRightTabBar < TabBar +MarketRightTabBarPanel < TabBarPanel +MarketRightTabBarButton < TabBarButton + size: 20 25 + font: verdana-11px-rounded + text-offset: 0 2 + color: #929292 + + $first: + anchors.right: parent.right + anchors.left: none + + $!first: + anchors.right: prev.left + anchors.left: none + + $hover !checked: + color: #ffffff + + $checked: + color: #ffffff + + $on !checked: + color: #f55e5e diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers.otui new file mode 100644 index 0000000..f8e04f8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers.otui @@ -0,0 +1,188 @@ +Panel + + MarketTabBar + id: leftTabBar + width: 107 + height:25 + anchors.top: parent.top + anchors.left: parent.left + + Panel + id: leftTabContent + width: 180 + anchors.top: prev.bottom + anchors.left: prev.left + anchors.bottom: parent.bottom + border-width: 1 + border-color: #000000 + + MarketRightTabBar + id: rightTabBar + width: 166 + height:25 + anchors.top: parent.top + anchors.right: parent.right + + Panel + id: rightTabContent + anchors.top: prev.bottom + anchors.left: leftTabContent.right + anchors.right: prev.right + anchors.bottom: parent.bottom + margin-left:3 + border-width: 1 + border-color: #000000 + + UIItem + id: selectedItem + phantom: true + size: 34 34 + padding: 1 + font: verdana-11px-rounded + border-color: white + anchors.top: rightTabBar.bottom + anchors.left: rightTabContent.left + margin-top: 6 + margin-left: 6 + + Label + id: nameLabel + !text: tr('No item selected.') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-left: 5 + + Label + id: createLabel + !text: tr('Create New Offer') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: rightTabBar.top + anchors.left: rightTabContent.left + margin-top: 355 + margin-left: 6 + + Label + id: offerTypeLabel + !text: tr('Offer Type') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 7 + + MarketComboBox + id: offerTypeComboBox + !text: tr('Please Select') + anchors.top: prev.bottom + anchors.left: createLabel.left + margin-top: 3 + width: 105 + + $disabled: + color: #aaaaaa44 + + Label + id: totalPriceLabel + !text: tr('Total Price') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: totalPriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 999999999 + focusable: true + + $disabled: + color: #aaaaaa44 + + Label + id: piecePriceLabel + !text: tr('Piece Price') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: prev.right + margin-left: 7 + + SpinBox + id: piecePriceEdit + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 3 + width: 75 + minimum: 1 + maximum: 999999999 + focusable: true + + $disabled: + color: #aaaaaa44 + + Label + id: amountLabel + !text: tr('Amount') .. ':' + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: offerTypeLabel.top + anchors.left: amountEdit.left + + PreviousButton + id: prevAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: piecePriceEdit.right + margin-left: 7 + @onClick: Market.decrementAmount() + + SpinBox + id: amountEdit + anchors.top: prev.top + anchors.left: prev.right + margin-left: 3 + width: 55 + buttons: false + minimum: 1 + maximum: 64000 + focusable: true + + NextButton + id: nextAmountButton + anchors.verticalCenter: piecePriceEdit.verticalCenter + anchors.left: prev.right + margin-left: 3 + @onClick: Market.incrementAmount() + + Button + id: createOfferButton + !text: tr('Create Offer') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 7 + width: 90 + + CheckBox + id: anonymousCheckBox + !text: tr('Anonymous') + anchors.left: prev.left + anchors.bottom: prev.top + margin-bottom: 6 + @onSetup: self:setChecked(false) + height: 16 + width: 90 + + Label + id: feeLabel + font: verdana-11px-rounded + anchors.top: createOfferButton.bottom + anchors.left: createOfferButton.left + margin: 2 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/browse.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/browse.otui new file mode 100644 index 0000000..9071697 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/browse.otui @@ -0,0 +1,158 @@ +MarketItemBox < UICheckBox + id: itemBox + border-width: 1 + border-color: #000000 + color: #aaaaaa + text-align: center + + Item + id: item + phantom: true + virtual: true + text-offset: 0 22 + text-align: right + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin: 1 + + $checked: + border-color: #ffffff + + $hover !checked: + border-color: #aaaaaa + + $disabled: + image-color: #ffffff88 + color: #aaaaaa88 + +Panel + background-color: #22283399 + margin: 1 + + MarketComboBox + id: categoryComboBox + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + margin-right: 3 + margin-left: 3 + + MarketComboBox + id: subCategoryComboBox + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + margin-right: 3 + margin-left: 3 + + $disabled: + color: #aaaaaa44 + + MarketButtonBox + id: filterLevel + &default: false + !text: tr('Level') + !tooltip: tr('Filter list to match your level') + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 3 + margin-right: 3 + margin-left: 3 + width: 40 + height: 20 + + MarketButtonBox + id: filterVocation + &default: false + !text: tr('Voc.') + !tooltip: tr('Filter list to match your vocation') + anchors.top: prev.top + anchors.left: prev.right + margin-right: 3 + margin-left: 3 + width: 34 + height: 20 + + MarketComboBox + id: slotComboBox + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin-right: 3 + margin-left: 3 + + $disabled: + color: #aaaaaa44 + + MarketButtonBox + id: filterDepot + &default: false + !text: tr('Show Depot Only') + !tooltip: tr('Show your depot items only') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 6 + margin-right: 3 + margin-left: 3 + + Panel + id: itemsContainer + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-top: 10 + margin-left: 3 + margin-bottom: 30 + margin-right: 3 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 28 + pixels-scroll: true + + ScrollablePanel + id: itemsPanel + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + layout: + type: grid + cell-size: 36 36 + flow: true + auto-spacing: true + + Label + !text: tr('Find') .. ':' + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 9 + width: 30 + font: verdana-11px-rounded + text-offset: 0 2 + + TextEdit + id: searchEdit + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + margin-left: 3 + width: 113 + @onTextChange: Market.updateCurrentItems() + + MarketButtonBox + id: filterSearchAll + &default: true + !text: tr('All') + !tooltip: tr('Search all items') + anchors.verticalCenter: prev.verticalCenter + anchors.left: prev.right + anchors.right: itemsContainer.right + margin-left: 3 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemdetails.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemdetails.otui new file mode 100644 index 0000000..d90dbee --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemdetails.otui @@ -0,0 +1,56 @@ +DetailsTableRow < TableRow + font: verdana-11px-monochrome + focusable: true + color: #cccccc + height: 45 + focusable: false + padding: 2 + even-background-color: alpha + odd-background-color: alpha + +DetailsTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 2 + color: #cccccc + width: 100 + focusable: false + +Panel + background-color: #22283399 + margin: 1 + + Table + id: detailsTable + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 63 + margin-left: 6 + margin-bottom: 85 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: detailsTableData + row-style: DetailsTableRow + column-style: DetailsTableColumn + + TableData + id: detailsTableData + anchors.top: detailsTable.top + anchors.bottom: detailsTable.bottom + anchors.left: detailsTable.left + anchors.right: detailsTable.right + vertical-scrollbar: detailsTableScrollBar + + VerticalScrollBar + id: detailsTableScrollBar + anchors.top: detailsTable.top + anchors.bottom: detailsTable.bottom + anchors.right: detailsTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemoffers.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemoffers.otui new file mode 100644 index 0000000..8f39260 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemoffers.otui @@ -0,0 +1,176 @@ +OfferTableRow < TableRow + font: verdana-11px-monochrome + color: #cccccc + height: 15 + +OfferTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 0 + color: #cccccc + width: 80 + +OfferTableWarningColumn < OfferTableColumn + color: #e03d3d + +OfferTableHeaderRow < TableHeaderRow + font: verdana-11px-monochrome + color: #cccccc + height: 20 + +OfferTableHeaderColumn < SortableTableHeaderColumn + font: verdana-11px-monochrome + text-offset: 2 0 + color: #cccccc + + $focus: + background-color: #294f6d + color: #ffffff + +Panel + background-color: #22283399 + margin: 1 + + Button + id: buyButton + !text: tr('Buy Now') + anchors.right: parent.right + anchors.bottom: next.bottom + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 44 + margin-left: 6 + + Table + id: sellingTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: parent.right + height: 115 + margin-top: 5 + margin-bottom: 5 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: sellingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Buyer Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: sellingTableData + anchors.bottom: sellingTable.bottom + anchors.left: sellingTable.left + anchors.right: sellingTable.right + margin-top: 2 + vertical-scrollbar: sellingTableScrollBar + + VerticalScrollBar + id: sellingTableScrollBar + anchors.top: sellingTable.top + anchors.bottom: sellingTable.bottom + anchors.right: sellingTable.right + step: 28 + pixels-scroll: true + + Button + id: sellButton + !text: tr('Sell Now') + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.top + anchors.left: parent.left + margin-top: 9 + margin-left: 6 + + Table + id: buyingTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: parent.right + margin-top: 5 + margin-bottom: 5 + margin-right: 6 + height: 115 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: buyingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Seller Name') + width: 100 + OfferTableHeaderColumn + !text: tr('Amount') + width: 60 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 90 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 80 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: buyingTableData + anchors.bottom: buyingTable.bottom + anchors.left: buyingTable.left + anchors.right: buyingTable.right + vertical-scrollbar: buyingTableScrollBar + + VerticalScrollBar + id: buyingTableScrollBar + anchors.top: buyingTable.top + anchors.bottom: buyingTable.bottom + anchors.right: buyingTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemstats.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemstats.otui new file mode 100644 index 0000000..61afa97 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/itemstats.otui @@ -0,0 +1,103 @@ +StatsTableRow < TableRow + font: verdana-11px-monochrome + focusable: true + color: #cccccc + height: 20 + focusable: false + +StatsTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 3 + color: #cccccc + width: 110 + focusable: false + +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 44 + margin-left: 6 + + Table + id: buyStatsTable + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + margin-top: 6 + margin-bottom: 5 + margin-right: 6 + height: 121 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: buyStatsTableData + row-style: StatsTableRow + column-style: StatsTableColumn + + TableData + id: buyStatsTableData + anchors.top: buyStatsTable.top + anchors.bottom: buyStatsTable.bottom + anchors.left: buyStatsTable.left + anchors.right: buyStatsTable.right + vertical-scrollbar: buyStatsTableScrollBar + + VerticalScrollBar + id: buyStatsTableScrollBar + anchors.top: buyStatsTable.top + anchors.bottom: buyStatsTable.bottom + anchors.right: buyStatsTable.right + step: 28 + pixels-scroll: true + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: buyStatsTable.bottom + anchors.left: parent.left + margin-top: 9 + margin-left: 6 + + Table + id: sellStatsTable + anchors.top: prev.bottom + anchors.left: buyStatsTable.left + anchors.right: buyStatsTable.right + margin-top: 6 + height: 112 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: sellStatsTableData + row-style: StatsTableRow + column-style: StatsTableColumn + + TableData + id: sellStatsTableData + anchors.top: sellStatsTable.top + anchors.bottom: sellStatsTable.bottom + anchors.left: sellStatsTable.left + anchors.right: sellStatsTable.right + vertical-scrollbar: sellStatsTableScrollBar + + VerticalScrollBar + id: sellStatsTableScrollBar + anchors.top: sellStatsTable.top + anchors.bottom: sellStatsTable.bottom + anchors.right: sellStatsTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/overview.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/overview.otui new file mode 100644 index 0000000..7e9cccb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/marketoffers/overview.otui @@ -0,0 +1,16 @@ +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Reserved for more functionality later.') + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 6 + margin-left: 6 + margin-right: 6 + font: verdana-11px-rounded + text-offset: 0 2 + height: 50 + text-wrap: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers.otui new file mode 100644 index 0000000..cfa39d0 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers.otui @@ -0,0 +1,16 @@ +Panel + + MarketTabBar + id: offersTabBar + width: 187 + height:25 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Panel + id: offersTabContent + anchors.top: prev.bottom + anchors.left: prev.left + anchors.right: prev.right + anchors.bottom: parent.bottom diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/currentoffers.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/currentoffers.otui new file mode 100644 index 0000000..6c53dac --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/currentoffers.otui @@ -0,0 +1,178 @@ +OfferTableRow < TableRow + font: verdana-11px-monochrome + color: #cccccc + height: 15 + +OfferTableColumn < TableColumn + font: verdana-11px-monochrome + background-color: alpha + text-offset: 5 0 + color: #cccccc + width: 80 + +OfferTableWarningColumn < OfferTableColumn + color: #e03d3d + +OfferTableHeaderRow < TableHeaderRow + font: verdana-11px-monochrome + color: #cccccc + height: 20 + +OfferTableHeaderColumn < SortableTableHeaderColumn + font: verdana-11px-monochrome + text-offset: 2 0 + color: #cccccc + + $focus: + background-color: #294f6d + color: #ffffff + +Panel + background-color: #22283399 + margin: 1 + + Button + id: sellCancelButton + !text: tr('Cancel') + anchors.right: parent.right + anchors.bottom: next.bottom + margin-right: 6 + width: 80 + enabled: false + + Label + !text: tr('Sell Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 20 + margin-left: 6 + + Table + id: mySellingTable + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 160 + margin-top: 5 + margin-bottom: 5 + margin-left: 6 + margin-right: 6 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: mySellingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Item Name') + width: 160 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Amount') + width: 100 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: mySellingTableData + anchors.bottom: mySellingTable.bottom + anchors.left: mySellingTable.left + anchors.right: mySellingTable.right + margin-top: 2 + vertical-scrollbar: mySellingTableScrollBar + + VerticalScrollBar + id: mySellingTableScrollBar + anchors.top: mySellingTable.top + anchors.bottom: mySellingTable.bottom + anchors.right: mySellingTable.right + step: 28 + pixels-scroll: true + + Label + !text: tr('Buy Offers') + font: verdana-11px-rounded + text-offset: 0 2 + anchors.top: prev.bottom + anchors.left: parent.left + margin-top: 20 + margin-left: 6 + + Button + id: buyCancelButton + !text: tr('Cancel') + anchors.right: parent.right + anchors.bottom: prev.bottom + margin-top: 5 + margin-right: 6 + width: 80 + enabled: false + + Table + id: myBuyingTable + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + margin-bottom: 5 + margin-left: 6 + margin-right: 6 + height: 160 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: myBuyingTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Item Name') + width: 160 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 125 + OfferTableHeaderColumn + !text: tr('Amount') + width: 100 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: myBuyingTableData + anchors.bottom: myBuyingTable.bottom + anchors.left: myBuyingTable.left + anchors.right: myBuyingTable.right + vertical-scrollbar: myBuyingTableScrollBar + + VerticalScrollBar + id: myBuyingTableScrollBar + anchors.top: myBuyingTable.top + anchors.bottom: myBuyingTable.bottom + anchors.right: myBuyingTable.right + step: 28 + pixels-scroll: true diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/itemoffers.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/itemoffers.otui new file mode 100644 index 0000000..c649ce8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/itemoffers.otui @@ -0,0 +1,9 @@ +Panel + background-color: #22283399 + margin: 1 + + Label + !text: tr('Item Offers') + anchors.top: parent.top + anchors.left: parent.left + margin-left: 10 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/offerhistory.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/offerhistory.otui new file mode 100644 index 0000000..3f8380d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_market/ui/myoffers/offerhistory.otui @@ -0,0 +1,61 @@ +Panel + background-color: #22283399 + margin: 1 + + Table + id: myHistoryTable + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 390 + margin-top: 5 + margin-bottom: 5 + margin-left: 8 + margin-right: 8 + padding: 1 + focusable: false + background-color: #222833 + border-width: 1 + border-color: #191f27 + table-data: myHistoryTableData + row-style: OfferTableRow + column-style: OfferTableColumn + header-column-style: false + header-row-style: false + + OfferTableHeaderRow + id: header + OfferTableHeaderColumn + !text: tr('Action') + width: 60 + OfferTableHeaderColumn + !text: tr('Item Name') + width: 140 + OfferTableHeaderColumn + !text: tr('Total Price') + width: 115 + OfferTableHeaderColumn + !text: tr('Piece Price') + width: 115 + OfferTableHeaderColumn + !text: tr('Amount') + width: 75 + OfferTableHeaderColumn + !text: tr('Auction End') + width: 120 + + TableData + id: myHistoryTableData + anchors.bottom: myHistoryTable.bottom + anchors.left: myHistoryTable.left + anchors.right: myHistoryTable.right + margin-top: 2 + vertical-scrollbar: myHistoryTableScrollBar + + VerticalScrollBar + id: myHistoryTableScrollBar + anchors.top: myHistoryTable.top + anchors.bottom: myHistoryTable.bottom + anchors.right: myHistoryTable.right + step: 28 + pixels-scroll: true \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/flagwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/flagwindow.otui new file mode 100644 index 0000000..c0fac4a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/flagwindow.otui @@ -0,0 +1,188 @@ +FlagButton < CheckBox + size: 15 15 + margin-left: 2 + image-source: /images/game/minimap/flagcheckbox + image-size: 15 15 + image-border: 3 + icon-source: /images/game/minimap/mapflags + icon-size: 11 11 + icon-clip: 0 0 11 11 + icon-offset: 2 4 + text: + + $!checked: + image-clip: 26 0 26 26 + + $hover !checked: + image-clip: 78 0 26 26 + + $checked: + image-clip: 0 0 26 26 + + $hover checked: + image-clip: 52 0 26 26 + + +FlagWindow < MainWindow + id: flagWindow + !text: tr('Create Map Mark') + size: 196 185 + + Label + id: position + !text: tr('Position') .. ':' + text-auto-resize: true + anchors.top: parent.top + anchors.left: parent.left + margin-top: 2 + + Label + !text: tr('Description') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 7 + + TextEdit + id: description + margin-top: 3 + anchors.left: parent.left + anchors.top: prev.bottom + width: 158 + + FlagButton + id: flag1 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + FlagButton + id: flag2 + icon-clip: 11 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag3 + icon-clip: 22 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag4 + icon-clip: 33 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag5 + icon-clip: 44 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag6 + icon-clip: 55 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag7 + icon-clip: 66 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag8 + icon-clip: 77 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag9 + icon-clip: 88 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag10 + icon-clip: 99 0 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag11 + icon-clip: 0 11 11 11 + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 6 + margin-left: 0 + + FlagButton + id: flag12 + icon-clip: 11 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag13 + icon-clip: 22 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag14 + icon-clip: 33 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag15 + icon-clip: 44 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag16 + icon-clip: 55 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag17 + icon-clip: 66 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag18 + icon-clip: 77 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag19 + icon-clip: 88 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + FlagButton + id: flag20 + icon-clip: 99 11 11 11 + anchors.left: prev.right + anchors.top: prev.top + + Button + id: okButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: cancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.lua new file mode 100644 index 0000000..9476a99 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.lua @@ -0,0 +1,176 @@ +minimapWidget = nil +minimapButton = nil +minimapWindow = nil +otmm = true +preloaded = false +fullmapView = false +oldZoom = nil +oldPos = nil + +function init() + minimapWindow = g_ui.loadUI('minimap', modules.game_interface.getRightPanel()) + minimapWindow:setContentMinimumHeight(64) + + if not minimapWindow.forceOpen then + minimapButton = modules.client_topmenu.addRightGameToggleButton('minimapButton', + tr('Minimap') .. ' (Ctrl+M)', '/images/topbuttons/minimap', toggle) + minimapButton:setOn(true) + end + + minimapWidget = minimapWindow:recursiveGetChildById('minimap') + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyPress('Alt+Left', function() minimapWidget:move(1,0) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Right', function() minimapWidget:move(-1,0) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Up', function() minimapWidget:move(0,1) end, gameRootPanel) + g_keyboard.bindKeyPress('Alt+Down', function() minimapWidget:move(0,-1) end, gameRootPanel) + g_keyboard.bindKeyDown('Ctrl+M', toggle) + g_keyboard.bindKeyDown('Ctrl+Shift+M', toggleFullMap) + + minimapWindow:setup() + + connect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + connect(LocalPlayer, { + onPositionChange = updateCameraPosition + }) + + if g_game.isOnline() then + online() + end +end + +function terminate() + if g_game.isOnline() then + saveMap() + end + + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline, + }) + + disconnect(LocalPlayer, { + onPositionChange = updateCameraPosition + }) + + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyPress('Alt+Left', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Right', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Up', gameRootPanel) + g_keyboard.unbindKeyPress('Alt+Down', gameRootPanel) + g_keyboard.unbindKeyDown('Ctrl+M') + g_keyboard.unbindKeyDown('Ctrl+Shift+M') + + minimapWindow:destroy() + if minimapButton then + minimapButton:destroy() + end +end + +function toggle() + if not minimapButton then return end + if minimapButton:isOn() then + minimapWindow:close() + minimapButton:setOn(false) + else + minimapWindow:open() + minimapButton:setOn(true) + end +end + +function onMiniWindowClose() + if minimapButton then + minimapButton:setOn(false) + end +end + +function preload() + loadMap(false) + preloaded = true +end + +function online() + loadMap(not preloaded) + updateCameraPosition() +end + +function offline() + saveMap() +end + +function loadMap(clean) + local clientVersion = g_game.getClientVersion() + + if clean then + g_minimap.clean() + end + + if otmm then + local minimapFile = '/minimap.otmm' + if g_resources.fileExists('/data' .. minimapFile) then + g_minimap.loadOtmm('/data' .. minimapFile) + elseif g_resources.fileExists(minimapFile) then + g_minimap.loadOtmm(minimapFile) + end + else + local minimapFile = '/minimap_' .. clientVersion .. '.otcm' + if g_resources.fileExists('/data' .. minimapFile) then + g_map.loadOtcm('/data' .. minimapFile) + elseif g_resources.fileExists(minimapFile) then + g_map.loadOtcm(minimapFile) + end + end + minimapWidget:load() +end + +function saveMap() + local clientVersion = g_game.getClientVersion() + if otmm then + local minimapFile = '/minimap.otmm' + g_minimap.saveOtmm(minimapFile) + else + local minimapFile = '/minimap_' .. clientVersion .. '.otcm' + g_map.saveOtcm(minimapFile) + end + minimapWidget:save() +end + +function updateCameraPosition() + local player = g_game.getLocalPlayer() + if not player then return end + local pos = player:getPosition() + if not pos then return end + if not minimapWidget:isDragging() then + if not fullmapView then + minimapWidget:setCameraPosition(player:getPosition()) + end + minimapWidget:setCrossPosition(player:getPosition()) + end +end + +function toggleFullMap() + if not fullmapView then + fullmapView = true + minimapWindow:hide() + minimapWidget:setParent(modules.game_interface.getRootPanel()) + minimapWidget:fill('parent') + minimapWidget:setAlternativeWidgetsVisible(true) + else + fullmapView = false + minimapWidget:setParent(minimapWindow:getChildById('contentsPanel')) + minimapWidget:fill('parent') + minimapWindow:show() + minimapWidget:setAlternativeWidgetsVisible(false) + end + + local zoom = oldZoom or 0 + local pos = oldPos or minimapWidget:getCameraPosition() + oldZoom = minimapWidget:getZoom() + oldPos = minimapWidget:getCameraPosition() + minimapWidget:setZoom(zoom) + minimapWidget:setCameraPosition(pos) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otmod new file mode 100644 index 0000000..a62ba81 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otmod @@ -0,0 +1,9 @@ +Module + name: game_minimap + description: Manage minimap + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ minimap ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otui new file mode 100644 index 0000000..61e358c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_minimap/minimap.otui @@ -0,0 +1,7 @@ +MinimapWindow + id: minimapWindow + !text: tr('Minimap') + icon: /images/topbuttons/minimap + @onClose: modules.game_minimap.onMiniWindowClose() + &save: true + &autoOpen: 1 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.lua new file mode 100644 index 0000000..c842553 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.lua @@ -0,0 +1,136 @@ +modalDialog = nil +lastDialogChoices = 0 +lastDialogChoice = 0 +lastDialogAnswer = 0 + +function init() + g_ui.importStyle('modaldialog') + + connect(g_game, { onModalDialog = onModalDialog, + onGameEnd = destroyDialog }) + + local dialog = rootWidget:recursiveGetChildById('modalDialog') + if dialog then + modalDialog = dialog + end +end + +function terminate() + disconnect(g_game, { onModalDialog = onModalDialog, + onGameEnd = destroyDialog }) +end + +function destroyDialog() + if modalDialog then + modalDialog:destroy() + modalDialog = nil + end +end + +function onModalDialog(id, title, message, buttons, enterButton, escapeButton, choices, priority) + -- priority parameter is unused, not sure what its use is. + if modalDialog then + return + end + + modalDialog = g_ui.createWidget('ModalDialog', rootWidget) + + local messageLabel = modalDialog:getChildById('messageLabel') + local choiceList = modalDialog:getChildById('choiceList') + local choiceScrollbar = modalDialog:getChildById('choiceScrollBar') + local buttonsPanel = modalDialog:getChildById('buttonsPanel') + + modalDialog:setText(title) + messageLabel:setText(message) + + local labelHeight + for i = 1, #choices do + local choiceId = choices[i][1] + local choiceName = choices[i][2] + + local label = g_ui.createWidget('ChoiceListLabel', choiceList) + label.choiceId = choiceId + label:setText(choiceName) + label:setPhantom(false) + if not labelHeight then + labelHeight = label:getHeight() + end + end + if #choices > 0 then + if g_clock.millis() < lastDialogAnswer + 1000 and lastDialogChoices == #choices then + choiceList:focusChild(choiceList:getChildByIndex(lastDialogChoice)) + else + choiceList:focusChild(choiceList:getFirstChild()) + end + end + + local buttonsWidth = 0 + for i = 1, #buttons do + local buttonId = buttons[i][1] + local buttonText = buttons[i][2] + + local button = g_ui.createWidget('ModalButton', buttonsPanel) + button:setText(buttonText) + button.onClick = function(self) + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, buttonId, choice) + destroyDialog() + end + buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + button:getMarginRight() + end + + local additionalHeight = 0 + if #choices > 0 then + choiceList:setVisible(true) + choiceScrollbar:setVisible(true) + + additionalHeight = math.min(modalDialog.maximumChoices, math.max(modalDialog.minimumChoices, #choices)) * labelHeight + additionalHeight = additionalHeight + choiceList:getPaddingTop() + choiceList:getPaddingBottom() + end + + local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight() + buttonsWidth = buttonsWidth + horizontalPadding + + local labelWidth = math.min(600, math.floor(message:len() * 1.5)) + modalDialog:setWidth(math.min(modalDialog.maximumWidth, math.max(buttonsWidth, labelWidth, modalDialog.minimumWidth))) + messageLabel:setTextWrap(true) + + modalDialog:setHeight(90 + additionalHeight + messageLabel:getHeight()) + + local enterFunc = function() + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, enterButton, choice) + destroyDialog() + end + + local escapeFunc = function() + local focusedChoice = choiceList:getFocusedChild() + local choice = 0xFF + if focusedChoice then + choice = focusedChoice.choiceId + lastDialogChoice = choiceList:getChildIndex(focusedChoice) + lastDialogAnswer = g_clock.millis() + end + g_game.answerModalDialog(id, escapeButton, choice) + destroyDialog() + end + + choiceList.onDoubleClick = enterFunc + + modalDialog.onEnter = enterFunc + modalDialog.onEscape = escapeFunc + + lastDialogChoices = #choices +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otmod new file mode 100644 index 0000000..237e067 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otmod @@ -0,0 +1,10 @@ +Module + name: game_modaldialog + description: Show and process modal dialogs + author: Summ + website: https://github.com/edubart/otclient + sandboxed: true + dependencies: [ game_interface ] + scripts: [ modaldialog ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otui new file mode 100644 index 0000000..2d67bea --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_modaldialog/modaldialog.otui @@ -0,0 +1,74 @@ +ChoiceListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #00000055 + color: #ffffff + +ChoiceList < TextList + id: choiceList + vertical-scrollbar: choiceScrollBar + anchors.fill: parent + anchors.top: prev.bottom + anchors.bottom: next.top + margin-top: 4 + margin-bottom: 10 + focusable: false + visible: false + +ChoiceScrollBar < VerticalScrollBar + id: choiceScrollBar + anchors.top: choiceList.top + anchors.bottom: choiceList.bottom + anchors.right: choiceList.right + step: 14 + pixels-scroll: true + visible: false + +ModalButton < Button + text-auto-resize: true + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + + $pressed: + text-offset: 0 0 + +ModalDialog < MainWindow + id: modalDialog + size: 280 97 + &minimumWidth: 300 + &maximumWidth: 600 + &minimumChoices: 4 + &maximumChoices: 10 + + Label + id: messageLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: left + text-auto-resize: true + text-wrap: true + + ChoiceList + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 5 + + Panel + id: buttonsPanel + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 24 + layout: horizontalBox + align-right: true + + ChoiceScrollBar diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.lua new file mode 100644 index 0000000..0562489 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.lua @@ -0,0 +1,579 @@ +BUY = 1 +SELL = 2 +CURRENCY = 'gold' +CURRENCY_DECIMAL = false +WEIGHT_UNIT = 'oz' +LAST_INVENTORY = 10 + +npcWindow = nil +itemsPanel = nil +radioTabs = nil +radioItems = nil +searchText = nil +setupPanel = nil +quantity = nil +quantityScroll = nil +idLabel = nil +nameLabel = nil +priceLabel = nil +moneyLabel = nil +weightDesc = nil +weightLabel = nil +capacityDesc = nil +capacityLabel = nil +tradeButton = nil +buyTab = nil +sellTab = nil +initialized = false + +showWeight = true +buyWithBackpack = nil +ignoreCapacity = nil +ignoreEquipped = nil +showAllItems = nil +sellAllButton = nil +sellAllWithDelayButton = nil +playerFreeCapacity = 0 +playerMoney = 0 +tradeItems = {} +playerItems = {} +selectedItem = nil + +cancelNextRelease = nil + +sellAllWithDelayEvent = nil + +function init() + npcWindow = g_ui.displayUI('npctrade') + npcWindow:setVisible(false) + + itemsPanel = npcWindow:recursiveGetChildById('itemsPanel') + searchText = npcWindow:recursiveGetChildById('searchText') + + setupPanel = npcWindow:recursiveGetChildById('setupPanel') + quantityScroll = setupPanel:getChildById('quantityScroll') + idLabel = setupPanel:getChildById('id') + nameLabel = setupPanel:getChildById('name') + priceLabel = setupPanel:getChildById('price') + moneyLabel = setupPanel:getChildById('money') + weightDesc = setupPanel:getChildById('weightDesc') + weightLabel = setupPanel:getChildById('weight') + capacityDesc = setupPanel:getChildById('capacityDesc') + capacityLabel = setupPanel:getChildById('capacity') + tradeButton = npcWindow:recursiveGetChildById('tradeButton') + + buyWithBackpack = npcWindow:recursiveGetChildById('buyWithBackpack') + ignoreCapacity = npcWindow:recursiveGetChildById('ignoreCapacity') + ignoreEquipped = npcWindow:recursiveGetChildById('ignoreEquipped') + showAllItems = npcWindow:recursiveGetChildById('showAllItems') + sellAllButton = npcWindow:recursiveGetChildById('sellAllButton') + sellAllWithDelayButton = npcWindow:recursiveGetChildById('sellAllWithDelayButton') + buyTab = npcWindow:getChildById('buyTab') + sellTab = npcWindow:getChildById('sellTab') + + radioTabs = UIRadioGroup.create() + radioTabs:addWidget(buyTab) + radioTabs:addWidget(sellTab) + radioTabs:selectWidget(buyTab) + radioTabs.onSelectionChange = onTradeTypeChange + + cancelNextRelease = false + + if g_game.isOnline() then + playerFreeCapacity = g_game.getLocalPlayer():getFreeCapacity() + end + + connect(g_game, { onGameEnd = hide, + onOpenNpcTrade = onOpenNpcTrade, + onCloseNpcTrade = onCloseNpcTrade, + onPlayerGoods = onPlayerGoods } ) + + connect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange, + onInventoryChange = onInventoryChange } ) + + initialized = true +end + +function terminate() + initialized = false + npcWindow:destroy() + removeEvent(sellAllWithDelayEvent) + + disconnect(g_game, { onGameEnd = hide, + onOpenNpcTrade = onOpenNpcTrade, + onCloseNpcTrade = onCloseNpcTrade, + onPlayerGoods = onPlayerGoods } ) + + disconnect(LocalPlayer, { onFreeCapacityChange = onFreeCapacityChange, + onInventoryChange = onInventoryChange } ) +end + +function show() + if g_game.isOnline() then + if #tradeItems[BUY] > 0 then + radioTabs:selectWidget(buyTab) + else + radioTabs:selectWidget(sellTab) + end + + npcWindow:show() + npcWindow:raise() + npcWindow:focus() + end +end + +function hide() + removeEvent(sellAllWithDelayEvent) + + npcWindow:hide() + + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + clearSelectedItem() + + searchText:clearText() + setupPanel:disable() + itemsPanel:destroyChildren() + + if radioItems then + radioItems:destroy() + radioItems = nil + end + + layout:enableUpdates() + layout:update() +end + +function onItemBoxChecked(widget) + if widget:isChecked() then + local item = widget.item + selectedItem = item + refreshItem(item) + tradeButton:enable() + + if getCurrentTradeType() == SELL then + quantityScroll:setValue(quantityScroll:getMaximum()) + end + end +end + +function onQuantityValueChange(quantity) + if selectedItem then + weightLabel:setText(string.format('%.2f', selectedItem.weight*quantity) .. ' ' .. WEIGHT_UNIT) + priceLabel:setText(formatCurrency(getItemPrice(selectedItem))) + end +end + +function onTradeTypeChange(radioTabs, selected, deselected) + tradeButton:setText(selected:getText()) + selected:setOn(true) + deselected:setOn(false) + + local currentTradeType = getCurrentTradeType() + buyWithBackpack:setVisible(currentTradeType == BUY) + ignoreCapacity:setVisible(currentTradeType == BUY) + ignoreEquipped:setVisible(currentTradeType == SELL) + showAllItems:setVisible(currentTradeType == SELL) + sellAllButton:setVisible(currentTradeType == SELL) + sellAllWithDelayButton:setVisible(currentTradeType == SELL) + + refreshTradeItems() + refreshPlayerGoods() +end + +function onTradeClick() + removeEvent(sellAllWithDelayEvent) + if getCurrentTradeType() == BUY then + g_game.buyItem(selectedItem.ptr, quantityScroll:getValue(), ignoreCapacity:isChecked(), buyWithBackpack:isChecked()) + else + g_game.sellItem(selectedItem.ptr, quantityScroll:getValue(), ignoreEquipped:isChecked()) + end +end + +function onSearchTextChange() + refreshPlayerGoods() +end + +function itemPopup(self, mousePosition, mouseButton) + if cancelNextRelease then + cancelNextRelease = false + return false + end + + if mouseButton == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end) + menu:display(mousePosition) + return true + elseif ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) + or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + cancelNextRelease = true + g_game.inspectNpcTrade(self:getItem()) + return true + end + return false +end + +function onBuyWithBackpackChange() + if selectedItem then + refreshItem(selectedItem) + end +end + +function onIgnoreCapacityChange() + refreshPlayerGoods() +end + +function onIgnoreEquippedChange() + refreshPlayerGoods() +end + +function onShowAllItemsChange() + refreshPlayerGoods() +end + +function setCurrency(currency, decimal) + CURRENCY = currency + CURRENCY_DECIMAL = decimal +end + +function setShowWeight(state) + showWeight = state + weightDesc:setVisible(state) + weightLabel:setVisible(state) +end + +function setShowYourCapacity(state) + capacityDesc:setVisible(state) + capacityLabel:setVisible(state) + ignoreCapacity:setVisible(state) +end + +function clearSelectedItem() + idLabel:clearText() + nameLabel:clearText() + weightLabel:clearText() + priceLabel:clearText() + tradeButton:disable() + quantityScroll:setMinimum(0) + quantityScroll:setMaximum(0) + if selectedItem then + radioItems:selectWidget(nil) + selectedItem = nil + end +end + +function getCurrentTradeType() + if tradeButton:getText() == tr('Buy') then + return BUY + else + return SELL + end +end + +function getItemPrice(item, single) + local amount = 1 + local single = single or false + if not single then + amount = quantityScroll:getValue() + end + if getCurrentTradeType() == BUY then + if buyWithBackpack:isChecked() then + if item.ptr:isStackable() then + return item.price*amount + 20 + else + return item.price*amount + math.ceil(amount/20)*20 + end + end + end + return item.price*amount +end + +function getSellQuantity(item) + if not item or not playerItems[item:getId()] then return 0 end + local removeAmount = 0 + if ignoreEquipped:isChecked() then + local localPlayer = g_game.getLocalPlayer() + for i=1,LAST_INVENTORY do + local inventoryItem = localPlayer:getInventoryItem(i) + if inventoryItem and inventoryItem:getId() == item:getId() then + removeAmount = removeAmount + inventoryItem:getCount() + end + end + end + return playerItems[item:getId()] - removeAmount +end + +function canTradeItem(item) + if getCurrentTradeType() == BUY then + return (ignoreCapacity:isChecked() or (not ignoreCapacity:isChecked() and playerFreeCapacity >= item.weight)) and playerMoney >= getItemPrice(item, true) + else + return getSellQuantity(item.ptr) > 0 + end +end + +function refreshItem(item) + idLabel:setText(item.ptr:getId()) + nameLabel:setText(item.name) + weightLabel:setText(string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT) + priceLabel:setText(formatCurrency(getItemPrice(item))) + + if getCurrentTradeType() == BUY then + local capacityMaxCount = math.floor(playerFreeCapacity / item.weight) + if ignoreCapacity:isChecked() then + capacityMaxCount = 65535 + end + local priceMaxCount = math.floor(playerMoney / getItemPrice(item, true)) + local finalCount = math.max(0, math.min(getMaxAmount(), math.min(priceMaxCount, capacityMaxCount))) + quantityScroll:setMinimum(1) + quantityScroll:setMaximum(finalCount) + else + quantityScroll:setMinimum(1) + quantityScroll:setMaximum(math.max(0, math.min(getMaxAmount(), getSellQuantity(item.ptr)))) + end + + setupPanel:enable() +end + +function refreshTradeItems() + local layout = itemsPanel:getLayout() + layout:disableUpdates() + + clearSelectedItem() + + searchText:clearText() + setupPanel:disable() + itemsPanel:destroyChildren() + + if radioItems then + radioItems:destroy() + end + radioItems = UIRadioGroup.create() + + local currentTradeItems = tradeItems[getCurrentTradeType()] + for key,item in pairs(currentTradeItems) do + local itemBox = g_ui.createWidget('NPCItemBox', itemsPanel) + itemBox.item = item + + local text = '' + local name = item.name + text = text .. name + if showWeight then + local weight = string.format('%.2f', item.weight) .. ' ' .. WEIGHT_UNIT + text = text .. '\n' .. weight + end + local price = formatCurrency(item.price) + text = text .. '\n' .. price + itemBox:setText(text) + + local itemWidget = itemBox:getChildById('item') + itemWidget:setItem(item.ptr) + itemWidget.onMouseRelease = itemPopup + + radioItems:addWidget(itemBox) + end + + layout:enableUpdates() + layout:update() +end + +function refreshPlayerGoods() + if not initialized then return end + + checkSellAllTooltip() + + moneyLabel:setText(formatCurrency(playerMoney)) + capacityLabel:setText(string.format('%.2f', playerFreeCapacity) .. ' ' .. WEIGHT_UNIT) + + local currentTradeType = getCurrentTradeType() + local searchFilter = searchText:getText():lower() + local foundSelectedItem = false + + local items = itemsPanel:getChildCount() + for i=1,items do + local itemWidget = itemsPanel:getChildByIndex(i) + local item = itemWidget.item + + local canTrade = canTradeItem(item) + itemWidget:setOn(canTrade) + itemWidget:setEnabled(canTrade) + + local searchCondition = (searchFilter == '') or (searchFilter ~= '' and string.find(item.name:lower(), searchFilter) ~= nil) + local showAllItemsCondition = (currentTradeType == BUY) or (showAllItems:isChecked()) or (currentTradeType == SELL and not showAllItems:isChecked() and canTrade) + itemWidget:setVisible(searchCondition and showAllItemsCondition) + + if selectedItem == item and itemWidget:isEnabled() and itemWidget:isVisible() then + foundSelectedItem = true + end + end + + if not foundSelectedItem then + clearSelectedItem() + end + + if selectedItem then + refreshItem(selectedItem) + end +end + +function onOpenNpcTrade(items) + tradeItems[BUY] = {} + tradeItems[SELL] = {} + for key,item in pairs(items) do + if item[4] > 0 then + local newItem = {} + newItem.ptr = item[1] + newItem.name = item[2] + newItem.weight = item[3] / 100 + newItem.price = item[4] + table.insert(tradeItems[BUY], newItem) + end + + if item[5] > 0 then + local newItem = {} + newItem.ptr = item[1] + newItem.name = item[2] + newItem.weight = item[3] / 100 + newItem.price = item[5] + table.insert(tradeItems[SELL], newItem) + end + end + + refreshTradeItems() + addEvent(show) -- player goods has not been parsed yet +end + +function closeNpcTrade() + g_game.closeNpcTrade() + addEvent(hide) +end + +function onCloseNpcTrade() + addEvent(hide) +end + +function onPlayerGoods(money, items) + playerMoney = money + + playerItems = {} + for key,item in pairs(items) do + local id = item[1]:getId() + if not playerItems[id] then + playerItems[id] = item[2] + else + playerItems[id] = playerItems[id] + item[2] + end + end + + refreshPlayerGoods() +end + +function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity) + playerFreeCapacity = freeCapacity + + if npcWindow:isVisible() then + refreshPlayerGoods() + end +end + +function onInventoryChange(inventory, item, oldItem) + refreshPlayerGoods() +end + +function getTradeItemData(id, type) + if table.empty(tradeItems[type]) then + return false + end + + if type then + for key,item in pairs(tradeItems[type]) do + if item.ptr and item.ptr:getId() == id then + return item + end + end + else + for _,items in pairs(tradeItems) do + for key,item in pairs(items) do + if item.ptr and item.ptr:getId() == id then + return item + end + end + end + end + return false +end + +function checkSellAllTooltip() + sellAllButton:setEnabled(true) + sellAllButton:removeTooltip() + sellAllWithDelayButton:setEnabled(true) + sellAllWithDelayButton:removeTooltip() + + local total = 0 + local info = '' + local first = true + + for key, amount in pairs(playerItems) do + local data = getTradeItemData(key, SELL) + if data then + amount = getSellQuantity(data.ptr) + if amount > 0 then + if data and amount > 0 then + info = info..(not first and "\n" or "").. + amount.." ".. + data.name.." (".. + data.price*amount.." gold)" + + total = total+(data.price*amount) + if first then first = false end + end + end + end + end + if info ~= '' then + info = info.."\nTotal: "..total.." gold" + sellAllButton:setTooltip(info) + sellAllWithDelayButton:setTooltip(info) + else + sellAllButton:setEnabled(false) + sellAllWithDelayButton:setEnabled(false) + end +end + +function formatCurrency(amount) + if CURRENCY_DECIMAL then + return string.format("%.02f", amount/100.0) .. ' ' .. CURRENCY + else + return amount .. ' ' .. CURRENCY + end +end + +function getMaxAmount() + if getCurrentTradeType() == SELL and g_game.getFeature(GameDoubleShopSellAmount) then + return 10000 + end + return 100 +end + +function sellAll(delayed) + removeEvent(sellAllWithDelayEvent) + local queue = {} + for _,entry in ipairs(tradeItems[SELL]) do + local sellQuantity = getSellQuantity(entry.ptr) + while sellQuantity > 0 do + local maxAmount = math.min(sellQuantity, getMaxAmount()) + if delayed then + g_game.sellItem(entry.ptr, maxAmount, ignoreEquipped:isChecked()) + sellAllWithDelayEvent = scheduleEvent(function() sellAll(true) end, 1100) + return + end + table.insert(queue, {entry.ptr, maxAmount, ignoreEquipped:isChecked()}) + sellQuantity = sellQuantity - maxAmount + end + end + for _, entry in ipairs(queue) do + g_game.sellItem(entry[1], entry[2], entry[3]) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otmod new file mode 100644 index 0000000..df1e957 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otmod @@ -0,0 +1,9 @@ +Module + name: game_npctrade + description: NPC trade interface + author: andrefaramir, baxnie + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ npctrade ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otui new file mode 100644 index 0000000..77938f1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_npctrade/npctrade.otui @@ -0,0 +1,289 @@ +NPCOfferLabel < Label + anchors.left: prev.right + anchors.top: prev.top + margin-left: 5 + text-auto-resize: true + +NPCItemBox < UICheckBox + border-width: 1 + border-color: #000000 + color: #aaaaaa + text-align: center + text-offset: 0 30 + @onCheckChange: modules.game_npctrade.onItemBoxChecked(self) + + Item + id: item + phantom: true + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + image-color: #ffffffff + margin-top: 3 + + $checked on: + border-color: #ffffff + + $!checked: + border-color: #000000 + + $!on: + image-color: #ffffff88 + color: #aaaaaa88 + +MainWindow + id: npcWindow + !text: tr('NPC Trade') + size: 550 340 + @onEscape: modules.game_npctrade.closeNpcTrade() + + TabButton + id: buyTab + !tooltip: tr("List of items that you're able to buy") + !text: tr('Buy') + checked: true + on: true + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: parent.top + margin-top: 0 + + TabButton + id: sellTab + !tooltip: tr("List of items that you're able to sell") + !text: tr('Sell') + anchors.left: parent.horizontalCenter + anchors.right: parent.right + anchors.top: parent.top + + FlatPanel + height: 150 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 5 + + VerticalScrollBar + id: itemsPanelListScrollBar + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + step: 24 + pixels-scroll: true + + ScrollablePanel + id: itemsPanel + height: 200 + anchors.left: parent.left + anchors.right: prev.left + anchors.top: parent.top + anchors.bottom: parent.bottom + vertical-scrollbar: itemsPanelListScrollBar + margin-left: 5 + margin-right: 5 + layout: + type: grid + cell-size: 160 90 + flow: true + auto-spacing: true + + FlatPanel + id: setupPanel + height: 105 + enabled: false + anchors.left: parent.left + anchors.right: parent.horizontalCenter + anchors.top: prev.bottom + margin-top: 5 + margin-right: 5 + image-color: #ffffff88 + + Label + !text: tr('Name') .. ':' + anchors.left: parent.left + anchors.top: parent.top + margin-top: 5 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: name + + Label + !text: tr('Id') .. ':' + anchors.left: parent.left + anchors.top: parent.top + margin-top: 5 + margin-left: 5 + margin-left: 195 + width: 15 + + NPCOfferLabel + id: id + + Label + !text: tr('Price') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: price + + Label + !text: tr('Your Money') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: money + + Label + id: weightDesc + !text: tr('Weight') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: weight + + Label + id: capacityDesc + !text: tr('Your Capacity') .. ':' + anchors.left: parent.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + width: 85 + + NPCOfferLabel + id: capacity + + HorizontalScrollBar + id: quantityScroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: prev.bottom + margin-top: 3 + margin-left: 5 + margin-right: 5 + show-value: true + minimum: 1 + maximum: 100 + step: 1 + @onValueChange: modules.game_npctrade.onQuantityValueChange(self:getValue()) + + FlatPanel + id: buyOptions + height: 80 + anchors.top: prev.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + margin-left: 5 + image-color: #ffffff88 + + Label + id: searchLabel + !text: tr('Search') .. ':' + anchors.left: parent.left + anchors.top: parent.top + text-auto-resize: true + margin-top: 7 + margin-left: 5 + + TextEdit + id: searchText + anchors.left: prev.right + anchors.top: prev.top + anchors.right: parent.right + margin-top: -2 + margin-left: 5 + margin-right: 5 + @onTextChange: modules.game_npctrade.onSearchTextChange() + + CheckBox + id: buyWithBackpack + !text: tr('Buy with backpack') + anchors.top: searchText.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + @onCheckChange: modules.game_npctrade.onBuyWithBackpackChange() + + CheckBox + id: ignoreCapacity + !text: tr('Ignore capacity') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + @onCheckChange: modules.game_npctrade.onIgnoreCapacityChange() + + CheckBox + id: ignoreEquipped + !text: tr('Ignore equipped') + anchors.top: searchText.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + visible: false + checked: true + @onCheckChange: modules.game_npctrade.onIgnoreEquippedChange() + + CheckBox + id: showAllItems + !text: tr('Show all items') + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 5 + margin-top: 3 + visible: false + checked: true + @onCheckChange: modules.game_npctrade.onShowAllItemsChange() + + Button + id: sellAllWithDelayButton + !text: tr('Sell all with delay') + width: 128 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + visible: false + @onClick: modules.game_npctrade.sellAll(true) + + Button + id: sellAllButton + !text: tr('Sell all') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + visible: false + @onClick: modules.game_npctrade.sellAll() + + Button + id: tradeButton + !text: tr('Buy') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_npctrade.onTradeClick() + + Button + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_npctrade.closeNpcTrade() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.lua new file mode 100644 index 0000000..4031017 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.lua @@ -0,0 +1,415 @@ +ADDON_SETS = { + [1] = {1}, + [2] = {2}, + [3] = {1, 2}, + [4] = {3}, + [5] = {1, 3}, + [6] = {2, 3}, + [7] = {1, 2, 3} +} + +outfitWindow = nil +outfit = nil +outfits = nil +outfitCreatureBox = nil +currentOutfit = 1 + +addons = nil +currentColorBox = nil +currentClotheButtonBox = nil +colorBoxes = {} + +mount = nil +mounts = nil +mountCreatureBox = nil +currentMount = 1 +ignoreNextOutfitWindow = 0 + +function init() + connect( + g_game, + { + onOpenOutfitWindow = create, + onGameEnd = destroy + } + ) +end + +function terminate() + disconnect( + g_game, + { + onOpenOutfitWindow = create, + onGameEnd = destroy + } + ) + destroy() +end + +function updateMount() + if table.empty(mounts) or not mount then + return + end + local nameMountWidget = outfitWindow:getChildById("mountName") + nameMountWidget:setText(mounts[currentMount][2]) + + mount.type = mounts[currentMount][1] + mountCreature:setOutfit(mount) +end + +function setupSelector(widget, id, outfit, list) + widget:setId(id) + if id == "healthBar" or id == "manaBar" then + widget.title:setText(id == "healthBar" and "Health Bar" or "Mana Bar") + table.insert(list, 1, {0, "-"}) + else + widget.title:setText(id:gsub("^%l", string.upper)) + if id ~= "type" or #list == 0 then + table.insert(list, 1, {0, "-"}) + end + end + + local pos = 1 + for i, o in pairs(list) do + if (id == "shader" and outfit[id] == o[2]) or outfit[id] == o[1] then + pos = i + end + end + if list[pos] then + widget.outfit = list[pos] + if id == "shader" then + widget.creature:setOutfit( + { + shader = list[pos][2] + } + ) + elseif id == "healthBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getHealthBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + widget.bar.selected = pos - 1 + elseif id == "manaBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getManaBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + widget.bar.selected = pos - 1 + else + widget.creature:setOutfit( + { + type = list[pos][1] + } + ) + end + widget.label:setText(list[pos][2]) + end + + widget.prevButton.onClick = function() + if pos == 1 then + pos = #list + else + pos = pos - 1 + end + if id == "healthBar" or id == "manaBar" then + if id == "healthBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getHealthBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + elseif id == "manaBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getManaBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + end + widget.bar.selected = pos - 1 + widget.label:setText(list[pos][2]) + else + local outfit = widget.creature:getOutfit() + if id == "shader" then + outfit.shader = list[pos][2] + else + outfit.type = list[pos][1] + end + widget.outfit = list[pos] + widget.creature:setOutfit(outfit) + widget.label:setText(list[pos][2]) + updateOutfit() + end + end + + widget.nextButton.onClick = function() + if pos == #list then + pos = 1 + else + pos = pos + 1 + end + if id == "healthBar" or id == "manaBar" then + if id == "healthBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getHealthBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + elseif id == "manaBar" then + if pos ~= 1 then + widget.bar:setImageSource(g_healthBars.getManaBarPath(pos - 1)) + else + widget.bar:setImageSource("") + end + end + widget.bar.selected = pos - 1 + widget.label:setText(list[pos][2]) + else + local outfit = widget.creature:getOutfit() + if id == "shader" then + outfit.shader = list[pos][2] + else + outfit.type = list[pos][1] + end + widget.outfit = list[pos] + widget.creature:setOutfit(outfit) + widget.label:setText(list[pos][2]) + updateOutfit() + end + end + return widget +end + +function create(currentOutfit, outfitList, mountList, wingList, auraList, shaderList, hpBarList, manaBarList) + if ignoreNextOutfitWindow and g_clock.millis() < ignoreNextOutfitWindow + 1000 then + return + end + if outfitWindow and not outfitWindow:isHidden() then + return + end + + destroy() + + outfitWindow = g_ui.displayUI("outfitwindow") + + setupSelector(outfitWindow.type, "type", currentOutfit, outfitList) + + local outfit = outfitWindow.type.creature:getOutfit() + outfit.head = currentOutfit.head + outfit.body = currentOutfit.body + outfit.legs = currentOutfit.legs + outfit.feet = currentOutfit.feet + outfitWindow.type.creature:setOutfit(outfit) + + if g_game.getFeature(GamePlayerMounts) then + setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "mount", currentOutfit, mountList) + end + if g_game.getFeature(GameWingsAndAura) then + setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "wings", currentOutfit, wingList) + setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "aura", currentOutfit, auraList) + end + if g_game.getFeature(GameOutfitShaders) then + setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "shader", currentOutfit, shaderList) + end + + if g_game.getFeature(GameHealthInfoBackground) then + setupSelector(g_ui.createWidget("BarSelectorPanel", outfitWindow.extensions), "healthBar", currentOutfit, hpBarList) + setupSelector(g_ui.createWidget("BarSelectorPanel", outfitWindow.extensions), "manaBar", currentOutfit, manaBarList) + end + + if not outfitWindow.extensions:getFirstChild() then + outfitWindow:setHeight(outfitWindow:getHeight() - 128) + end + + for j = 0, 6 do + for i = 0, 18 do + local colorBox = g_ui.createWidget("ColorBox", outfitWindow.colorBoxPanel) + local outfitColor = getOutfitColor(j * 19 + i) + colorBox:setImageColor(outfitColor) + colorBox:setId("colorBox" .. j * 19 + i) + colorBox.colorId = j * 19 + i + + if j * 19 + i == currentOutfit.head then + currentColorBox = colorBox + colorBox:setChecked(true) + end + colorBox.onCheckChange = onColorCheckChange + colorBoxes[#colorBoxes + 1] = colorBox + end + end + + -- set addons + addons = { + [1] = {widget = outfitWindow:getChildById("addon1"), value = 1}, + [2] = {widget = outfitWindow:getChildById("addon2"), value = 2}, + [3] = {widget = outfitWindow:getChildById("addon3"), value = 4} + } + + for _, addon in pairs(addons) do + addon.widget.onCheckChange = function(self) + onAddonCheckChange(self, addon.value) + end + end + + if currentOutfit.addons and currentOutfit.addons > 0 then + for _, i in pairs(ADDON_SETS[currentOutfit.addons]) do + addons[i].widget:setChecked(true) + end + end + + -- hook outfit sections + currentClotheButtonBox = outfitWindow.head + outfitWindow.head.onCheckChange = onClotheCheckChange + outfitWindow.primary.onCheckChange = onClotheCheckChange + outfitWindow.secondary.onCheckChange = onClotheCheckChange + outfitWindow.detail.onCheckChange = onClotheCheckChange + + updateOutfit() +end + +function destroy() + if outfitWindow then + outfitWindow:destroy() + outfitWindow = nil + currentColorBox = nil + currentClotheButtonBox = nil + colorBoxes = {} + addons = {} + end +end + +function randomize() + local outfitTemplate = { + outfitWindow.head, + outfitWindow.primary, + outfitWindow.secondary, + outfitWindow.detail + } + + for i = 1, #outfitTemplate do + outfitTemplate[i]:setChecked(true) + colorBoxes[math.random(1, #colorBoxes)]:setChecked(true) + outfitTemplate[i]:setChecked(false) + end + outfitTemplate[1]:setChecked(true) +end + +function accept() + local outfit = outfitWindow.type.creature:getOutfit() + for i, child in pairs(outfitWindow.extensions:getChildren()) do + if child:getId() == "healthBar" or child:getId() == "manaBar" then + outfit[child:getId()] = child.bar.selected + else + if child.creature:getCreature() then + if child:getId() == "shader" then + outfit[child:getId()] = child.creature:getOutfit().shader + else + outfit[child:getId()] = child.creature:getOutfit().type + end + end + end + end + + g_game.changeOutfit(outfit) + destroy() +end + +function onAddonCheckChange(addon, value) + local outfit = outfitWindow.type.creature:getOutfit() + if addon:isChecked() then + outfit.addons = outfit.addons + value + else + outfit.addons = outfit.addons - value + end + outfitWindow.type.creature:setOutfit(outfit) +end + +function onColorCheckChange(colorBox) + local outfit = outfitWindow.type.creature:getOutfit() + if colorBox == currentColorBox then + colorBox.onCheckChange = nil + colorBox:setChecked(true) + colorBox.onCheckChange = onColorCheckChange + else + if currentColorBox then + currentColorBox.onCheckChange = nil + currentColorBox:setChecked(false) + currentColorBox.onCheckChange = onColorCheckChange + end + + currentColorBox = colorBox + + if currentClotheButtonBox:getId() == "head" then + outfit.head = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "primary" then + outfit.body = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "secondary" then + outfit.legs = currentColorBox.colorId + elseif currentClotheButtonBox:getId() == "detail" then + outfit.feet = currentColorBox.colorId + end + outfitWindow.type.creature:setOutfit(outfit) + end +end + +function onClotheCheckChange(clotheButtonBox) + local outfit = outfitWindow.type.creature:getOutfit() + if clotheButtonBox == currentClotheButtonBox then + clotheButtonBox.onCheckChange = nil + clotheButtonBox:setChecked(true) + clotheButtonBox.onCheckChange = onClotheCheckChange + else + currentClotheButtonBox.onCheckChange = nil + currentClotheButtonBox:setChecked(false) + currentClotheButtonBox.onCheckChange = onClotheCheckChange + + currentClotheButtonBox = clotheButtonBox + + local colorId = 0 + if currentClotheButtonBox:getId() == "head" then + colorId = outfit.head + elseif currentClotheButtonBox:getId() == "primary" then + colorId = outfit.body + elseif currentClotheButtonBox:getId() == "secondary" then + colorId = outfit.legs + elseif currentClotheButtonBox:getId() == "detail" then + colorId = outfit.feet + end + outfitWindow:recursiveGetChildById("colorBox" .. colorId):setChecked(true) + end +end + +function updateOutfit() + local currentSelection = outfitWindow.type.outfit + if not currentSelection then + return + end + local outfit = outfitWindow.type.creature:getOutfit() + + local availableAddons = currentSelection[3] + local prevAddons = {} + for k, addon in pairs(addons) do + prevAddons[k] = addon.widget:isChecked() + addon.widget:setChecked(false) + addon.widget:setEnabled(false) + end + outfit.addons = 0 + outfitWindow.type.creature:setOutfit(outfit) + + local shader = outfitWindow.extensions:getChildById("shader") + if shader then + outfit.shader = shader.creature:getOutfit().shader + if outfit.shader == "-" then + outfit.shader = "" + end + shader.creature:setOutfit(outfit) + end + + if availableAddons > 0 then + for _, i in pairs(ADDON_SETS[availableAddons]) do + addons[i].widget:setEnabled(true) + addons[i].widget:setChecked(true) + end + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.otmod new file mode 100644 index 0000000..284d076 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfit.otmod @@ -0,0 +1,9 @@ +Module + name: game_outfit + description: Change local player outfit + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ outfit ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfitwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfitwindow.otui new file mode 100644 index 0000000..a7e4810 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_outfit/outfitwindow.otui @@ -0,0 +1,219 @@ +NextOutfitButton < NextButton +PrevOutfitButton < PreviousButton +NextMountButton < NextButton +PrevMountButton < PreviousButton + +OutfitSelectorPanel < Panel + size: 125 120 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + + UICreature + id: creature + size: 100 80 + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 1 + + PreviousButton + id: prevButton + anchors.left: parent.left + anchors.bottom: parent.bottom + + NextButton + id: nextButton + anchors.right: parent.right + anchors.bottom: parent.bottom + + Label + id: label + text: Outfit name + text-align: center + anchors.left: prevButton.right + anchors.right: nextButton.left + anchors.top: prevButton.top + anchors.bottom: parent.bottom + margin-left: 2 + margin-right: 2 + image-source: /images/ui/panel_flat + image-border: 2 + text: - + +BarSelectorPanel < Panel + size: 125 120 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + + UIWidget + id: bar + anchors.centerIn: parent + + PreviousButton + id: prevButton + anchors.left: parent.left + anchors.bottom: parent.bottom + + NextButton + id: nextButton + anchors.right: parent.right + anchors.bottom: parent.bottom + + Label + id: label + text: - + text-align: center + anchors.left: prevButton.right + anchors.right: nextButton.left + anchors.top: prevButton.top + anchors.bottom: parent.bottom + margin-left: 2 + margin-right: 2 + image-source: /images/ui/panel_flat + image-border: 2 + +MainWindow + !text: tr('Select Outfit') + size: 560 330 + + @onEnter: modules.game_outfit.accept() + @onEscape: modules.game_outfit.destroy() + + // Creature Boxes + + Panel + id: line1 + anchors.top: outfit.bottom + anchors.left: parent.left + anchors.right: parent.right + backgroud: red + layout: + type: horizontalBox + spacing: 3 + + OutfitSelectorPanel + id: type + anchors.left: parent.left + anchors.top: parent.top + + CheckBox + id: addon1 + width: 80 + anchors.top: type.bottom + anchors.left: type.left + !text: tr('Addon 1') + margin-left: 2 + margin-top: 5 + enabled: false + + CheckBox + id: addon2 + width: 80 + anchors.top: prev.top + anchors.left: prev.right + !text: tr('Addon 2') + enabled: false + + CheckBox + id: addon3 + width: 80 + anchors.top: prev.top + anchors.left: prev.right + !text: tr('Addon 3') + enabled: false + + ButtonBox + id: head + !text: tr('Head') + anchors.top: type.top + anchors.left: type.right + margin-left: 5 + checked: true + width: 76 + + ButtonBox + id: primary + !text: tr('Primary') + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 2 + width: 76 + + ButtonBox + id: secondary + !text: tr('Secondary') + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 2 + width: 76 + + ButtonBox + id: detail + !text: tr('Detail') + anchors.top: prev.bottom + anchors.left: prev.left + margin-top: 2 + width: 76 + + ButtonBox + id: randomizeButton + !text: tr('Randomize') + anchors.top: prev.bottom + !tooltip: tr('Randomize characters outfit') + anchors.left: prev.left + margin-top: 2 + width: 76 + @onClick: modules.game_outfit.randomize() + + Panel + id: colorBoxPanel + anchors.top: head.top + anchors.left: head.right + anchors.right: parent.right + anchors.bottom: type.bottom + margin-left: 5 + margin-top: 2 + layout: + type: grid + cell-size: 15 15 + cell-spacing: 2 + num-columns: 19 + num-lines: 7 + + Panel + id: extensions + height: 120 + margin-top: 5 + anchors.top: addon1.bottom + anchors.horizontalCenter: parent.horizontalCenter + backgroud: red + layout: + type: horizontalBox + fit-children: true + spacing: 8 + + Button + id: outfitOkButton + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 16 + @onClick: modules.game_outfit.accept() + + Button + id: outfitCancelButton + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_outfit.destroy() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/deathwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/deathwindow.otui new file mode 100644 index 0000000..4f185bf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/deathwindow.otui @@ -0,0 +1,30 @@ +DeathWindow < MainWindow + id: deathWindow + !text: tr('You are dead') + &baseWidth: 350 + &baseHeight: 15 + + Label + id: labelText + width: 550 + height: 140 + anchors.left: parent.left + anchors.top: parent.top + margin-left: 10 + margin-top: 2 + + Button + id: buttonOk + !text: tr('Ok') + width: 64 + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-left: 160 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.left: prev.right + anchors.bottom: parent.bottom + margin-left: 5 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.lua new file mode 100644 index 0000000..736f86e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.lua @@ -0,0 +1,86 @@ +deathWindow = nil + +local deathTexts = { + regular = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!', height = 140, width = 0}, + unfair = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nThis death penalty has been reduced by %i%%\nbecause it was an unfair fight.\n\nSimply click on Ok to resume your journeys!', height = 185, width = 0}, + blessed = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back into this world\n\nThis death penalty has been reduced by 100%\nbecause you are blessed with the Adventurer\'s Blessing\n\nSimply click on Ok to resume your journeys!', height = 170, width = 90} +} + +function init() + g_ui.importStyle('deathwindow') + + connect(g_game, { onDeath = display, + onGameEnd = reset }) +end + +function terminate() + disconnect(g_game, { onDeath = display, + onGameEnd = reset }) + + reset() +end + +function reset() + if deathWindow then + deathWindow:destroy() + deathWindow = nil + end +end + +function display(deathType, penalty) + displayDeadMessage() + openWindow(deathType, penalty) +end + +function displayDeadMessage() + local advanceLabel = modules.game_interface.getRootPanel():recursiveGetChildById('middleCenterLabel') + if advanceLabel:isVisible() then return end + + modules.game_textmessage.displayGameMessage(tr('You are dead.')) +end + +function openWindow(deathType, penalty) + if deathWindow then + deathWindow:destroy() + return + end + + deathWindow = g_ui.createWidget('DeathWindow', rootWidget) + + local textLabel = deathWindow:getChildById('labelText') + if deathType == DeathType.Regular then + if penalty == 100 then + textLabel:setText(deathTexts.regular.text) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.regular.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.regular.width) + else + textLabel:setText(string.format(deathTexts.unfair.text, 100 - penalty)) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.unfair.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.unfair.width) + end + elseif deathType == DeathType.Blessed then + textLabel:setText(deathTexts.blessed.text) + deathWindow:setHeight(deathWindow.baseHeight + deathTexts.blessed.height) + deathWindow:setWidth(deathWindow.baseWidth + deathTexts.blessed.width) + end + + local okButton = deathWindow:getChildById('buttonOk') + local cancelButton = deathWindow:getChildById('buttonCancel') + + local okFunc = function() + CharacterList.doLogin() + okButton:getParent():destroy() + deathWindow = nil + end + local cancelFunc = function() + g_game.safeLogout() + cancelButton:getParent():destroy() + deathWindow = nil + end + + deathWindow.onEnter = okFunc + deathWindow.onEscape = cancelFunc + + okButton.onClick = okFunc + cancelButton.onClick = cancelFunc +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.otmod new file mode 100644 index 0000000..d8dcbb5 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playerdeath/playerdeath.otmod @@ -0,0 +1,9 @@ +Module + name: game_playerdeath + description: Manage player deaths + author: BeniS, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playerdeath ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.lua new file mode 100644 index 0000000..391d19f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.lua @@ -0,0 +1,48 @@ +function init() + connect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + if g_game.isOnline() then online() end +end + +function terminate() + disconnect(g_game, { + onGameStart = online, + onGameEnd = offline + }) + offline() +end + +function online() + if g_game.getFeature(GamePlayerMounts) then + g_keyboard.bindKeyDown('Ctrl+R', toggleMount) + end +end + +function offline() + if g_game.getFeature(GamePlayerMounts) then + g_keyboard.unbindKeyDown('Ctrl+R') + end +end + +function toggleMount() + local player = g_game.getLocalPlayer() + if player then + player:toggleMount() + end +end + +function mount() + local player = g_game.getLocalPlayer() + if player then + player:mount() + end +end + +function dismount() + local player = g_game.getLocalPlayer() + if player then + player:dismount() + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.otmod new file mode 100644 index 0000000..987e265 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playermount/playermount.otmod @@ -0,0 +1,9 @@ +Module + name: game_playermount + description: Manage player mounts + author: BeniS + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playermount ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.lua new file mode 100644 index 0000000..4a39327 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.lua @@ -0,0 +1,81 @@ +tradeWindow = nil + +function init() + g_ui.importStyle('tradewindow') + + connect(g_game, { onOwnTrade = onGameOwnTrade, + onCounterTrade = onGameCounterTrade, + onCloseTrade = onGameCloseTrade, + onGameEnd = onGameCloseTrade }) +end + +function terminate() + disconnect(g_game, { onOwnTrade = onGameOwnTrade, + onCounterTrade = onGameCounterTrade, + onCloseTrade = onGameCloseTrade, + onGameEnd = onGameCloseTrade }) + + if tradeWindow then + tradeWindow:destroy() + end +end + +function createTrade() + tradeWindow = g_ui.createWidget('TradeWindow', modules.game_interface.getRightPanel()) + tradeWindow.onClose = function() + g_game.rejectTrade() + tradeWindow:hide() + end + tradeWindow:setup() +end + +function fillTrade(name, items, counter) + if not tradeWindow then + createTrade() + end + + local tradeItemWidget = tradeWindow:getChildById('tradeItem') + tradeItemWidget:setItemId(items[1]:getId()) + + local tradeContainer + local label + local countLabel + if counter then + tradeContainer = tradeWindow:recursiveGetChildById('counterTradeContainer') + label = tradeWindow:recursiveGetChildById('counterTradeLabel') + countLabel = tradeWindow:recursiveGetChildById('counterTradeCountLabel') + tradeWindow:recursiveGetChildById('acceptButton'):enable() + else + tradeContainer = tradeWindow:recursiveGetChildById('ownTradeContainer') + label = tradeWindow:recursiveGetChildById('ownTradeLabel') + countLabel = tradeWindow:recursiveGetChildById('ownTradeCountLabel') + end + label:setText(name) + countLabel:setText(tr("Items") .. ": " .. #items) + + + for index,item in ipairs(items) do + local itemWidget = g_ui.createWidget('Item', tradeContainer) + itemWidget:setItem(item) + itemWidget:setVirtual(true) + itemWidget:setMargin(0) + itemWidget.onClick = function() + g_game.inspectTrade(counter, index-1) + end + end +end + +function onGameOwnTrade(name, items) + fillTrade(name, items, false) +end + +function onGameCounterTrade(name, items) + fillTrade(name, items, true) +end + +function onGameCloseTrade() + if tradeWindow then + tradeWindow:destroy() + tradeWindow = nil + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.otmod new file mode 100644 index 0000000..ff670d1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/playertrade.otmod @@ -0,0 +1,9 @@ +Module + name: game_playertrade + description: Allow to trade items with players + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ playertrade ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/tradewindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/tradewindow.otui new file mode 100644 index 0000000..da5c1ab --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_playertrade/tradewindow.otui @@ -0,0 +1,112 @@ +TradeWindow < MiniWindow + !text: tr('Trade') + height: 150 + + UIItem + id: tradeItem + virtual: true + size: 16 16 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 4 + margin-left: 4 + + MiniWindowContents + padding: 2 + + Label + id: ownTradeLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.horizontalCenter + + Label + id: counterTradeLabel + anchors.top: parent.top + anchors.left: parent.horizontalCenter + anchors.right: parent.right + + Label + id: ownTradeCountLabel + anchors.top: ownTradeLabel.bottom + anchors.left: ownTradeLabel.left + anchors.right: ownTradeLabel.right + font: verdana-9px-bold + text-align: center + + Label + id: counterTradeCountLabel + anchors.top: counterTradeLabel.bottom + anchors.left: counterTradeLabel.left + anchors.right: counterTradeLabel.right + font: verdana-9px-bold + text-align: center + + ScrollableFlatPanel + id: ownTradeContainer + anchors.top: ownTradeCountLabel.bottom + anchors.bottom: acceptButton.top + anchors.left: ownTradeCountLabel.left + anchors.right: ownTradeCountLabel.right + margin-bottom: 3 + padding: 2 + layout: + type: grid + cell-size: 34 34 + flow: true + cell-spacing: 1 + vertical-scrollbar: ownTradeScrollBar + + VerticalScrollBar + id: ownTradeScrollBar + anchors.top: ownTradeContainer.top + anchors.bottom: ownTradeContainer.bottom + anchors.right: parent.horizontalCenter + step: 14 + pixels-scroll: true + $!on: + width: 0 + + ScrollableFlatPanel + id: counterTradeContainer + anchors.top: counterTradeCountLabel.bottom + anchors.bottom: acceptButton.top + anchors.left: counterTradeCountLabel.left + anchors.right: counterTradeCountLabel.right + margin-bottom: 3 + padding: 2 + layout: + type: grid + cell-size: 34 34 + flow: true + cell-spacing: 1 + vertical-scrollbar: counterTradeScrollBar + + VerticalScrollBar + id: counterTradeScrollBar + anchors.top: counterTradeContainer.top + anchors.bottom: counterTradeContainer.bottom + anchors.right: parent.right + step: 14 + pixels-scroll: true + $!on: + width: 0 + + Button + !text: tr('Accept') + id: acceptButton + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.horizontalCenter + margin-right: 2 + enabled: false + @onClick: g_game.acceptTrade(); self:disable() + + Button + !text: tr('Reject') + id: rejectButton + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.horizontalCenter + margin-left: 2 + @onClick: g_game.rejectTrade() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.lua new file mode 100644 index 0000000..762a6d9 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.lua @@ -0,0 +1,309 @@ +-- sponsored by kivera-global.com + +local preyWindow +local preyButton +local msgWindow +local bankGold = 0 +local inventoryGold = 0 +local rerollPrice = 0 +local bonusRerolls = 0 + +local PREY_BONUS_DAMAGE_BOOST = 0 +local PREY_BONUS_DAMAGE_REDUCTION = 1 +local PREY_BONUS_XP_BONUS = 2 +local PREY_BONUS_IMPROVED_LOOT = 3 +local PREY_BONUS_NONE = 4 + +local PREY_ACTION_LISTREROLL = 0 +local PREY_ACTION_BONUSREROLL = 1 +local PREY_ACTION_MONSTERSELECTION = 2 +local PREY_ACTION_REQUEST_ALL_MONSTERS = 3 +local PREY_ACTION_CHANGE_FROM_ALL = 4 +local PREY_ACTION_LOCK_PREY = 5 + + +function bonusDescription(bonusType, bonusValue, bonusGrade) + if bonusType == PREY_BONUS_DAMAGE_BOOST then + return "Damage bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_DAMAGE_REDUCTION then + return "Damage reduction bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_XP_BONUS then + return "XP bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_IMPROVED_LOOT then + return "Loot bonus (" .. bonusGrade .. "/10)" + elseif bonusType == PREY_BONUS_DAMAGE_BOOST then + return "-" + end + return "Uknown bonus" +end + +function timeleftTranslation(timeleft, forPreyTimeleft) -- in seconds + if timeleft == 0 then + if forPreyTimeleft then + return tr("infinite bonus") + end + return tr("Available now") + end + local minutes = math.ceil(timeleft / 60) + local hours = math.floor(minutes / 60) + minutes = minutes - hours * 60 + if hours > 0 then + if forPreyTimeleft then + return "" .. hours .. "h " .. minutes .. "m" + end + return tr("Available in") .. " " .. hours .. "h " .. minutes .. "m" + end + if forPreyTimeleft then + return "" .. minutes .. "m" + end + return tr("Available in") .. " " .. minutes .. "m" +end +function init() + connect(g_game, { + onGameStart = check, + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onPreyFreeRolls = onPreyFreeRolls, + onPreyTimeLeft = onPreyTimeLeft, + onPreyPrice = onPreyPrice, + onPreyLocked = onPreyLocked, + onPreyInactive = onPreyInactive, + onPreyActive = onPreyActive, + onPreySelection = onPreySelection + }) + + preyWindow = g_ui.displayUI('prey') + preyWindow:hide() + if g_game.isOnline() then + check() + end +end + +function terminate() + disconnect(g_game, { + onGameStart = check, + onGameEnd = hide, + onResourceBalance = onResourceBalance, + onPreyFreeRolls = onPreyFreeRolls, + onPreyTimeLeft = onPreyTimeLeft, + onPreyPrice = onPreyPrice, + onPreyLocked = onPreyLocked, + onPreyInactive = onPreyInactive, + onPreyActive = onPreyActive, + onPreySelection = onPreySelection + }) + + if preyButton then + preyButton:destroy() + end + preyWindow:destroy() + if msgWindow then + msgWindow:destroy() + msgWindow = nil + end +end + +function check() + if g_game.getFeature(GamePrey) then + if not preyButton then + preyButton = modules.client_topmenu.addRightGameToggleButton('preyButton', tr('Preys'), '/images/topbuttons/prey', toggle) + end + elseif preyButton then + preyButton:destroy() + preyButton = nil + end +end + +function hide() + preyWindow:hide() + if msgWindow then + msgWindow:destroy() + msgWindow = nil + end +end + +function show() + if not g_game.getFeature(GamePrey) then + return hide() + end + preyWindow:show() + preyWindow:raise() + preyWindow:focus() + --g_game.preyRequest() -- update preys, it's for tibia 12 +end + +function toggle() + if preyWindow:isVisible() then + return hide() + end + show() +end + +function onPreyFreeRolls(slot, timeleft) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + if prey.state ~= "active" and prey.state ~= "selection" then + return + end + prey.bottomLabel:setText(tr("Free list reroll") .. ": \n" .. timeleftTranslation(timeleft * 60)) +end + +function onPreyTimeLeft(slot, timeleft) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + if prey.state ~= "active" then + return + end + prey.description:setText(tr("Time left") .. ": " .. timeleftTranslation(timeleft, true)) +end + +function onPreyPrice(price) + rerollPrice = price + preyWindow.rerollPrice:setText(tr("Reroll price") .. ":\n" .. price) +end + +function onPreyLocked(slot, unlockState, timeUntilFreeReroll) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + prey.state = "locked" + prey.title:setText(tr("Prey Locked")) + prey.list:hide() + prey.listScrollbar:hide() + prey.creature:hide() + prey.description:hide() + prey.bonuses:hide() + prey.button:hide() + prey.bottomLabel:show() + if unlockState == 0 then + prey.bottomLabel:setText(tr("You need to have premium account and buy this prey slot in the game store.")) + elseif unlockState == 1 then + prey.bottomLabel:setText(tr("You need to buy this prey slot in the game store.")) + else + prey.bottomLabel:setText(tr("You can't unlock it.")) + prey.bottomButton:hide() + end + if (unlockState == 0 or unlockState == 1) and modules.game_shop then + prey.bottomButton:setText("Open game store") + prey.bottomButton.onClick = function() hide() modules.game_shop.show() end + prey.bottomButton:show() + end +end + +function onPreyInactive(slot, timeUntilFreeReroll) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + prey.state = "inactive" + prey.title:setText(tr("Prey Inactive")) + prey.list:hide() + prey.listScrollbar:hide() + prey.creature:hide() + prey.description:hide() + prey.bonuses:hide() + prey.button:hide() + prey.bottomLabel:hide() + + prey.bottomLabel:setText(tr("Free list reroll")..": \n" .. timeleftTranslation(timeUntilFreeReroll * 60)) + prey.bottomLabel:show() + if timeUntilFreeReroll > 0 then + prey.bottomButton:setText(tr("Buy list reroll")) + else + prey.bottomButton:setText(tr("Free list reroll")) + end + prey.bottomButton:show() + prey.bottomButton.onClick = function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end +end + +function onPreyActive(slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + prey.state = "active" + prey.title:setText(currentHolderName) + prey.list:hide() + prey.listScrollbar:hide() + prey.creature:show() + prey.creature:setOutfit(currentHolderOutfit) + prey.description:setText(tr("Time left") .. ": " .. timeleftTranslation(timeLeft, true)) + prey.description:show() + prey.bonuses:setText(bonusDescription(bonusType, bonusValue, bonusGrade)) + prey.bonuses:show() + prey.button:setText(tr("Bonus reroll")) + prey.button:show() + prey.bottomLabel:setText(tr("Free list reroll")..": \n" .. timeleftTranslation(timeUntilFreeReroll * 60)) + prey.bottomLabel:show() + if timeUntilFreeReroll > 0 then + prey.bottomButton:setText(tr("Buy list reroll")) + else + prey.bottomButton:setText(tr("Free list reroll")) + end + prey.bottomButton:show() + + prey.button.onClick = function() + if bonusRerolls == 0 then + return showMessage(tr("Error"), tr("You don't have any bonus rerolls.\nYou can buy them in ingame store.")) + end + g_game.preyAction(slot, PREY_ACTION_BONUSREROLL, 0) + end + + prey.bottomButton.onClick = function() + g_game.preyAction(slot, PREY_ACTION_LISTREROLL, 0) + end +end + +function onPreySelection(slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll) + local prey = preyWindow["slot" .. (slot + 1)] + if not prey then return end + prey.state = "selection" + prey.title:setText(tr("Select monster")) + prey.list:show() + prey.listScrollbar:show() + prey.creature:hide() + prey.description:hide() + prey.bonuses:hide() + prey.button:setText(tr("Select")) + prey.button:show() + prey.bottomLabel:setText("Free list reroll: \n" .. timeleftTranslation(timeUntilFreeReroll * 60)) + prey.bottomLabel:show() + prey.bottomButton:hide() + prey.list:destroyChildren() + for i, name in ipairs(names) do + local label = g_ui.createWidget("PreySelectionLabel", prey.list) + label:setText(name) + label.creature:setOutfit(outfits[i]) + end + prey.button.onClick = function() + local child = prey.list:getFocusedChild() + if not child then + return showMessage(tr("Error"), tr("Select monster to proceed.")) + end + local index = prey.list:getChildIndex(child) + g_game.preyAction(slot, PREY_ACTION_MONSTERSELECTION, index - 1) + end +end + +function onResourceBalance(type, balance) + if type == 0 then -- bank gold + bankGold = balance + elseif type == 1 then -- inventory gold + inventoryGold = balance + elseif type == 10 then -- bonus rerolls + bonusRerolls = balance + preyWindow.bonusRerolls:setText(tr("Available bonus rerolls") .. ":\n" .. balance) + end + + if type == 0 or type == 1 then + preyWindow.balance:setText(tr("Balance") .. ":\n" .. (bankGold + inventoryGold)) + end +end + +function showMessage(title, message) + if msgWindow then + msgWindow:destroy() + end + + msgWindow = displayInfoBox(title, message) + msgWindow:show() + msgWindow:raise() + msgWindow:focus() +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otmod new file mode 100644 index 0000000..99808da --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otmod @@ -0,0 +1,10 @@ +Module + name: game_prey + description: Preys, sponsored by kivera-global.com + author: otclient.ovh & kivera-global.com + website: http://otclient.ovh + sandboxed: true + scripts: [ prey ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otui new file mode 100644 index 0000000..ddd262d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_prey/prey.otui @@ -0,0 +1,199 @@ +PreySelectionLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 34 0 + margin-right: 5 + text-align: center + text-wrap: true + focusable: true + height: 34 + + $focus: + background-color: #00000055 + color: #ffffff + + UICreature + id: creature + size: 32 32 + anchors.top: parent.top + anchors.left: parent.left + margin-top: 1 + +PreySlot < Panel + width: 190 + height: 280 + border: 1 black + padding: 5 + + Label + id: title + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text-align: center + !text: tr("Prey Inactive") + + TextList + id: list + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + margin-top: 15 + margin-right: 10 + vertical-scrollbar: listScrollbar + height: 150 + visible: false + focusable: false + + VerticalScrollBar + id: listScrollbar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: parent.right + pixels-scroll: true + visible: false + step: 10 + + UICreature + id: creature + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + margin-top: 40 + height: 64 + width: 64 + visible: false + + Label + id: description + margin-top: 30 + margin-left: 5 + margin-right: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Label + id: bonuses + margin-top: 5 + margin-left: 5 + margin-right: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Button + id: button + margin-top: 5 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + visible: false + + Label + id: bottomLabel + margin-left: 5 + margin-right: 5 + margin-bottom: 5 + anchors.bottom: next.top + anchors.left: parent.left + anchors.right: parent.right + text-auto-resize: true + text-align: center + text-wrap: true + visible: false + + Button + id: bottomButton + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-left: 10 + margin-right: 10 + text: 11 + visible: false + + $hidden: + height: 0 + + $!hidden: + height: 22 + +MainWindow + id: preyWindow + !text: tr('Preys') + size: 600 405 + background-color: #AAAAAA + @onEscape: modules.game_prey.hide() + + PreySlot + id: slot1 + anchors.left: parent.left + anchors.top: parent.top + + PreySlot + id: slot2 + anchors.left: prev.right + anchors.top: prev.top + + PreySlot + id: slot3 + anchors.left: prev.right + anchors.top: prev.top + + HorizontalSeparator + id: mainSeparator + anchors.top: slot3.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + + Label + id: rerollPrice + anchors.top: mainSeparator.bottom + anchors.left: parent.left + margin-top: 5 + width: 190 + height: 30 + text-align: center + + Label + id: balance + anchors.top: mainSeparator.bottom + anchors.left: prev.right + margin-top: 5 + width: 190 + height: 30 + text-align: center + + Label + id: bonusRerolls + anchors.top: mainSeparator.bottom + anchors.left: prev.right + margin-top: 5 + width: 190 + height: 30 + text-align: center + + HorizontalSeparator + anchors.bottom: buttonCancel.top + anchors.left: parent.left + anchors.right: parent.right + margin-bottom: 5 + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_prey.hide() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.lua new file mode 100644 index 0000000..48c29ad --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.lua @@ -0,0 +1,155 @@ +local registredOpcodes = nil + +local ServerPackets = { + DailyRewardCollectionState = 0xDE, + OpenRewardWall = 0xE2, + CloseRewardWall = 0xE3, + DailyRewardBasic = 0xE4, + DailyRewardHistory = 0xE5, + RestingAreaState = 0xA9, + BestiaryData = 0xd5, + BestiaryOverview = 0xd6, + BestiaryMonsterData = 0xd7, + BestiaryCharmsData = 0xd8, + BestiaryTracker = 0xd9, + BestiaryTrackerTab = 0xB9 +} + +-- Server Types +local DAILY_REWARD_TYPE_ITEM = 1 +local DAILY_REWARD_TYPE_STORAGE = 2 +local DAILY_REWARD_TYPE_PREY_REROLL = 3 +local DAILY_REWARD_TYPE_XP_BOOST = 4 + +-- Client Types +local DAILY_REWARD_SYSTEM_SKIP = 1 +local DAILY_REWARD_SYSTEM_TYPE_ONE = 1 +local DAILY_REWARD_SYSTEM_TYPE_TWO = 2 +local DAILY_REWARD_SYSTEM_TYPE_OTHER = 1 +local DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL = 2 +local DAILY_REWARD_SYSTEM_TYPE_XP_BOOST = 3 + +function init() + connect(g_game, { onEnterGame = registerProtocol, + onPendingGame = registerProtocol, + onGameEnd = unregisterProtocol }) + if g_game.isOnline() then + registerProtocol() + end +end + +function terminate() + disconnect(g_game, { onEnterGame = registerProtocol, + onPendingGame = registerProtocol, + onGameEnd = unregisterProtocol }) + + unregisterProtocol() +end + +function registerProtocol() + local protocolGame = g_game.getProtocolGame() + if protocolGame then + local myValue = math.random(1, 2) + protocolGame:sendExtendedOpcode(227, myValue) + error("1") + end + + if registredOpcodes ~= nil or not g_game.getFeature(GameTibia12Protocol) then + return + end + + registredOpcodes = {} + + registerOpcode(ServerPackets.OpenRewardWall, function(protocol, msg) + msg:getU8() + msg:getU32() + msg:getU8() + local taken = msg:getU8() + if taken > 0 then + msg:getString() + end + msg:getU32() + msg:getU16() + msg:getU16() + end) + + registerOpcode(ServerPackets.CloseRewardWall, function(protocol, msg) + + end) + + registerOpcode(ServerPackets.DailyRewardBasic, function(protocol, msg) + local count = msg:getU8() + for i = 1, count do + readDailyReward(msg) + readDailyReward(msg) + end + local maxBonus = msg:getU8() + for i = 1, maxBonus do + msg:getString() + msg:getU8() + end + msg:getU8() + end) + + registerOpcode(ServerPackets.DailyRewardHistory, function(protocol, msg) + local count = msg:getU8() + for i=1,count do + msg:getU32() + msg:getU8() + msg:getString() + msg:getU16() + end + end) + + registerOpcode(ServerPackets.BestiaryTrackerTab, function(protocol, msg) + local count = msg:getU8() + for i = 1, count do + msg:getU16() + msg:getU32() + msg:getU16() + msg:getU16() + msg:getU16() + msg:getU8() + end + end) +end + +function unregisterProtocol() + if registredOpcodes == nil then + return + end + for _, opcode in ipairs(registredOpcodes) do + ProtocolGame.unregisterOpcode(opcode) + end + registredOpcodes = nil +end + +function registerOpcode(code, func) + if registredOpcodes[code] ~= nil then + error("Duplicated registed opcode: " .. code) + end + registredOpcodes[code] = func + ProtocolGame.registerOpcode(code, func) +end + +function readDailyReward(msg) + local systemType = msg:getU8() + if (systemType == 1) then + msg:getU8() + local count = msg:getU8() + for i = 1, count do + msg:getU16() + msg:getString() + msg:getU32() + end + elseif (systemType == 2) then + msg:getU8() + local type = msg:getU8() + + if (type == DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL) then + msg:getU8() + elseif (type == DAILY_REWARD_SYSTEM_TYPE_XP_BOOST) then + msg:getU16() + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.otmod new file mode 100644 index 0000000..81238ac --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_protocol/protocol.otmod @@ -0,0 +1,11 @@ +Module + name: game_protocol + description: Game protocol + author: otclientv8 + website: http://otclient.ovh + scripts: [ protocol ] + sandboxed: true + autoload: true + autoload-priority: 500 + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlinewindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlinewindow.otui new file mode 100644 index 0000000..7d6a5cf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlinewindow.otui @@ -0,0 +1,51 @@ +MissionLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #ffffff22 + color: #ffffff + +QuestLineWindow < MainWindow + id: questLineWindow + !text: tr('Quest Log') + size: 500 400 + @onEscape: self:destroy() + + TextList + id: missionList + anchors.top: parent.top + anchors.left: parent.left + anchors.right: missionListScrollBar.left + height: 100 + padding: 1 + focusable: false + vertical-scrollbar: missionListScrollBar + + VerticalScrollBar + id: missionListScrollBar + anchors.top: parent.top + anchors.right: parent.right + height: 100 + step: 14 + pixels-scroll: true + + FlatLabel + id: missionDescription + anchors.top: missionList.bottom + anchors.left: parent.left + anchors.right: missionListScrollBar.right + anchors.bottom: closeButton.top + margin-bottom: 10 + margin-top: 10 + text-wrap: true + + Button + id: closeButton + anchors.bottom: parent.bottom + anchors.right: parent.right + !text: tr('Close') + width: 90 + @onClick: self:getParent():destroy() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.lua new file mode 100644 index 0000000..f30c6f7 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.lua @@ -0,0 +1,92 @@ +questLogButton = nil +questLineWindow = nil + +function init() + g_ui.importStyle('questlogwindow') + g_ui.importStyle('questlinewindow') + + if not g_app.isMobile() then + questLogButton = modules.client_topmenu.addLeftGameButton('questLogButton', tr('Quest Log'), '/images/topbuttons/questlog', function() g_game.requestQuestLog() end, false, 8) + end + + connect(g_game, { onQuestLog = onGameQuestLog, + onQuestLine = onGameQuestLine, + onGameEnd = destroyWindows}) +end + +function terminate() + disconnect(g_game, { onQuestLog = onGameQuestLog, + onQuestLine = onGameQuestLine, + onGameEnd = destroyWindows}) + + destroyWindows() + if questLogButton then + questLogButton:destroy() + end +end + +function destroyWindows() + if questLogWindow then + questLogWindow:destroy() + end + + if questLineWindow then + questLineWindow:destroy() + end +end + +function onGameQuestLog(quests) + destroyWindows() + + questLogWindow = g_ui.createWidget('QuestLogWindow', rootWidget) + local questList = questLogWindow:getChildById('questList') + + for i,questEntry in pairs(quests) do + local id, name, completed = unpack(questEntry) + + local questLabel = g_ui.createWidget('QuestLabel', questList) + questLabel:setOn(completed) + questLabel:setText(name) + questLabel.onDoubleClick = function() + questLogWindow:hide() + g_game.requestQuestLine(id) + end + end + + questLogWindow.onDestroy = function() + questLogWindow = nil + end + + questList:focusChild(questList:getFirstChild()) +end + +function onGameQuestLine(questId, questMissions) + if questLogWindow then questLogWindow:hide() end + if questLineWindow then questLineWindow:destroy() end + + questLineWindow = g_ui.createWidget('QuestLineWindow', rootWidget) + local missionList = questLineWindow:getChildById('missionList') + local missionDescription = questLineWindow:getChildById('missionDescription') + + connect(missionList, { onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + missionDescription:setText(focusedChild.description) + end }) + + for i,questMission in pairs(questMissions) do + local name, description = unpack(questMission) + + local missionLabel = g_ui.createWidget('MissionLabel') + missionLabel:setText(name) + missionLabel.description = description + missionList:addChild(missionLabel) + end + + questLineWindow.onDestroy = function() + if questLogWindow then questLogWindow:show() end + questLineWindow = nil + end + + missionList:focusChild(missionList:getFirstChild()) +end + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.otmod new file mode 100644 index 0000000..7bafbeb --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlog.otmod @@ -0,0 +1,9 @@ +Module + name: game_questlog + description: View game quests status + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ questlog ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlogwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlogwindow.otui new file mode 100644 index 0000000..c173c16 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_questlog/questlogwindow.otui @@ -0,0 +1,54 @@ +QuestLabel < Label + font: verdana-11px-monochrome + text-offset: 2 0 + focusable: true + color: #aaaaaa + background-color: alpha + + $on: + color: #00aa00 + $!on: + color: #aaaaaa + + $focus: + background-color: #444444 + + $on focus: + color: #00ff00 + $!on focus: + color: #ffffff + +QuestLogWindow < MainWindow + id: questLogWindow + !text: tr('Quest Log') + size: 500 400 + @onEscape: self:destroy() + $mobile: + size: 500 350 + + TextList + id: questList + anchors.top: parent.top + anchors.bottom: closeButton.top + anchors.left: parent.left + anchors.right: questListScrollBar.left + margin-bottom: 10 + focusable: false + vertical-scrollbar: questListScrollBar + + VerticalScrollBar + id: questListScrollBar + anchors.top: parent.top + anchors.bottom: closeButton.top + anchors.right: parent.right + margin-bottom: 10 + step: 14 + pixels-scroll: true + + Button + id: closeButton + anchors.bottom: parent.bottom + anchors.right: parent.right + !text: tr('Close') + width: 90 + @onClick: self:getParent():destroy() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.lua new file mode 100644 index 0000000..156fdde --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.lua @@ -0,0 +1,151 @@ +rvreasons = {} +rvreasons[1] = tr("1a) Offensive Name") +rvreasons[2] = tr("1b) Invalid Name Format") +rvreasons[3] = tr("1c) Unsuitable Name") +rvreasons[4] = tr("1d) Name Inciting Rule Violation") +rvreasons[5] = tr("2a) Offensive Statement") +rvreasons[6] = tr("2b) Spamming") +rvreasons[7] = tr("2c) Illegal Advertising") +rvreasons[8] = tr("2d) Off-Topic Public Statement") +rvreasons[9] = tr("2e) Non-English Public Statement") +rvreasons[10] = tr("2f) Inciting Rule Violation") +rvreasons[11] = tr("3a) Bug Abuse") +rvreasons[12] = tr("3b) Game Weakness Abuse") +rvreasons[13] = tr("3c) Using Unofficial Software to Play") +rvreasons[14] = tr("3d) Hacking") +rvreasons[15] = tr("3e) Multi-Clienting") +rvreasons[16] = tr("3f) Account Trading or Sharing") +rvreasons[17] = tr("4a) Threatening Gamemaster") +rvreasons[18] = tr("4b) Pretending to Have Influence on Rule Enforcement") +rvreasons[19] = tr("4c) False Report to Gamemaster") +rvreasons[20] = tr("Destructive Behaviour") +rvreasons[21] = tr("Excessive Unjustified Player Killing") + +rvactions = {} +rvactions[0] = tr("Notation") +rvactions[1] = tr("Name Report") +rvactions[2] = tr("Banishment") +rvactions[3] = tr("Name Report + Banishment") +rvactions[4] = tr("Banishment + Final Warning") +rvactions[5] = tr("Name Report + Banishment + Final Warning") +rvactions[6] = tr("Statement Report") + +ruleViolationWindow = nil +reasonsTextList = nil +actionsTextList = nil + +function init() + connect(g_game, { onGMActions = loadReasons }) + + ruleViolationWindow = g_ui.displayUI('ruleviolation') + ruleViolationWindow:setVisible(false) + + reasonsTextList = ruleViolationWindow:getChildById('reasonList') + actionsTextList = ruleViolationWindow:getChildById('actionList') + + g_keyboard.bindKeyDown('Ctrl+Y', function() show() end) + + if g_game.isOnline() then + loadReasons() + end +end + +function terminate() + disconnect(g_game, { onGMActions = loadReasons }) + g_keyboard.unbindKeyDown('Ctrl+Y') + + ruleViolationWindow:destroy() +end + +function hasWindowAccess() + return reasonsTextList:getChildCount() > 0 +end + +function loadReasons() + reasonsTextList:destroyChildren() + actionsTextList:destroyChildren() + + local actions = g_game.getGMActions() + for reason, actionFlags in pairs(actions) do + local label = g_ui.createWidget('RVListLabel', reasonsTextList) + label.onFocusChange = onSelectReason + label:setText(rvreasons[reason]) + label.reasonId = reason + label.actionFlags = actionFlags + end + + if not hasWindowAccess() and ruleViolationWindow:isVisible() then hide() end +end + +function show(target, statement) + if g_game.isOnline() and hasWindowAccess() then + if target then + ruleViolationWindow:getChildById('nameText'):setText(target) + end + + if statement then + ruleViolationWindow:getChildById('statementText'):setText(statement) + end + + ruleViolationWindow:show() + ruleViolationWindow:raise() + ruleViolationWindow:focus() + ruleViolationWindow:getChildById('commentText'):focus() + end +end + +function hide() + ruleViolationWindow:hide() + clearForm() +end + +function onSelectReason(reasonLabel, focused) + if reasonLabel.actionFlags and focused then + actionsTextList:destroyChildren() + for actionBaseFlag = 0, #rvactions do + local actionFlagString = rvactions[actionBaseFlag] + if bit32.band(reasonLabel.actionFlags, math.pow(2, actionBaseFlag)) > 0 then + local label = g_ui.createWidget('RVListLabel', actionsTextList) + label:setText(actionFlagString) + label.actionId = actionBaseFlag + end + end + end +end + +function report() + local reasonLabel = reasonsTextList:getFocusedChild() + if not reasonLabel then + displayErrorBox(tr("Error"), tr("You must select a reason.")) + return + end + + local actionLabel = actionsTextList:getFocusedChild() + if not actionLabel then + displayErrorBox(tr("Error"), tr("You must select an action.")) + return + end + + local target = ruleViolationWindow:getChildById('nameText'):getText() + local reason = reasonLabel.reasonId + local action = actionLabel.actionId + local comment = ruleViolationWindow:getChildById('commentText'):getText() + local statement = ruleViolationWindow:getChildById('statementText'):getText() + local statementId = 0 -- TODO: message unique id ? + local ipBanishment = ruleViolationWindow:getChildById('ipBanCheckBox'):isChecked() + if action == 6 and statement == "" then + displayErrorBox(tr("Error"), tr("No statement has been selected.")) + elseif comment == "" then + displayErrorBox(tr("Error"), tr("You must enter a comment.")) + else + g_game.reportRuleViolation(target, reason, action, comment, statement, statementId, ipBanishment) + hide() + end +end + +function clearForm() + ruleViolationWindow:getChildById('nameText'):clearText() + ruleViolationWindow:getChildById('commentText'):clearText() + ruleViolationWindow:getChildById('statementText'):clearText() + ruleViolationWindow:getChildById('ipBanCheckBox'):setChecked(false) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otmod new file mode 100644 index 0000000..4b5f4fd --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otmod @@ -0,0 +1,9 @@ +Module + name: game_ruleviolation + description: Rule violation interface (Ctrl+Y) + author: andrefaramir + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ ruleviolation ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otui new file mode 100644 index 0000000..570c118 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_ruleviolation/ruleviolation.otui @@ -0,0 +1,120 @@ +RVListLabel < Label + background-color: alpha + text-offset: 2 0 + focusable: true + + $focus: + background-color: #ffffff22 + color: #ffffff + +RVLabel < Label + anchors.left: parent.left + anchors.right: parent.right + + $first: + anchors.top: parent.top + + $!first: + margin-top: 10 + anchors.top: prev.bottom + +RVTextEdit < TextEdit + margin-top: 2 + anchors.left: parent.left + anchors.right: parent.right + + $first: + anchors.top: parent.top + + $!first: + anchors.top: prev.bottom + +MainWindow + id: ruleViolationWindow + size: 400 445 + text: Rule Violation + @onEscape: hide() + + RVLabel + !text: tr('Name') .. ':' + + RVTextEdit + id: nameText + + RVLabel + !text: tr('Statement') .. ':' + + RVTextEdit + id: statementText + enabled: false + + RVLabel + !text: tr('Reason') .. ':' + + TextList + id: reasonList + height: 100 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + focusable: false + vertical-scrollbar: reasonListScrollBar + + VerticalScrollBar + id: reasonListScrollBar + anchors.top: reasonList.top + anchors.bottom: reasonList.bottom + anchors.right: reasonList.right + step: 14 + pixels-scroll: true + + RVLabel + !text: tr('Action') .. ':' + + TextList + id: actionList + height: 60 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 2 + focusable: false + vertical-scrollbar: actionListScrollBar + + VerticalScrollBar + id: actionListScrollBar + anchors.top: actionList.top + anchors.bottom: actionList.bottom + anchors.right: actionList.right + step: 14 + pixels-scroll: true + + CheckBox + id: ipBanCheckBox + !text: tr('IP Address Banishment') + margin-top: 10 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + + RVLabel + !text: tr('Comment') .. ':' + + RVTextEdit + id: commentText + + Button + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: hide() + + Button + !text: tr('Ok') + width: 64 + margin-right: 5 + anchors.right: prev.left + anchors.bottom: parent.bottom + @onClick: report() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.lua new file mode 100644 index 0000000..180a862 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.lua @@ -0,0 +1,25 @@ +function init() + -- add manually your shaders from /data/shaders + + -- map shaders + g_shaders.createShader("map_default", "/shaders/map_default_vertex", "/shaders/map_default_fragment") + + g_shaders.createShader("map_rainbow", "/shaders/map_rainbow_vertex", "/shaders/map_rainbow_fragment") + g_shaders.addTexture("map_rainbow", "/images/shaders/rainbow.png") + + -- use modules.game_interface.gameMapPanel:setShader("map_rainbow") to set shader + + -- outfit shaders + g_shaders.createOutfitShader("outfit_default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment") + + g_shaders.createOutfitShader("outfit_rainbow", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment") + g_shaders.addTexture("outfit_rainbow", "/images/shaders/rainbow.png") + + -- you can use creature:setOutfitShader("outfit_rainbow") to set shader + +end + +function terminate() +end + + diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.otmod new file mode 100644 index 0000000..0c6d727 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shaders/shaders.otmod @@ -0,0 +1,9 @@ +Module + name: game_shaders + description: Load shaders + author: otclientv8 + website: http://otclient.ovh + scripts: [ shaders ] + sandboxed: true + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.lua new file mode 100644 index 0000000..bce0046 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.lua @@ -0,0 +1,562 @@ +-- private variables +local SHOP_EXTENTED_OPCODE = 201 + +shop = nil +local otcv8shop = false +local shopButton = nil +local msgWindow = nil +local browsingHistory = false + +-- for classic store +local storeUrl = "" +local coinsPacketSize = 0 + +local CATEGORIES = {} +local HISTORY = {} +local STATUS = {} +local AD = {} + +local selectedOffer = {} + +local function sendAction(action, data) + if not g_game.getFeature(GameExtendedOpcode) then + return + end + + local protocolGame = g_game.getProtocolGame() + if data == nil then + data = {} + end + if protocolGame then + protocolGame:sendExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, {action = action, data = data}) + end +end + +-- public functions +function init() + connect(g_game, { + onGameStart = check, + onGameEnd = hide, + onStoreInit = onStoreInit, + onStoreCategories = onStoreCategories, + onStoreOffers = onStoreOffers, + onStoreTransactionHistory = onStoreTransactionHistory, + onStorePurchase = onStorePurchase, + onStoreError = onStoreError, + onCoinBalance = onCoinBalance + }) + + ProtocolGame.registerExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode) + + if g_game.isOnline() then + check() + end +end + +function terminate() + disconnect(g_game, { + onGameStart = check, + onGameEnd = hide, + onStoreInit = onStoreInit, + onStoreCategories = onStoreCategories, + onStoreOffers = onStoreOffers, + onStoreTransactionHistory = onStoreTransactionHistory, + onStorePurchase = onStorePurchase, + onStoreError = onStoreError, + onCoinBalance = onCoinBalance + }) + + ProtocolGame.unregisterExtendedJSONOpcode(SHOP_EXTENTED_OPCODE, onExtendedJSONOpcode) + + if shopButton then + shopButton:destroy() + shopButton = nil + end + if shop then + disconnect(shop.categories, { onChildFocusChange = changeCategory }) + shop:destroy() + shop = nil + end + if msgWindow then + msgWindow:destroy() + end +end + +function check() + otcv8shop = false + sendAction("init") +end + +function hide() + if not shop then + return + end + shop:hide() +end + +function show() + if not shop or not shopButton then + return + end + if g_game.getFeature(GameIngameStore) then + g_game.openStore(0) + end + + shop:show() + shop:raise() + shop:focus() +end + +function toggle() + if not shop then + return + end + if shop:isVisible() then + return hide() + end + show() + check() +end + +function createShop() + if shop then return end + shop = g_ui.displayUI('shop') + shop:hide() + shopButton = modules.client_topmenu.addRightGameToggleButton('shopButton', tr('Shop'), '/images/topbuttons/shop', toggle, false, 8) + connect(shop.categories, { onChildFocusChange = changeCategory }) +end + + +function onStoreInit(url, coins) + if otcv8shop then return end + storeUrl = url + if storeUrl:len() > 0 then + if storeUrl:sub(storeUrl:len(), storeUrl:len()) ~= "/" then + storeUrl = storeUrl .. "/" + end + storeUrl = storeUrl .. "64/" + if storeUrl:sub(1, 4):lower() ~= "http" then + storeUrl = "http://" .. storeUrl + end + end + coinsPacketSize = coins + createShop() +end + +function onStoreCategories(categories) + if not shop or otcv8shop then return end + local correctCategories = {} + for i, category in ipairs(categories) do + local image = "" + if category.icon:len() > 0 then + image = storeUrl .. category.icon + end + table.insert(correctCategories, { + type = "image", + image = image, + name = category.name, + offers = {} + }) + end + processCategories(correctCategories) +end + +function onStoreOffers(categoryName, offers) + if not shop or otcv8shop then return end + local updated = false + + for i, category in ipairs(CATEGORIES) do + if category.name == categoryName then + if #category.offers ~= #offers then + updated = true + end + for i=1,#category.offers do + if category.offers[i].title ~= offers[i].name or category.offers[i].id ~= offers[i].id or category.offers[i].cost ~= offers[i].price then + updated = true + end + end + if updated then + for offer in pairs(category.offers) do + category.offers[offer] = nil + end + for i, offer in ipairs(offers) do + local image = "" + if offer.icon:len() > 0 then + image = storeUrl .. offer.icon + end + table.insert(category.offers, { + id=offer.id, + type="image", + image=image, + cost=offer.price, + title=offer.name, + description=offer.description + }) + end + end + end + end + if not updated then + return + end + + local activeCategory = shop.categories:getFocusedChild() + changeCategory(activeCategory, activeCategory) +end + +function onStoreTransactionHistory(currentPage, hasNextPage, offers) + if not shop or otcv8shop then return end + HISTORY = {} + for i, offer in ipairs(offers) do + table.insert(HISTORY, { + id=offer.id, + type="image", + image=storeUrl .. offer.icon, + cost=offer.price, + title=offer.name, + description=offer.description + }) + end + + if not browsingHistory then return end + clearOffers() + shop.categories:focusChild(nil) + for i, transaction in ipairs(HISTORY) do + addOffer(0, transaction) + end +end + +function onStorePurchase(message) + if not shop or otcv8shop then return end + processMessage({title="Successful shop purchase", msg=message}) +end + +function onStoreError(errorType, message) + if not shop or otcv8shop then return end + processMessage({title="Shop error", msg=message}) +end + +function onCoinBalance(coins, transferableCoins) + if not shop or otcv8shop then return end + shop.infoPanel.points:setText(tr("Points:") .. " " .. coins) + shop.infoPanel.buy:hide() + shop.infoPanel:setHeight(20) +end + +function onExtendedJSONOpcode(protocol, code, json_data) + createShop() + + local action = json_data['action'] + local data = json_data['data'] + local status = json_data['status'] + if not action or not data then + return false + end + + otcv8shop = true + if action == 'categories' then + processCategories(data) + elseif action == 'history' then + processHistory(data) + elseif action == 'message' then + processMessage(data) + end + + if status then + processStatus(status) + end +end + +function clearOffers() + while shop.offers:getChildCount() > 0 do + local child = shop.offers:getLastChild() + shop.offers:destroyChildren(child) + end +end + +function clearCategories() + CATEGORIES = {} + clearOffers() + while shop.categories:getChildCount() > 0 do + local child = shop.categories:getLastChild() + shop.categories:destroyChildren(child) + end +end + +function clearHistory() + HISTORY = {} + if browsingHistory then + clearOffers() + end +end + +function processCategories(data) + if table.equal(CATEGORIES,data) then + return + end + clearCategories() + CATEGORIES = data + for i, category in ipairs(data) do + addCategory(category) + end + if not browsingHistory then + local firstCategory = shop.categories:getChildByIndex(1) + if firstCategory then + firstCategory:focus() + end + end +end + +function processHistory(data) + if table.equal(HISTORY,data) then + return + end + HISTORY = data + if browsingHistory then + showHistory(true) + end +end + +function processMessage(data) + if msgWindow then + msgWindow:destroy() + end + + local title = tr(data["title"]) + local msg = data["msg"] + msgWindow = displayInfoBox(title, msg) + msgWindow.onDestroy = function(widget) + if widget == msgWindow then + msgWindow = nil + end + end + msgWindow:show() + msgWindow:raise() + msgWindow:focus() +end + +function processStatus(data) + if table.equal(STATUS,data) then + return + end + STATUS = data + + if data['ad'] then + processAd(data['ad']) + end + if data['points'] then + shop.infoPanel.points:setText(tr("Points:") .. " " .. data['points']) + end + if data['buyUrl'] and data['buyUrl']:sub(1, 4):lower() == "http" then + shop.infoPanel.buy:show() + shop.infoPanel.buy.onMouseRelease = function() + scheduleEvent(function() g_platform.openUrl(data['buyUrl']) end, 50) + end + else + shop.infoPanel.buy:hide() + shop.infoPanel:setHeight(20) + end +end + +function processAd(data) + if table.equal(AD,data) then + return + end + AD = data + + if data['image'] and data['image']:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end + shop.adPanel:setHeight(shop.infoPanel:getHeight()) + shop.adPanel.ad:setText("") + shop.adPanel.ad:setImageSource(path) + shop.adPanel.ad:setImageFixedRatio(true) + shop.adPanel.ad:setImageAutoResize(true) + shop.adPanel.ad:setHeight(shop.infoPanel:getHeight()) + end) + elseif data['text'] and data['text']:len() > 0 then + shop.adPanel:setHeight(shop.infoPanel:getHeight()) + shop.adPanel.ad:setText(data['text']) + shop.adPanel.ad:setHeight(shop.infoPanel:getHeight()) + else + shop.adPanel:setHeight(0) + end + if data['url'] and data['url']:sub(1, 4):lower() == "http" then + shop.adPanel.ad.onMouseRelease = function() + scheduleEvent(function() g_platform.openUrl(data['url']) end, 50) + end + else + shop.adPanel.ad.onMouseRelease = nil + end +end + +function addCategory(data) + local category + if data["type"] == "item" then + category = g_ui.createWidget('ShopCategoryItem', shop.categories) + category.item:setItemId(data["item"]) + category.item:setItemCount(data["count"]) + category.item:setShowCount(false) + elseif data["type"] == "outfit" then + category = g_ui.createWidget('ShopCategoryCreature', shop.categories) + category.creature:setOutfit(data["outfit"]) + if data["outfit"]["rotating"] then + category.creature:setAutoRotating(true) + end + elseif data["type"] == "image" then + category = g_ui.createWidget('ShopCategoryImage', shop.categories) + if data["image"] and data["image"]:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data["image"]) return end + category.image:setImageSource(path) + end) + else + category.image:setImageSource(data["image"]) + end + else + g_logger.error("Invalid shop category type: " .. tostring(data["type"])) + return + end + category:setId("category_" .. shop.categories:getChildCount()) + category.name:setText(data["name"]) +end + +function showHistory(force) + if browsingHistory and not force then + return + end + + if g_game.getFeature(GameIngameStore) and not otcv8shop then + g_game.openTransactionHistory(100) + end + sendAction("history") + + browsingHistory = true + clearOffers() + shop.categories:focusChild(nil) + for i, transaction in ipairs(HISTORY) do + addOffer(0, transaction) + end +end + +function addOffer(category, data) + local offer + if data["type"] == "item" then + offer = g_ui.createWidget('ShopOfferItem', shop.offers) + offer.item:setItemId(data["item"]) + offer.item:setItemCount(data["count"]) + offer.item:setShowCount(false) + elseif data["type"] == "outfit" then + offer = g_ui.createWidget('ShopOfferCreature', shop.offers) + offer.creature:setOutfit(data["outfit"]) + if data["outfit"]["rotating"] then + offer.creature:setAutoRotating(true) + end + elseif data["type"] == "image" then + offer = g_ui.createWidget('ShopOfferImage', shop.offers) + if data["image"] and data["image"]:sub(1, 4):lower() == "http" then + HTTP.downloadImage(data['image'], function(path, err) + if err then g_logger.warning("HTTP error: " .. err .. " - " .. data['image']) return end + if not offer.image then return end + offer.image:setImageSource(path) + end) + elseif data["image"] and data["image"]:len() > 1 then + offer.image:setImageSource(data["image"]) + end + else + g_logger.error("Invalid shop offer type: " .. tostring(data["type"])) + return + end + offer:setId("offer_" .. category .. "_" .. shop.offers:getChildCount()) + offer.title:setText(data["title"] .. " (" .. data["cost"] .. " points)") + offer.description:setText(data["description"]) + offer.offerId = data["id"] + if category ~= 0 then + offer.onDoubleClick = buyOffer + offer.buyButton.onClick = function() buyOffer(offer) end + else + offer.buyButton:hide() + end +end + + +function changeCategory(widget, newCategory) + if not newCategory then + return + end + + if g_game.getFeature(GameIngameStore) and widget ~= newCategory and not otcv8shop then + local serviceType = 0 + if g_game.getFeature(GameTibia12Protocol) then + serviceType = 2 + end + g_game.requestStoreOffers(newCategory.name:getText(), serviceType) + end + + browsingHistory = false + local id = tonumber(newCategory:getId():split("_")[2]) + clearOffers() + for i, offer in ipairs(CATEGORIES[id]["offers"]) do + addOffer(id, offer) + end +end + +function buyOffer(widget) + if not widget then + return + end + local split = widget:getId():split("_") + if #split ~= 3 then + return + end + local category = tonumber(split[2]) + local offer = tonumber(split[3]) + local item = CATEGORIES[category]["offers"][offer] + if not item then + return + end + + selectedOffer = {category=category, offer=offer, title=item.title, cost=item.cost, id=widget.offerId} + + scheduleEvent(function() + if msgWindow then + msgWindow:destroy() + end + + local title = tr("Buying from shop") + local msg = "Do you want to buy " .. item.title .. " for " .. item.cost .. " premium points?" + msgWindow = displayGeneralBox(title, msg, { + { text=tr('Yes'), callback=buyConfirmed }, + { text=tr('No'), callback=buyCanceled }, + anchor=AnchorHorizontalCenter}, buyConfirmed, buyCanceled) + msgWindow:show() + msgWindow:raise() + msgWindow:focus() + msgWindow:raise() + end, 50) +end + +function buyConfirmed() + msgWindow:destroy() + msgWindow = nil + sendAction("buy", selectedOffer) + if g_game.getFeature(GameIngameStore) and selectedOffer.id and not otcv8shop then + local offerName = selectedOffer.title:lower() + if string.find(offerName, "name") and string.find(offerName, "change") and modules.client_textedit then + modules.client_textedit.singlelineEditor("", function(newName) + if newName:len() == 0 then + return + end + g_game.buyStoreOffer(selectedOffer.id, 1, newName) + end) + else + g_game.buyStoreOffer(selectedOffer.id, 0, "") + end + end +end + +function buyCanceled() + msgWindow:destroy() + msgWindow = nil + selectedOffer = {} +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otmod new file mode 100644 index 0000000..eec111a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otmod @@ -0,0 +1,10 @@ +Module + name: game_shop + description: Game shop + author: otclient.ovh + website: http://otclient.ovh + sandboxed: true + scripts: [ shop ] + dependencies: [ client_topmenu ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otui new file mode 100644 index 0000000..10c05f3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_shop/shop.otui @@ -0,0 +1,236 @@ +ShopCategory < Panel + height: 36 + focusable: true + background: alpha + + $focus: + background: #99999999 + + Label + id: name + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-left: 40 + text-align: left + color: white + font: verdana-11px-rounded + +ShopCategoryItem < ShopCategory + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + virtual: true + size: 32 32 + +ShopCategoryCreature < ShopCategory + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + size: 32 32 + +ShopCategoryImage < ShopCategory + Label + id: image + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 2 + margin-bottom: 2 + margin-left: 2 + size: 32 32 + + + +ShopOffer < Panel + height: 56 + background: alpha + + $focus: + background: #99999999 + + Label + id: title + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + margin-top: 4 + margin-left: 55 + text-align: topleft + color: white + font: verdana-11px-rounded + + Label + id: description + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + margin-left: 55 + margin-right: 55 + text-align: topleft + text-auto-resize: true + text-wrap: true + color: white + font: verdana-11px-rounded + + Button + id: buyButton + text: BUY + height: 25 + anchors.verticalCenter: parent.verticalCenter + anchors.left: prev.right + anchors.right: parent.right + margin-right: 15 + text-align: center + +ShopOfferItem < ShopOffer + UIItem + id: item + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + virtual: true + size: 48 48 + +ShopOfferCreature < ShopOffer + UICreature + id: creature + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + size: 48 48 + +ShopOfferImage < ShopOffer + Label + id: image + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-top: 4 + margin-bottom: 4 + margin-left: 2 + size: 48 48 + +MainWindow + id: shopWindow + !text: tr('Shop') + size: 750 500 + @onEscape: modules.game_shop.hide() + $mobile: + size: 500 360 + + Panel + id: infoPanel + anchors.top: parent.top + anchors.left: parent.left + width: 230 + height: 60 + + Label + id: points + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + margin-top: 10 + text: - + text-auto-resize: true + + Button + id: buy + anchors.horizontalCenter: parent.horizontalCenter + width: 150 + anchors.top: prev.bottom + margin-top: 10 + visible: false + !text: tr("Buy points") + + Panel + id: adPanel + anchors.top: parent.top + anchors.left: infoPanel.right + anchors.right: parent.right + margin-left: 10 + height: 0 + + Label + id: ad + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + text-auto-resize: true + text-wrap: true + text-align: center + font: sans-bold-16px + + TextList + id: categories + vertical-scrollbar: categoriesScrollBar + anchors.top: infoPanel.bottom + anchors.left: infoPanel.left + anchors.right: infoPanel.right + anchors.bottom: transactionHistory.top + margin-top: 10 + margin-bottom: 10 + padding: 1 + focusable: false + + VerticalScrollBar + id: categoriesScrollBar + anchors.top: categories.top + anchors.bottom: categories.bottom + anchors.right: categories.right + step: 50 + pixels-scroll: true + + TextList + id: offers + vertical-scrollbar: offersScrollBar + anchors.top: adPanel.bottom + anchors.left: adPanel.left + anchors.right: adPanel.right + anchors.bottom: transactionHistory.top + margin-top: 10 + margin-bottom: 10 + padding: 1 + focusable: false + + VerticalScrollBar + id: offersScrollBar + anchors.top: offers.top + anchors.bottom: offers.bottom + anchors.right: offers.right + step: 50 + pixels-scroll: true + + Button + id: transactionHistory + !text: tr('Transaction history') + width: 128 + anchors.left: parent.left + anchors.bottom: parent.bottom + @onClick: modules.game_shop.showHistory() + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_shop.hide() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.lua new file mode 100644 index 0000000..c68b53b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.lua @@ -0,0 +1,437 @@ +skillsWindow = nil +skillsButton = nil + +function init() + connect(LocalPlayer, { + onExperienceChange = onExperienceChange, + onLevelChange = onLevelChange, + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange, + onTotalCapacityChange = onTotalCapacityChange, + onStaminaChange = onStaminaChange, + onOfflineTrainingChange = onOfflineTrainingChange, + onRegenerationChange = onRegenerationChange, + onSpeedChange = onSpeedChange, + onBaseSpeedChange = onBaseSpeedChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + connect(g_game, { + onGameStart = refresh, + onGameEnd = offline + }) + + skillsButton = modules.client_topmenu.addRightGameToggleButton('skillsButton', tr('Skills'), '/images/topbuttons/skills', toggle, false, 1) + skillsButton:setOn(true) + skillsWindow = g_ui.loadUI('skills', modules.game_interface.getRightPanel()) + + refresh() + skillsWindow:setup() +end + +function terminate() + disconnect(LocalPlayer, { + onExperienceChange = onExperienceChange, + onLevelChange = onLevelChange, + onHealthChange = onHealthChange, + onManaChange = onManaChange, + onSoulChange = onSoulChange, + onFreeCapacityChange = onFreeCapacityChange, + onTotalCapacityChange = onTotalCapacityChange, + onStaminaChange = onStaminaChange, + onOfflineTrainingChange = onOfflineTrainingChange, + onRegenerationChange = onRegenerationChange, + onSpeedChange = onSpeedChange, + onBaseSpeedChange = onBaseSpeedChange, + onMagicLevelChange = onMagicLevelChange, + onBaseMagicLevelChange = onBaseMagicLevelChange, + onSkillChange = onSkillChange, + onBaseSkillChange = onBaseSkillChange + }) + disconnect(g_game, { + onGameStart = refresh, + onGameEnd = offline + }) + + skillsWindow:destroy() + skillsButton:destroy() +end + +function expForLevel(level) + return math.floor((50*level*level*level)/3 - 100*level*level + (850*level)/3 - 200) +end + +function expToAdvance(currentLevel, currentExp) + return expForLevel(currentLevel+1) - currentExp +end + +function resetSkillColor(id) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setColor('#bbbbbb') +end + +function toggleSkill(id, state) + local skill = skillsWindow:recursiveGetChildById(id) + skill:setVisible(state) +end + +function setSkillBase(id, value, baseValue) + if baseValue <= 0 or value < 0 then + return + end + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + + if value > baseValue then + widget:setColor('#008b00') -- green + skill:setTooltip(baseValue .. ' +' .. (value - baseValue)) + elseif value < baseValue then + widget:setColor('#b22222') -- red + skill:setTooltip(baseValue .. ' ' .. (value - baseValue)) + else + widget:setColor('#bbbbbb') -- default + skill:removeTooltip() + end +end + +function setSkillValue(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setText(value) +end + +function setSkillColor(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setColor(value) +end + +function setSkillTooltip(id, value) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('value') + widget:setTooltip(value) +end + +function setSkillPercent(id, percent, tooltip, color) + local skill = skillsWindow:recursiveGetChildById(id) + local widget = skill:getChildById('percent') + if widget then + widget:setPercent(math.floor(percent)) + + if tooltip then + widget:setTooltip(tooltip) + end + + if color then + widget:setBackgroundColor(color) + end + end +end + +function checkAlert(id, value, maxValue, threshold, greaterThan) + if greaterThan == nil then greaterThan = false end + local alert = false + + -- maxValue can be set to false to check value and threshold + -- used for regeneration checking + if type(maxValue) == 'boolean' then + if maxValue then + return + end + + if greaterThan then + if value > threshold then + alert = true + end + else + if value < threshold then + alert = true + end + end + elseif type(maxValue) == 'number' then + if maxValue < 0 then + return + end + + local percent = math.floor((value / maxValue) * 100) + if greaterThan then + if percent > threshold then + alert = true + end + else + if percent < threshold then + alert = true + end + end + end + + if alert then + setSkillColor(id, '#b22222') -- red + else + resetSkillColor(id) + end +end + +function update() + local offlineTraining = skillsWindow:recursiveGetChildById('offlineTraining') + if not g_game.getFeature(GameOfflineTrainingTime) then + offlineTraining:hide() + else + offlineTraining:show() + end + + local regenerationTime = skillsWindow:recursiveGetChildById('regenerationTime') + if not g_game.getFeature(GamePlayerRegenerationTime) then + regenerationTime:hide() + else + regenerationTime:show() + end +end + +function refresh() + local player = g_game.getLocalPlayer() + if not player then return end + + if expSpeedEvent then expSpeedEvent:cancel() end + expSpeedEvent = cycleEvent(checkExpSpeed, 30*1000) + + onExperienceChange(player, player:getExperience()) + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + onHealthChange(player, player:getHealth(), player:getMaxHealth()) + onManaChange(player, player:getMana(), player:getMaxMana()) + onSoulChange(player, player:getSoul()) + onFreeCapacityChange(player, player:getFreeCapacity()) + onStaminaChange(player, player:getStamina()) + onMagicLevelChange(player, player:getMagicLevel(), player:getMagicLevelPercent()) + onOfflineTrainingChange(player, player:getOfflineTrainingTime()) + onRegenerationChange(player, player:getRegenerationTime()) + onSpeedChange(player, player:getSpeed()) + + local hasAdditionalSkills = g_game.getFeature(GameAdditionalSkills) + for i = Skill.Fist, Skill.ManaLeechAmount do + onSkillChange(player, i, player:getSkillLevel(i), player:getSkillLevelPercent(i)) + onBaseSkillChange(player, i, player:getSkillBaseLevel(i)) + + if i > Skill.Fishing then + toggleSkill('skillId'..i, hasAdditionalSkills) + end + end + + update() + + local contentsPanel = skillsWindow:getChildById('contentsPanel') + skillsWindow:setContentMinimumHeight(44) + if hasAdditionalSkills then + skillsWindow:setContentMaximumHeight(480) + else + skillsWindow:setContentMaximumHeight(390) + end +end + +function offline() + if expSpeedEvent then expSpeedEvent:cancel() expSpeedEvent = nil end +end + +function toggle() + if skillsButton:isOn() then + skillsWindow:close() + skillsButton:setOn(false) + else + skillsWindow:open() + skillsButton:setOn(true) + end +end + +function checkExpSpeed() + local player = g_game.getLocalPlayer() + if not player then return end + + local currentExp = player:getExperience() + local currentTime = g_clock.seconds() + if player.lastExps ~= nil then + player.expSpeed = (currentExp - player.lastExps[1][1])/(currentTime - player.lastExps[1][2]) + onLevelChange(player, player:getLevel(), player:getLevelPercent()) + else + player.lastExps = {} + end + table.insert(player.lastExps, {currentExp, currentTime}) + if #player.lastExps > 30 then + table.remove(player.lastExps, 1) + end +end + +function onMiniWindowClose() + skillsButton:setOn(false) +end + +function onSkillButtonClick(button) + local percentBar = button:getChildById('percent') + if percentBar then + percentBar:setVisible(not percentBar:isVisible()) + if percentBar:isVisible() then + button:setHeight(21) + else + button:setHeight(21 - 6) + end + end +end + +function onExperienceChange(localPlayer, value) + local postFix = "" + if value > 1e15 then + postFix = "B" + value = math.floor(value / 1e9) + elseif value > 1e12 then + postFix = "M" + value = math.floor(value / 1e6) + elseif value > 1e9 then + postFix = "K" + value = math.floor(value / 1e3) + end + setSkillValue('experience', comma_value(value) .. postFix) +end + +function onLevelChange(localPlayer, value, percent) + setSkillValue('level', value) + local text = tr('You have %s percent to go', 100 - percent) .. '\n' .. + tr('%s of experience left', expToAdvance(localPlayer:getLevel(), localPlayer:getExperience())) + + if localPlayer.expSpeed ~= nil then + local expPerHour = math.floor(localPlayer.expSpeed * 3600) + if expPerHour > 0 then + local nextLevelExp = expForLevel(localPlayer:getLevel()+1) + local hoursLeft = (nextLevelExp - localPlayer:getExperience()) / expPerHour + local minutesLeft = math.floor((hoursLeft - math.floor(hoursLeft))*60) + hoursLeft = math.floor(hoursLeft) + text = text .. '\n' .. tr('%d of experience per hour', expPerHour) + text = text .. '\n' .. tr('Next level in %d hours and %d minutes', hoursLeft, minutesLeft) + end + end + + setSkillPercent('level', percent, text) +end + +function onHealthChange(localPlayer, health, maxHealth) + setSkillValue('health', health) + checkAlert('health', health, maxHealth, 30) +end + +function onManaChange(localPlayer, mana, maxMana) + setSkillValue('mana', mana) + checkAlert('mana', mana, maxMana, 30) +end + +function onSoulChange(localPlayer, soul) + setSkillValue('soul', soul) +end + +function onFreeCapacityChange(localPlayer, freeCapacity) + setSkillValue('capacity', freeCapacity) + checkAlert('capacity', freeCapacity, localPlayer:getTotalCapacity(), 20) +end + +function onTotalCapacityChange(localPlayer, totalCapacity) + checkAlert('capacity', localPlayer:getFreeCapacity(), totalCapacity, 20) +end + +function onStaminaChange(localPlayer, stamina) + local hours = math.floor(stamina / 60) + local minutes = stamina % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + local percent = math.floor(100 * stamina / (42 * 60)) -- max is 42 hours --TODO not in all client versions + + setSkillValue('stamina', hours .. ":" .. minutes) + + --TODO not all client versions have premium time + if stamina > 2400 and g_game.getClientVersion() >= 1038 and localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("Now you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina > 2400 and g_game.getClientVersion() >= 1038 and not localPlayer:isPremium() then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("You will not gain 50%% more experience because you aren't premium player, now you receive only 1x experience points") + setSkillPercent('stamina', percent, text, '#89F013') + elseif stamina > 2400 and g_game.getClientVersion() < 1038 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. '\n' .. + tr("If you are premium player, you will gain 50%% more experience") + setSkillPercent('stamina', percent, text, 'green') + elseif stamina <= 2400 and stamina > 840 then + setSkillPercent('stamina', percent, tr("You have %s hours and %s minutes left", hours, minutes), 'orange') + elseif stamina <= 840 and stamina > 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You gain only 50%% experience and you don't may gain loot from monsters") + setSkillPercent('stamina', percent, text, 'red') + elseif stamina == 0 then + local text = tr("You have %s hours and %s minutes left", hours, minutes) .. "\n" .. + tr("You don't may receive experience and loot from monsters") + setSkillPercent('stamina', percent, text, 'black') + end +end + +function onOfflineTrainingChange(localPlayer, offlineTrainingTime) + if not g_game.getFeature(GameOfflineTrainingTime) then + return + end + local hours = math.floor(offlineTrainingTime / 60) + local minutes = offlineTrainingTime % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + local percent = 100 * offlineTrainingTime / (12 * 60) -- max is 12 hours + + setSkillValue('offlineTraining', hours .. ":" .. minutes) + setSkillPercent('offlineTraining', percent, tr('You have %s percent', percent)) +end + +function onRegenerationChange(localPlayer, regenerationTime) + if not g_game.getFeature(GamePlayerRegenerationTime) or regenerationTime < 0 then + return + end + local minutes = math.floor(regenerationTime / 60) + local seconds = regenerationTime % 60 + if seconds < 10 then + seconds = '0' .. seconds + end + + setSkillValue('regenerationTime', minutes .. ":" .. seconds) + checkAlert('regenerationTime', regenerationTime, false, 300) +end + +function onSpeedChange(localPlayer, speed) + setSkillValue('speed', speed) + + onBaseSpeedChange(localPlayer, localPlayer:getBaseSpeed()) +end + +function onBaseSpeedChange(localPlayer, baseSpeed) + setSkillBase('speed', localPlayer:getSpeed(), baseSpeed) +end + +function onMagicLevelChange(localPlayer, magiclevel, percent) + setSkillValue('magiclevel', magiclevel) + setSkillPercent('magiclevel', percent, tr('You have %s percent to go', 100 - percent)) + + onBaseMagicLevelChange(localPlayer, localPlayer:getBaseMagicLevel()) +end + +function onBaseMagicLevelChange(localPlayer, baseMagicLevel) + setSkillBase('magiclevel', localPlayer:getMagicLevel(), baseMagicLevel) +end + +function onSkillChange(localPlayer, id, level, percent) + setSkillValue('skillId' .. id, level) + setSkillPercent('skillId' .. id, percent, tr('You have %s percent to go', 100 - percent)) + + onBaseSkillChange(localPlayer, id, localPlayer:getSkillBaseLevel(id)) +end + +function onBaseSkillChange(localPlayer, id, baseLevel) + setSkillBase('skillId'..id, localPlayer:getSkillLevel(id), baseLevel) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otmod new file mode 100644 index 0000000..fdf4fd6 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otmod @@ -0,0 +1,11 @@ +Module + name: game_skills + description: Manage skills window + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ skills ] + @onLoad: init() + @onUnload: terminate() + dependencies: + - game_interface diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otui new file mode 100644 index 0000000..c51e893 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_skills/skills.otui @@ -0,0 +1,212 @@ +SkillFirstWidget < UIWidget + +SkillButton < UIButton + height: 21 + margin-bottom: 2 + &onClick: onSkillButtonClick + +SmallSkillButton < SkillButton + height: 14 + +SkillNameLabel < GameLabel + font: verdana-11px-monochrome + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + +SkillValueLabel < GameLabel + id: value + font: verdana-11px-monochrome + text-align: topright + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: prev.left + +SkillPercentPanel < ProgressBar + id: percent + background-color: green + height: 5 + margin-top: 15 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + phantom: false + +MiniWindow + id: skillWindow + !text: tr('Skills') + height: 150 + icon: /images/topbuttons/skills + @onClose: modules.game_skills.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + padding-left: 5 + padding-right: 5 + layout: verticalBox + + SkillButton + margin-top: 5 + id: experience + height: 15 + SkillNameLabel + !text: tr('Experience') + SkillValueLabel + + SkillButton + id: level + SkillNameLabel + !text: tr('Level') + SkillValueLabel + SkillPercentPanel + background-color: red + + SkillButton + id: health + height: 15 + SkillNameLabel + !text: tr('Hit Points') + SkillValueLabel + + SkillButton + id: mana + height: 15 + SkillNameLabel + !text: tr('Mana') + SkillValueLabel + + SkillButton + id: soul + height: 15 + SkillNameLabel + !text: tr('Soul Points') + SkillValueLabel + + SkillButton + id: capacity + height: 15 + SkillNameLabel + !text: tr('Capacity') + SkillValueLabel + + SkillButton + id: speed + height: 15 + SkillNameLabel + !text: tr('Speed') + SkillValueLabel + + SkillButton + id: regenerationTime + SkillNameLabel + !text: tr('Regeneration Time') + SkillValueLabel + + SkillButton + id: stamina + SkillNameLabel + !text: tr('Stamina') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: offlineTraining + SkillNameLabel + !text: tr('Offline Training') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: magiclevel + SkillNameLabel + !text: tr('Magic Level') + SkillValueLabel + SkillPercentPanel + background-color: red + + SkillButton + id: skillId0 + SkillNameLabel + !text: tr('Fist Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId1 + SkillNameLabel + !text: tr('Club Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId2 + SkillNameLabel + !text: tr('Sword Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId3 + SkillNameLabel + !text: tr('Axe Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId4 + SkillNameLabel + !text: tr('Distance Fighting') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId5 + SkillNameLabel + !text: tr('Shielding') + SkillValueLabel + SkillPercentPanel + + SkillButton + id: skillId6 + SkillNameLabel + !text: tr('Fishing') + SkillValueLabel + SkillPercentPanel + + SmallSkillButton + id: skillId7 + SkillNameLabel + !text: tr('Critical Hit Chance') + SkillValueLabel + + SmallSkillButton + id: skillId8 + SkillNameLabel + !text: tr('Critical Hit Damage') + SkillValueLabel + + SmallSkillButton + id: skillId9 + SkillNameLabel + !text: tr('Life Leech Chance') + SkillValueLabel + + SmallSkillButton + id: skillId10 + SkillNameLabel + !text: tr('Life Leech Amount') + SkillValueLabel + + SmallSkillButton + id: skillId11 + SkillNameLabel + !text: tr('Mana Leech Chance') + SkillValueLabel + + SmallSkillButton + id: skillId12 + SkillNameLabel + !text: tr('Mana Leech Amount') + SkillValueLabel diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.lua new file mode 100644 index 0000000..367f675 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.lua @@ -0,0 +1,390 @@ +local SpelllistProfile = 'Default' + +spelllistWindow = nil +spelllistButton = nil +spellList = nil +nameValueLabel = nil +formulaValueLabel = nil +vocationValueLabel = nil +groupValueLabel = nil +typeValueLabel = nil +cooldownValueLabel = nil +levelValueLabel = nil +manaValueLabel = nil +premiumValueLabel = nil +descriptionValueLabel = nil + +vocationBoxAny = nil +vocationBoxSorcerer = nil +vocationBoxDruid = nil +vocationBoxPaladin = nil +vocationBoxKnight = nil + +groupBoxAny = nil +groupBoxAttack = nil +groupBoxHealing = nil +groupBoxSupport = nil + +premiumBoxAny = nil +premiumBoxNo = nil +premiumBoxYes = nil + +vocationRadioGroup = nil +groupRadioGroup = nil +premiumRadioGroup = nil + +-- consts +FILTER_PREMIUM_ANY = 0 +FILTER_PREMIUM_NO = 1 +FILTER_PREMIUM_YES = 2 + +FILTER_VOCATION_ANY = 0 +FILTER_VOCATION_SORCERER = 1 +FILTER_VOCATION_DRUID = 2 +FILTER_VOCATION_PALADIN = 3 +FILTER_VOCATION_KNIGHT = 4 + +FILTER_GROUP_ANY = 0 +FILTER_GROUP_ATTACK = 1 +FILTER_GROUP_HEALING = 2 +FILTER_GROUP_SUPPORT = 3 + +-- Filter Settings +local filters = { + level = false, + vocation = false, + + vocationId = FILTER_VOCATION_ANY, + premium = FILTER_PREMIUM_ANY, + groupId = FILTER_GROUP_ANY +} + +function getSpelllistProfile() + return SpelllistProfile +end + +function setSpelllistProfile(name) + if SpelllistProfile == name then return end + + if SpelllistSettings[name] and SpellInfo[name] then + local oldProfile = SpelllistProfile + SpelllistProfile = name + changeSpelllistProfile(oldProfile) + else + perror('Spelllist profile \'' .. name .. '\' could not be set.') + end +end + +function online() + if g_game.getFeature(GameSpellList) then + spelllistButton:show() + else + spelllistButton:hide() + end + + -- Vocation is only send in newer clients + if g_game.getClientVersion() >= 950 then + spelllistWindow:getChildById('buttonFilterVocation'):setVisible(true) + else + spelllistWindow:getChildById('buttonFilterVocation'):setVisible(false) + end +end + +function offline() + resetWindow() +end + +function init() + connect(g_game, { onGameStart = online, + onGameEnd = offline }) + + spelllistWindow = g_ui.displayUI('spelllist', modules.game_interface.getRightPanel()) + spelllistWindow:hide() + + spelllistButton = modules.client_topmenu.addRightGameToggleButton('spelllistButton', tr('Spell List'), '/images/topbuttons/spelllist', toggle, false, 4) + spelllistButton:setOn(false) + + nameValueLabel = spelllistWindow:getChildById('labelNameValue') + formulaValueLabel = spelllistWindow:getChildById('labelFormulaValue') + vocationValueLabel = spelllistWindow:getChildById('labelVocationValue') + groupValueLabel = spelllistWindow:getChildById('labelGroupValue') + typeValueLabel = spelllistWindow:getChildById('labelTypeValue') + cooldownValueLabel = spelllistWindow:getChildById('labelCooldownValue') + levelValueLabel = spelllistWindow:getChildById('labelLevelValue') + manaValueLabel = spelllistWindow:getChildById('labelManaValue') + premiumValueLabel = spelllistWindow:getChildById('labelPremiumValue') + descriptionValueLabel = spelllistWindow:getChildById('labelDescriptionValue') + + vocationBoxAny = spelllistWindow:getChildById('vocationBoxAny') + vocationBoxSorcerer = spelllistWindow:getChildById('vocationBoxSorcerer') + vocationBoxDruid = spelllistWindow:getChildById('vocationBoxDruid') + vocationBoxPaladin = spelllistWindow:getChildById('vocationBoxPaladin') + vocationBoxKnight = spelllistWindow:getChildById('vocationBoxKnight') + + groupBoxAny = spelllistWindow:getChildById('groupBoxAny') + groupBoxAttack = spelllistWindow:getChildById('groupBoxAttack') + groupBoxHealing = spelllistWindow:getChildById('groupBoxHealing') + groupBoxSupport = spelllistWindow:getChildById('groupBoxSupport') + + premiumBoxAny = spelllistWindow:getChildById('premiumBoxAny') + premiumBoxYes = spelllistWindow:getChildById('premiumBoxYes') + premiumBoxNo = spelllistWindow:getChildById('premiumBoxNo') + + vocationRadioGroup = UIRadioGroup.create() + vocationRadioGroup:addWidget(vocationBoxAny) + vocationRadioGroup:addWidget(vocationBoxSorcerer) + vocationRadioGroup:addWidget(vocationBoxDruid) + vocationRadioGroup:addWidget(vocationBoxPaladin) + vocationRadioGroup:addWidget(vocationBoxKnight) + + groupRadioGroup = UIRadioGroup.create() + groupRadioGroup:addWidget(groupBoxAny) + groupRadioGroup:addWidget(groupBoxAttack) + groupRadioGroup:addWidget(groupBoxHealing) + groupRadioGroup:addWidget(groupBoxSupport) + + premiumRadioGroup = UIRadioGroup.create() + premiumRadioGroup:addWidget(premiumBoxAny) + premiumRadioGroup:addWidget(premiumBoxYes) + premiumRadioGroup:addWidget(premiumBoxNo) + + premiumRadioGroup:selectWidget(premiumBoxAny) + vocationRadioGroup:selectWidget(vocationBoxAny) + groupRadioGroup:selectWidget(groupBoxAny) + + vocationRadioGroup.onSelectionChange = toggleFilter + groupRadioGroup.onSelectionChange = toggleFilter + premiumRadioGroup.onSelectionChange = toggleFilter + + spellList = spelllistWindow:getChildById('spellList') + + g_keyboard.bindKeyPress('Down', function() spellList:focusNextChild(KeyboardFocusReason) end, spelllistWindow) + g_keyboard.bindKeyPress('Up', function() spellList:focusPreviousChild(KeyboardFocusReason) end, spelllistWindow) + + initializeSpelllist() + resizeWindow() + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onGameEnd = offline }) + + disconnect(spellList, { onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + updateSpellInformation(focusedChild) + end }) + + spelllistWindow:destroy() + spelllistButton:destroy() + + vocationRadioGroup:destroy() + groupRadioGroup:destroy() + premiumRadioGroup:destroy() +end + +function initializeSpelllist() + for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do + local spell = SpelllistSettings[SpelllistProfile].spellOrder[i] + local info = SpellInfo[SpelllistProfile][spell] + + local tmpLabel = g_ui.createWidget('SpellListLabel', spellList) + tmpLabel:setId(spell) + tmpLabel:setText(spell .. '\n\'' .. info.words .. '\'') + tmpLabel:setPhantom(false) + + local iconId = tonumber(info.icon) + if not iconId and SpellIcons[info.icon] then + iconId = SpellIcons[info.icon][1] + end + + if not(iconId) then + perror('Spell icon \'' .. info.icon .. '\' not found.') + end + + tmpLabel:setHeight(SpelllistSettings[SpelllistProfile].iconSize.height + 4) + tmpLabel:setTextOffset(topoint((SpelllistSettings[SpelllistProfile].iconSize.width + 10) .. ' ' .. (SpelllistSettings[SpelllistProfile].iconSize.height - 32)/2 + 3)) + tmpLabel:setImageSource(SpelllistSettings[SpelllistProfile].iconFile) + tmpLabel:setImageClip(Spells.getImageClip(iconId, SpelllistProfile)) + tmpLabel:setImageSize(tosize(SpelllistSettings[SpelllistProfile].iconSize.width .. ' ' .. SpelllistSettings[SpelllistProfile].iconSize.height)) + tmpLabel.onClick = updateSpellInformation + end + + connect(spellList, { onChildFocusChange = function(self, focusedChild) + if focusedChild == nil then return end + updateSpellInformation(focusedChild) + end }) +end + +function changeSpelllistProfile(oldProfile) + -- Delete old labels + for i = 1, #SpelllistSettings[oldProfile].spellOrder do + local spell = SpelllistSettings[oldProfile].spellOrder[i] + local tmpLabel = spellList:getChildById(spell) + + tmpLabel:destroy() + end + + -- Create new spelllist and ajust window + initializeSpelllist() + resizeWindow() + resetWindow() +end + +function updateSpelllist() + for i = 1, #SpelllistSettings[SpelllistProfile].spellOrder do + local spell = SpelllistSettings[SpelllistProfile].spellOrder[i] + local info = SpellInfo[SpelllistProfile][spell] + local tmpLabel = spellList:getChildById(spell) + + local localPlayer = g_game.getLocalPlayer() + if (not(filters.level) or info.level <= localPlayer:getLevel()) and (not(filters.vocation) or table.find(info.vocations, localPlayer:getVocation())) and (filters.vocationId == FILTER_VOCATION_ANY or table.find(info.vocations, filters.vocationId) or table.find(info.vocations, filters.vocationId+4)) and (filters.groupId == FILTER_GROUP_ANY or info.group[filters.groupId]) and (filters.premium == FILTER_PREMIUM_ANY or (info.premium and filters.premium == FILTER_PREMIUM_YES) or (not(info.premium) and filters.premium == FILTER_PREMIUM_NO)) then + tmpLabel:setVisible(true) + else + tmpLabel:setVisible(false) + end + end +end + +function updateSpellInformation(widget) + local spell = widget:getId() + + local name = '' + local formula = '' + local vocation = '' + local group = '' + local type = '' + local cooldown = '' + local level = '' + local mana = '' + local premium = '' + local description = '' + + if SpellInfo[SpelllistProfile][spell] then + local info = SpellInfo[SpelllistProfile][spell] + + name = spell + formula = info.words + + for i = 1, #info.vocations do + local vocationId = info.vocations[i] + if vocationId <= 4 or not(table.find(info.vocations, (vocationId-4))) then + vocation = vocation .. (vocation:len() == 0 and '' or ', ') .. VocationNames[vocationId] + end + end + + cooldown = (info.exhaustion / 1000) .. 's' + for groupId, groupName in ipairs(SpellGroups) do + if info.group[groupId] then + group = group .. (group:len() == 0 and '' or ' / ') .. groupName + cooldown = cooldown .. ' / ' .. (info.group[groupId] / 1000) .. 's' + end + end + + type = info.type + level = info.level + mana = info.mana .. ' / ' .. info.soul + premium = (info.premium and 'yes' or 'no') + description = info.description or '-' + end + + nameValueLabel:setText(name) + formulaValueLabel:setText(formula) + vocationValueLabel:setText(vocation) + groupValueLabel:setText(group) + typeValueLabel:setText(type) + cooldownValueLabel:setText(cooldown) + levelValueLabel:setText(level) + manaValueLabel:setText(mana) + premiumValueLabel:setText(premium) + descriptionValueLabel:setText(description) +end + +function toggle() + if spelllistButton:isOn() then + spelllistButton:setOn(false) + spelllistWindow:hide() + else + spelllistButton:setOn(true) + spelllistWindow:show() + spelllistWindow:raise() + spelllistWindow:focus() + end +end + +function toggleFilter(widget, selectedWidget) + if widget == vocationRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'vocationBoxAny' then + filters.vocationId = FILTER_VOCATION_ANY + elseif boxId == 'vocationBoxSorcerer' then + filters.vocationId = FILTER_VOCATION_SORCERER + elseif boxId == 'vocationBoxDruid' then + filters.vocationId = FILTER_VOCATION_DRUID + elseif boxId == 'vocationBoxPaladin' then + filters.vocationId = FILTER_VOCATION_PALADIN + elseif boxId == 'vocationBoxKnight' then + filters.vocationId = FILTER_VOCATION_KNIGHT + end + elseif widget == groupRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'groupBoxAny' then + filters.groupId = FILTER_GROUP_ANY + elseif boxId == 'groupBoxAttack' then + filters.groupId = FILTER_GROUP_ATTACK + elseif boxId == 'groupBoxHealing' then + filters.groupId = FILTER_GROUP_HEALING + elseif boxId == 'groupBoxSupport' then + filters.groupId = FILTER_GROUP_SUPPORT + end + elseif widget == premiumRadioGroup then + local boxId = selectedWidget:getId() + if boxId == 'premiumBoxAny' then + filters.premium = FILTER_PREMIUM_ANY + elseif boxId == 'premiumBoxNo' then + filters.premium = FILTER_PREMIUM_NO + elseif boxId == 'premiumBoxYes' then + filters.premium = FILTER_PREMIUM_YES + end + else + local id = widget:getId() + if id == 'buttonFilterLevel' then + filters.level = not(filters.level) + widget:setOn(filters.level) + elseif id == 'buttonFilterVocation' then + filters.vocation = not(filters.vocation) + widget:setOn(filters.vocation) + end + end + + updateSpelllist() +end + +function resizeWindow() + spelllistWindow:setWidth(SpelllistSettings['Default'].spellWindowWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32) + spellList:setWidth(SpelllistSettings['Default'].spellListWidth + SpelllistSettings[SpelllistProfile].iconSize.width - 32) +end + +function resetWindow() + spelllistWindow:hide() + spelllistButton:setOn(false) + + -- Resetting filters + filters.level = false + filters.vocation = false + + local buttonFilterLevel = spelllistWindow:getChildById('buttonFilterLevel') + buttonFilterLevel:setOn(filters.level) + + local buttonFilterVocation = spelllistWindow:getChildById('buttonFilterVocation') + buttonFilterVocation:setOn(filters.vocation) + + vocationRadioGroup:selectWidget(vocationBoxAny) + groupRadioGroup:selectWidget(groupBoxAny) + premiumRadioGroup:selectWidget(premiumBoxAny) + + updateSpelllist() +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otmod new file mode 100644 index 0000000..4e3eaec --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otmod @@ -0,0 +1,9 @@ +Module + name: game_spelllist + description: View available spells + author: Summ, Edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ spelllist ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otui new file mode 100644 index 0000000..7bbc46b --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_spelllist/spelllist.otui @@ -0,0 +1,326 @@ +SpellListLabel < Label + font: verdana-11px-monochrome + background-color: alpha + text-offset: 42 3 + focusable: true + height: 36 + image-clip: 0 0 32 32 + image-size: 32 32 + image-offset: 2 2 + image-source: /images/game/spells/defaultspells + + $focus: + background-color: #ffffff22 + color: #ffffff + +SpellInfoLabel < Label + width: 70 + font: verdana-11px-monochrome + text-align: right + margin-left: 10 + margin-top: 5 + +SpellInfoValueLabel < Label + text-align: left + width: 190 + margin-left: 10 + margin-top: 5 + +FilterButton < Button + width: 64 + anchors.left: prev.right + anchors.top: spellList.bottom + @onClick: toggleFilter(self) + margin: 5 0 0 6 + color: #630000 + $on: + color: green + +MainWindow + id: spelllistWindow + !text: tr('Spell List') + size: 550 400 + @onEscape: toggle() + + TextList + id: spellList + vertical-scrollbar: spellsScrollBar + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: next.top + margin-bottom: 10 + padding: 1 + width: 210 + focusable: false + + Button + id: buttonCancel + !text: tr('Close') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: toggle() + + VerticalScrollBar + id: spellsScrollBar + anchors.top: spellList.top + anchors.bottom: spellList.bottom + anchors.right: spellList.right + step: 50 + pixels-scroll: true + + SpellInfoLabel + id: labelName + anchors.left: spellList.right + anchors.top: spellList.top + !text: tr('Name') .. ':' + + Label + anchors.left: parent.left + anchors.top: spellList.bottom + !text: tr('Filters') .. ':' + margin-top: 8 + + FilterButton + id: buttonFilterLevel + !text: tr('Level') + !tooltip: tr('Hide spells for higher exp. levels') + + FilterButton + id: buttonFilterVocation + !text: tr('Vocation') + !tooltip: tr('Hide spells for other vocations') + + SpellInfoLabel + id: labelFormula + anchors.left: spellList.right + anchors.top: labelName.bottom + !text: tr('Formula') .. ':' + + + SpellInfoLabel + id: labelVocation + anchors.left: spellList.right + anchors.top: labelFormula.bottom + !text: tr('Vocation') .. ':' + + SpellInfoLabel + id: labelGroup + anchors.left: spellList.right + anchors.top: labelVocation.bottom + !text: tr('Group') .. ':' + + SpellInfoLabel + id: labelType + anchors.left: spellList.right + anchors.top: labelGroup.bottom + !text: tr('Type') .. ':' + + SpellInfoLabel + id: labelCooldown + anchors.left: spellList.right + anchors.top: labelType.bottom + !text: tr('Cooldown') .. ':' + + SpellInfoLabel + id: labelLevel + anchors.left: spellList.right + anchors.top: labelCooldown.bottom + !text: tr('Level') .. ':' + + SpellInfoLabel + id: labelMana + anchors.left: spellList.right + anchors.top: labelLevel.bottom + !text: tr('Mana') .. ' / ' .. tr('Soul') .. ':' + + SpellInfoLabel + id: labelPremium + anchors.left: spellList.right + anchors.top: labelMana.bottom + !text: tr('Premium') .. ':' + + SpellInfoLabel + id: labelDescription + anchors.left: spellList.right + anchors.top: labelPremium.bottom + !text: tr('Description') .. ':' + + SpellInfoValueLabel + id: labelNameValue + anchors.left: labelName.right + anchors.top: spellList.top + + SpellInfoValueLabel + id: labelFormulaValue + anchors.left: labelFormula.right + anchors.top: labelNameValue.bottom + + SpellInfoValueLabel + id: labelVocationValue + anchors.left: labelVocation.right + anchors.top: labelFormulaValue.bottom + + SpellInfoValueLabel + id: labelGroupValue + anchors.left: labelGroup.right + anchors.top: labelVocationValue.bottom + + SpellInfoValueLabel + id: labelTypeValue + anchors.left: labelType.right + anchors.top: labelGroupValue.bottom + + SpellInfoValueLabel + id: labelCooldownValue + anchors.left: labelCooldown.right + anchors.top: labelTypeValue.bottom + + SpellInfoValueLabel + id: labelLevelValue + anchors.left: labelLevel.right + anchors.top: labelCooldownValue.bottom + + SpellInfoValueLabel + id: labelManaValue + anchors.left: labelMana.right + anchors.top: labelLevelValue.bottom + + SpellInfoValueLabel + id: labelPremiumValue + anchors.left: labelPremium.right + anchors.top: labelManaValue.bottom + + SpellInfoValueLabel + id: labelDescriptionValue + anchors.left: labelDescription.right + anchors.top: labelPremiumValue.bottom + + Label + id: labelVocationFilter + anchors.top: labelPremium.bottom + anchors.left: spellList.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Vocation') + margin-top: 30 + margin-left: 20 + + CheckBox + id: vocationBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: vocationBoxSorcerer + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Sorcerer') + width: 75 + + CheckBox + id: vocationBoxDruid + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Druid') + width: 75 + + CheckBox + id: vocationBoxPaladin + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Paladin') + width: 75 + + CheckBox + id: vocationBoxKnight + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Knight') + width: 75 + + Label + id: labelGroupFilter + anchors.top: labelPremium.bottom + anchors.left: labelVocationFilter.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Group') + margin-top: 30 + margin-left: 20 + + CheckBox + id: groupBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: groupBoxAttack + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Attack') + width: 75 + + CheckBox + id: groupBoxHealing + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Healing') + width: 75 + + CheckBox + id: groupBoxSupport + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Support') + width: 75 + + Label + id: labelPremiumFilter + anchors.top: labelPremium.bottom + anchors.left: labelGroupFilter.right + width: 70 + font: verdana-11px-monochrome + !text: tr('Premium') + margin-top: 30 + margin-left: 20 + + CheckBox + id: premiumBoxAny + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + margin-left: 3 + !text: tr('Any') + width: 75 + + CheckBox + id: premiumBoxNo + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('No') + width: 75 + + CheckBox + id: premiumBoxYes + anchors.left: prev.left + anchors.top: prev.bottom + margin-top: 3 + !text: tr('Yes') + width: 75 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.lua new file mode 100644 index 0000000..fc85b90 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.lua @@ -0,0 +1,35 @@ +ui = nil +updateEvent = nil + +function init() + ui = g_ui.loadUI('stats', modules.game_interface.getMapPanel()) + + if not modules.client_options.getOption("showFps") then + ui.fps:hide() + end + if not modules.client_options.getOption("showPing") then + ui.ping:hide() + end + + updateEvent = scheduleEvent(update, 200) +end + +function terminate() + removeEvent(updateEvent) +end + +function update() + updateEvent = scheduleEvent(update, 500) + if ui:isHidden() then return end + + text = 'FPS: ' .. g_app.getFps() + ui.fps:setText(text) +end + +function show() + ui:setVisible(true) +end + +function hide() + ui:setVisible(false) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otmod new file mode 100644 index 0000000..fc6a6c8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otmod @@ -0,0 +1,9 @@ +Module + name: game_stats + description: Display ping and fps + author: otclient.ovh + website: http://otclient.ovh + sandboxed: true + scripts: [ stats ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otui new file mode 100644 index 0000000..33fcfb4 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_stats/stats.otui @@ -0,0 +1,17 @@ +UIWidget + id: game_stats + anchors.top: parent.top + anchors.left: parent.left + margin-left: 3 + size: 100 100 + visible: false + layout: + type: verticalBox + + Label + id: fps + font: verdana-11px-rounded + + Label + id: ping + font: verdana-11px-rounded diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.lua new file mode 100644 index 0000000..47f9fdf --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.lua @@ -0,0 +1,135 @@ +MessageSettings = { + none = {}, + consoleRed = { color = TextColors.red, consoleTab='Default' }, + consoleOrange = { color = TextColors.orange, consoleTab='Default' }, + consoleBlue = { color = TextColors.blue, consoleTab='Default' }, + centerRed = { color = TextColors.red, consoleTab='Server Log', screenTarget='lowCenterLabel' }, + centerGreen = { color = TextColors.green, consoleTab='Server Log', screenTarget='highCenterLabel', consoleOption='showInfoMessagesInConsole' }, + centerWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='middleCenterLabel', consoleOption='showEventMessagesInConsole' }, + bottomWhite = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showEventMessagesInConsole' }, + status = { color = TextColors.white, consoleTab='Server Log', screenTarget='statusLabel', consoleOption='showStatusMessagesInConsole' }, + statusSmall = { color = TextColors.white, screenTarget='statusLabel' }, + private = { color = TextColors.lightblue, screenTarget='privateLabel' } +} + +MessageTypes = { + [MessageModes.MonsterSay] = MessageSettings.consoleOrange, + [MessageModes.MonsterYell] = MessageSettings.consoleOrange, + [MessageModes.BarkLow] = MessageSettings.consoleOrange, + [MessageModes.BarkLoud] = MessageSettings.consoleOrange, + [MessageModes.Failure] = MessageSettings.statusSmall, + [MessageModes.Login] = MessageSettings.bottomWhite, + [MessageModes.Game] = MessageSettings.centerWhite, + [MessageModes.Status] = MessageSettings.status, + [MessageModes.Warning] = MessageSettings.centerRed, + [MessageModes.Look] = MessageSettings.centerGreen, + [MessageModes.Loot] = MessageSettings.centerGreen, + [MessageModes.Red] = MessageSettings.consoleRed, + [MessageModes.Blue] = MessageSettings.consoleBlue, + [MessageModes.PrivateFrom] = MessageSettings.consoleBlue, + + [MessageModes.GamemasterBroadcast] = MessageSettings.consoleRed, + + [MessageModes.DamageDealed] = MessageSettings.status, + [MessageModes.DamageReceived] = MessageSettings.status, + [MessageModes.Heal] = MessageSettings.status, + [MessageModes.Exp] = MessageSettings.status, + + [MessageModes.DamageOthers] = MessageSettings.none, + [MessageModes.HealOthers] = MessageSettings.none, + [MessageModes.ExpOthers] = MessageSettings.none, + + [MessageModes.TradeNpc] = MessageSettings.centerWhite, + [MessageModes.Guild] = MessageSettings.centerWhite, + [MessageModes.Party] = MessageSettings.centerGreen, + [MessageModes.PartyManagement] = MessageSettings.centerWhite, + [MessageModes.TutorialHint] = MessageSettings.centerWhite, + [MessageModes.BeyondLast] = MessageSettings.centerWhite, + [MessageModes.Report] = MessageSettings.consoleRed, + [MessageModes.HotkeyUse] = MessageSettings.centerGreen, + + [254] = MessageSettings.private +} + +messagesPanel = nil + +function init() + for messageMode, _ in pairs(MessageTypes) do + registerMessageMode(messageMode, displayMessage) + end + + connect(g_game, 'onGameEnd', clearMessages) + messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel()) +end + +function terminate() + for messageMode, _ in pairs(MessageTypes) do + unregisterMessageMode(messageMode, displayMessage) + end + + disconnect(g_game, 'onGameEnd', clearMessages) + clearMessages() + messagesPanel:destroy() +end + +function calculateVisibleTime(text) + return math.max(#text * 50, 3000) +end + +function displayMessage(mode, text) + if not g_game.isOnline() then return end + + local msgtype = MessageTypes[mode] + if not msgtype then + return + end + + if msgtype == MessageSettings.none then return end + + if msgtype.consoleTab ~= nil and (msgtype.consoleOption == nil or modules.client_options.getOption(msgtype.consoleOption)) then + modules.game_console.addText(text, msgtype, tr(msgtype.consoleTab)) + --TODO move to game_console + end + + if msgtype.screenTarget then + local label = messagesPanel:recursiveGetChildById(msgtype.screenTarget) + label:setText(text) + label:setColor(msgtype.color) + label:setVisible(true) + removeEvent(label.hideEvent) + label.hideEvent = scheduleEvent(function() label:setVisible(false) end, calculateVisibleTime(text)) + end +end + +function displayPrivateMessage(text) + displayMessage(254, text) +end + +function displayStatusMessage(text) + displayMessage(MessageModes.Status, text) +end + +function displayFailureMessage(text) + displayMessage(MessageModes.Failure, text) +end + +function displayGameMessage(text) + displayMessage(MessageModes.Game, text) +end + +function displayBroadcastMessage(text) + displayMessage(MessageModes.Warning, text) +end + +function clearMessages() + for _i,child in pairs(messagesPanel:recursiveGetChildren()) do + if child:getId():match('Label') then + child:hide() + removeEvent(child.hideEvent) + end + end +end + +function LocalPlayer:onAutoWalkFail(player) + modules.game_textmessage.displayFailureMessage(tr('There is no way.')) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otmod new file mode 100644 index 0000000..068d066 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otmod @@ -0,0 +1,9 @@ +Module + name: game_textmessage + description: Manage game text messages + author: edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ textmessage ] + @onLoad: init() + @onUnload: terminate() \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otui new file mode 100644 index 0000000..7af5ed1 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textmessage/textmessage.otui @@ -0,0 +1,40 @@ +TextMessageLabel < UILabel + font: verdana-11px-rounded + text-align: center + text-wrap: true + text-auto-resize: true + margin-bottom: 2 + visible: false + +Panel + anchors.fill: gameMapPanel + focusable: false + + Panel + id: centerTextMessagePanel + layout: + type: verticalBox + fit-children: true + width: 360 + anchors.centerIn: parent + + TextMessageLabel + id: highCenterLabel + TextMessageLabel + id: middleCenterLabel + TextMessageLabel + id: lowCenterLabel + + TextMessageLabel + id: privateLabel + anchors.top: parent.top + anchors.bottom: centerTextMessagePanel.top + anchors.horizontalCenter: parent.horizontalCenter + text-auto-resize: false + width: 275 + + TextMessageLabel + id: statusLabel + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.lua new file mode 100644 index 0000000..e4e4040 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.lua @@ -0,0 +1,144 @@ +windows = {} + +function init() + g_ui.importStyle('textwindow') + + connect(g_game, { onEditText = onGameEditText, + onEditList = onGameEditList, + onGameEnd = destroyWindows }) +end + +function terminate() + disconnect(g_game, { onEditText = onGameEditText, + onEditList = onGameEditList, + onGameEnd = destroyWindows }) + + destroyWindows() +end + +function destroyWindows() + for _,window in pairs(windows) do + window:destroy() + end + windows = {} +end + +function onGameEditText(id, itemId, maxLength, text, writer, time) + local textWindow = g_ui.createWidget('TextWindow', rootWidget) + + local writeable = #text < maxLength and maxLength > 0 + local textItem = textWindow:getChildById('textItem') + local description = textWindow:getChildById('description') + local textEdit = textWindow:getChildById('text') + local okButton = textWindow:getChildById('okButton') + local cancelButton = textWindow:getChildById('cancelButton') + + local textScroll = textWindow:getChildById('textScroll') + + if textItem:isHidden() then + textItem:show() + end + + textItem:setItemId(itemId) + textEdit:setMaxLength(maxLength) + textEdit:setText(text) + textEdit:setEditable(writeable) + textEdit:setCursorVisible(writeable) + + local desc = '' + if #writer > 0 then + desc = tr('You read the following, written by \n%s\n', writer) + if #time > 0 then + desc = desc .. tr('on %s.\n', time) + end + elseif #time > 0 then + desc = tr('You read the following, written on \n%s.\n', time) + end + + if #text == 0 and not writeable then + desc = tr("It is empty.") + elseif writeable then + desc = desc .. tr('You can enter new text.') + end + + local lines = #{string.find(desc, '\n')} + if lines < 2 then desc = desc .. '\n' end + + description:setText(desc) + + if not writeable then + textWindow:setText(tr('Show Text')) + cancelButton:hide() + cancelButton:setWidth(0) + okButton:setMarginRight(0) + else + textWindow:setText(tr('Edit Text')) + end + + if description:getHeight() < 64 then + description:setHeight(64) + end + + local function destroy() + textWindow:destroy() + table.removevalue(windows, textWindow) + end + + local doneFunc = function() + if writeable then + g_game.editText(id, textEdit:getText()) + end + destroy() + end + + okButton.onClick = doneFunc + cancelButton.onClick = destroy + + if not writeable then + textWindow.onEnter = doneFunc + end + + textWindow.onEscape = destroy + + table.insert(windows, textWindow) +end + +function onGameEditList(id, doorId, text) + local textWindow = g_ui.createWidget('TextWindow', rootWidget) + + local textEdit = textWindow:getChildById('text') + local description = textWindow:getChildById('description') + local okButton = textWindow:getChildById('okButton') + local cancelButton = textWindow:getChildById('cancelButton') + + local textItem = textWindow:getChildById('textItem') + if textItem and not textItem:isHidden() then + textItem:hide() + end + + textEdit:setMaxLength(8192) + textEdit:setText(text) + textEdit:setEditable(true) + description:setText(tr('Enter one name per line.')) + textWindow:setText(tr('Edit List')) + + if description:getHeight() < 64 then + description:setHeight(64) + end + + local function destroy() + textWindow:destroy() + table.removevalue(windows, textWindow) + end + + local doneFunc = function() + g_game.editList(id, doorId, textEdit:getText()) + destroy() + end + + okButton.onClick = doneFunc + cancelButton.onClick = destroy + textWindow.onEscape = destroy + + table.insert(windows, textWindow) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otmod new file mode 100644 index 0000000..d70d650 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otmod @@ -0,0 +1,10 @@ +Module + name: game_textwindow + description: Allow to edit text books and lists + author: edubart, BeniS + website: https://github.com/edubart/otclient + sandboxed: true + dependencies: [ game_interface ] + scripts: [ textwindow ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otui new file mode 100644 index 0000000..d803737 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_textwindow/textwindow.otui @@ -0,0 +1,53 @@ +TextWindow < MainWindow + id: textWindow + size: 300 280 + + Item + id: textItem + virtual: true + anchors.top: parent.top + anchors.left: parent.left + + Label + id: description + anchors.top: parent.top + anchors.left: textItem.right + anchors.right: parent.right + margin-left: 8 + text-auto-resize: true + text-align: left + text-wrap: true + + MultilineTextEdit + id: text + anchors.top: textScroll.top + anchors.left: parent.left + anchors.right: textScroll.left + anchors.bottom: textScroll.bottom + vertical-scrollbar: textScroll + text-wrap: true + + VerticalScrollBar + id: textScroll + anchors.top: description.bottom + anchors.bottom: okButton.top + anchors.right: parent.right + margin-top: 10 + margin-bottom: 10 + step: 16 + pixels-scroll: true + + Button + id: okButton + !text: tr('Ok') + anchors.bottom: parent.bottom + anchors.right: next.left + margin-right: 10 + width: 60 + + Button + id: cancelButton + !text: tr('Cancel') + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 60 diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.lua new file mode 100644 index 0000000..648bc10 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.lua @@ -0,0 +1,54 @@ +filename = nil +loaded = false + +function setFileName(name) + filename = name +end + +function isLoaded() + return loaded +end + +function load() + local version = g_game.getClientVersion() + local things = g_settings.getNode('things') + + local datPath, sprPath + if things and things["data"] ~= nil and things["sprites"] ~= nil then + datPath = '/things/' .. things["data"] + sprPath = '/things/' .. things["sprites"] + else + if filename then + datPath = resolvepath('/things/' .. filename) + sprPath = resolvepath('/things/' .. filename) + else + datPath = resolvepath('/things/' .. version .. '/Tibia') + sprPath = resolvepath('/things/' .. version .. '/Tibia') + end + end + + local errorMessage = '' + if not g_things.loadDat(datPath) then + if not g_game.getFeature(GameSpritesU32) then + g_game.enableFeature(GameSpritesU32) + if not g_things.loadDat(datPath) then + errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' + end + else + errorMessage = errorMessage .. tr("Unable to load dat file, please place a valid dat in '%s'", datPath) .. '\n' + end + end + if not g_sprites.loadSpr(sprPath, false) then + errorMessage = errorMessage .. tr("Unable to load spr file, please place a valid spr in '%s'", sprPath) + end + + loaded = (errorMessage:len() == 0) + + if errorMessage:len() > 0 then + local messageBox = displayErrorBox(tr('Error'), errorMessage) + addEvent(function() messageBox:raise() messageBox:focus() end) + + g_game.setClientVersion(0) + g_game.setProtocolVersion(0) + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.otmod new file mode 100644 index 0000000..c2536d3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_things/things.otmod @@ -0,0 +1,6 @@ +Module + name: game_things + description: Contains things spr and dat + reloadable: false + sandboxed: true + scripts: [things] diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua new file mode 100644 index 0000000..7538659 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.lua @@ -0,0 +1,148 @@ +unjustifiedPointsWindow = nil +unjustifiedPointsButton = nil +contentsPanel = nil + +openPvpSituationsLabel = nil +currentSkullWidget = nil +skullTimeLabel = nil + +dayProgressBar = nil +weekProgressBar = nil +monthProgressBar = nil + +daySkullWidget = nil +weekSkullWidget = nil +monthSkullWidget = nil + +function init() + connect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + connect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton', + tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle) + unjustifiedPointsButton:setOn(true) + unjustifiedPointsButton:hide() + + unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel()) + unjustifiedPointsWindow:disableResize() + unjustifiedPointsWindow:setup() + + contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel') + + openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel') + currentSkullWidget = contentsPanel:getChildById('currentSkullWidget') + skullTimeLabel = contentsPanel:getChildById('skullTimeLabel') + + dayProgressBar = contentsPanel:getChildById('dayProgressBar') + weekProgressBar = contentsPanel:getChildById('weekProgressBar') + monthProgressBar = contentsPanel:getChildById('monthProgressBar') + daySkullWidget = contentsPanel:getChildById('daySkullWidget') + weekSkullWidget = contentsPanel:getChildById('weekSkullWidget') + monthSkullWidget = contentsPanel:getChildById('monthSkullWidget') + + if g_game.isOnline() then + online() + end +end + +function terminate() + disconnect(g_game, { onGameStart = online, + onUnjustifiedPointsChange = onUnjustifiedPointsChange, + onOpenPvpSituationsChange = onOpenPvpSituationsChange }) + disconnect(LocalPlayer, { onSkullChange = onSkullChange } ) + + unjustifiedPointsWindow:destroy() + unjustifiedPointsButton:destroy() +end + +function onMiniWindowClose() + unjustifiedPointsButton:setOn(false) +end + +function toggle() + if unjustifiedPointsButton:isOn() then + unjustifiedPointsWindow:close() + unjustifiedPointsButton:setOn(false) + else + unjustifiedPointsWindow:open() + unjustifiedPointsButton:setOn(true) + end +end + +function online() + if g_game.getFeature(GameUnjustifiedPoints) then + unjustifiedPointsButton:show() + else + unjustifiedPointsButton:hide() + unjustifiedPointsWindow:close() + end + + refresh() +end + +function refresh() + local localPlayer = g_game.getLocalPlayer() + + local unjustifiedPoints = g_game.getUnjustifiedPoints() + onUnjustifiedPointsChange(unjustifiedPoints) + + onSkullChange(localPlayer, localPlayer:getSkull()) + onOpenPvpSituationsChange(g_game.getOpenPvpSituations()) +end + +function onSkullChange(localPlayer, skull) + if not localPlayer:isLocalPlayer() then return end + + if skull == SkullRed or skull == SkullBlack then + currentSkullWidget:setIcon(getSkullImagePath(skull)) + currentSkullWidget:setTooltip('Remaining skull time') + else + currentSkullWidget:setIcon('') + currentSkullWidget:setTooltip('You have no skull') + end + + daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) + monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull))) +end + +function onOpenPvpSituationsChange(amount) + openPvpSituationsLabel:setText(amount) +end + +local function getColorByKills(kills) + if kills < 2 then + return 'red' + elseif kills < 3 then + return 'yellow' + end + + return 'green' +end + +function onUnjustifiedPointsChange(unjustifiedPoints) + if unjustifiedPoints.skullTime == 0 then + skullTimeLabel:setText('No skull') + skullTimeLabel:setTooltip('You have no skull') + else + skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days') + skullTimeLabel:setTooltip('Remaining skull time') + end + + dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100) + dayProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsDayRemaining)) + dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kill%s left.', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) + dayProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's'))) + + weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100) + weekProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsWeekRemaining)) + weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kill%s left.', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) + weekProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's'))) + + monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100) + monthProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsMonthRemaining)) + monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kill%s left.', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) + monthProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's'))) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod new file mode 100644 index 0000000..178a414 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otmod @@ -0,0 +1,8 @@ +Module + name: game_unjustifiedpoints + description: View unjustified points + author: Summ + sandboxed: true + scripts: [ unjustifiedpoints ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui new file mode 100644 index 0000000..8f0d663 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_unjustifiedpoints/unjustifiedpoints.otui @@ -0,0 +1,81 @@ +SkullProgressBar < ProgressBar + height: 13 + margin: 4 18 0 10 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + +SkullWidget < UIWidget + size: 13 13 + margin-right: 2 + anchors.right: parent.right + image-source: /images/game/skull_socket + +MiniWindow + id: unjustifiedPointsWindow + !text: tr('Unjustified Points') + height: 114 + icon: /images/topbuttons/unjustifiedpoints + @onClose: modules.game_unjustifiedpoints.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + Label + anchors.top: parent.top + anchors.left: parent.left + !text: tr('Open PvP') + !tooltip: tr('Open PvP Situations') + phantom: false + margin-top: 2 + margin-left: 10 + + Label + id: openPvpSituationsLabel + anchors.top: prev.bottom + anchors.left: parent.left + font: verdana-11px-rounded + margin-left: 12 + phantom: false + + Label + anchors.top: parent.top + anchors.right: parent.right + !text: tr('Skull Time') + margin-top: 2 + margin-right: 10 + + SkullWidget + id: currentSkullWidget + anchors.top: prev.bottom + margin-right: 10 + + Label + id: skullTimeLabel + anchors.top: prev.top + anchors.right: prev.left + font: verdana-11px-rounded + margin-right: 6 + phantom: false + + SkullProgressBar + id: dayProgressBar + margin-top: 10 + + SkullWidget + id: daySkullWidget + anchors.top: prev.top + + SkullProgressBar + id: weekProgressBar + + SkullWidget + id: weekSkullWidget + anchors.top: prev.top + + SkullProgressBar + id: monthProgressBar + + SkullWidget + id: monthSkullWidget + anchors.top: prev.top diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/addvip.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/addvip.otui new file mode 100644 index 0000000..bc88a2a --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/addvip.otui @@ -0,0 +1,39 @@ +MainWindow + size: 256 128 + !text: tr('Add to VIP list') + @onEnter: modules.game_viplist.addVip() + @onEscape: modules.game_viplist.destroyAddWindow() + + Label + !text: tr('Please enter a character name:') + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + TextEdit + id: name + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 4 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + @onClick: modules.game_viplist.addVip() + + Button + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + @onClick: modules.game_viplist.destroyAddWindow() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/editvip.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/editvip.otui new file mode 100644 index 0000000..096489f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/editvip.otui @@ -0,0 +1,138 @@ +IconButton < CheckBox + size: 20 20 + image-source: /images/game/viplist/vipcheckbox + image-size: 20 20 + image-border: 3 + margin: 2 + icon-source: /images/game/viplist/icons + icon-size: 12 12 + icon-rect: 0 0 12 12 + icon-clip: 0 0 12 12 + icon-offset: 4 6 + + $first: + margin-left: 0 + + $!checked: + image-clip: 26 0 26 26 + + $hover !checked: + image-clip: 78 0 26 26 + + $checked: + image-clip: 0 0 26 26 + + $hover checked: + image-clip: 52 0 26 26 + +MainWindow + size: 272 170 + !text: tr('Edit VIP list entry') + + Label + id: nameLabel + text: Name + anchors.top: parent.top + anchors.left: parent.left + color: green + width: 180 + + Label + !text: tr('Description') .. ':' + anchors.top: prev.bottom + anchors.left: parent.left + text-offset: 0 3 + height: 20 + margin-top: 5 + + TextEdit + id: descriptionText + anchors.top: prev.top + anchors.left: prev.right + anchors.right: parent.right + margin: 0 5 + + Label + !text: tr('Notify-Login') .. ':' + anchors.top: prev.bottom + anchors.left: parent.left + text-offset: 0 3 + height: 20 + margin-top: 5 + + CheckBox + id: checkBoxNotify + anchors.top: prev.top + anchors.left: prev.right + margin: 2 6 + + UIWidget + layout: horizontalBox + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + IconButton + id: icon0 + + IconButton + id: icon1 + icon-clip: 12 0 12 12 + + IconButton + id: icon2 + icon-clip: 24 0 12 12 + + IconButton + id: icon3 + icon-clip: 36 0 12 12 + + IconButton + id: icon4 + icon-clip: 48 0 12 12 + + IconButton + id: icon5 + icon-clip: 60 0 12 12 + + IconButton + id: icon6 + icon-clip: 72 0 12 12 + + IconButton + id: icon7 + icon-clip: 84 0 12 12 + + IconButton + id: icon8 + icon-clip: 96 0 12 12 + + IconButton + id: icon9 + icon-clip: 108 0 12 12 + + IconButton + id: icon10 + icon-clip: 120 0 12 12 + + HorizontalSeparator + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: next.top + margin-bottom: 10 + + Button + id: buttonOK + !text: tr('Ok') + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin-right: 10 + + Button + id: buttonCancel + !text: tr('Cancel') + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.lua new file mode 100644 index 0000000..195664c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.lua @@ -0,0 +1,426 @@ +vipWindow = nil +vipButton = nil +addVipWindow = nil +editVipWindow = nil +vipInfo = {} + +function init() + connect(g_game, { onGameStart = refresh, + onGameEnd = clear, + onAddVip = onAddVip, + onVipStateChange = onVipStateChange }) + + + g_keyboard.bindKeyDown('Ctrl+P', toggle) + + vipButton = modules.client_topmenu.addRightGameToggleButton('vipListButton', tr('VIP List') .. ' (Ctrl+P)', '/images/topbuttons/viplist', toggle, false, 3) + vipButton:setOn(true) + vipWindow = g_ui.loadUI('viplist', modules.game_interface.getRightPanel()) + + if not g_game.getFeature(GameAdditionalVipInfo) then + loadVipInfo() + end + refresh() + vipWindow:setup() +end + +function terminate() + g_keyboard.unbindKeyDown('Ctrl+P') + disconnect(g_game, { onGameStart = refresh, + onGameEnd = clear, + onAddVip = onAddVip, + onVipStateChange = onVipStateChange }) + + if not g_game.getFeature(GameAdditionalVipInfo) then + saveVipInfo() + end + + if addVipWindow then + addVipWindow:destroy() + end + + if editVipWindow then + editVipWindow:destroy() + end + + vipWindow:destroy() + vipButton:destroy() +end + +function loadVipInfo() + local settings = g_settings.getNode('VipList') + if not settings then + vipInfo = {} + return + end + vipInfo = settings['VipInfo'] or {} +end + +function saveVipInfo() + settings = {} + settings['VipInfo'] = vipInfo + g_settings.mergeNode('VipList', settings) +end + + +function refresh() + clear() + for id,vip in pairs(g_game.getVips()) do + onAddVip(id, unpack(vip)) + end + + vipWindow:setContentMinimumHeight(38) +end + +function clear() + local vipList = vipWindow:getChildById('contentsPanel') + vipList:destroyChildren() +end + +function toggle() + if vipButton:isOn() then + vipWindow:close() + vipButton:setOn(false) + else + vipWindow:open() + vipButton:setOn(true) + end +end + +function onMiniWindowClose() + vipButton:setOn(false) +end + +function createAddWindow() + if not addVipWindow then + addVipWindow = g_ui.displayUI('addvip') + end +end + +function createEditWindow(widget) + if editVipWindow then + return + end + + editVipWindow = g_ui.displayUI('editvip') + + local name = widget:getText() + local id = widget:getId():sub(4) + + local okButton = editVipWindow:getChildById('buttonOK') + local cancelButton = editVipWindow:getChildById('buttonCancel') + + local nameLabel = editVipWindow:getChildById('nameLabel') + nameLabel:setText(name) + + local descriptionText = editVipWindow:getChildById('descriptionText') + descriptionText:appendText(widget:getTooltip()) + + local notifyCheckBox = editVipWindow:getChildById('checkBoxNotify') + notifyCheckBox:setChecked(widget.notifyLogin) + + local iconRadioGroup = UIRadioGroup.create() + for i = VipIconFirst, VipIconLast do + iconRadioGroup:addWidget(editVipWindow:recursiveGetChildById('icon' .. i)) + end + iconRadioGroup:selectWidget(editVipWindow:recursiveGetChildById('icon' .. widget.iconId)) + + local cancelFunction = function() + editVipWindow:destroy() + iconRadioGroup:destroy() + editVipWindow = nil + end + + local saveFunction = function() + local vipList = vipWindow:getChildById('contentsPanel') + if not widget or not vipList:hasChild(widget) then + cancelFunction() + return + end + + local name = widget:getText() + local state = widget.vipState + local description = descriptionText:getText() + local iconId = tonumber(iconRadioGroup:getSelectedWidget():getId():sub(5)) + local notify = notifyCheckBox:isChecked() + + if g_game.getFeature(GameAdditionalVipInfo) then + g_game.editVip(id, description, iconId, notify) + else + if notify ~= false or #description > 0 or iconId > 0 then + vipInfo[id] = {description = description, iconId = iconId, notifyLogin = notify} + else + vipInfo[id] = nil + end + end + + widget:destroy() + onAddVip(id, name, state, description, iconId, notify) + + editVipWindow:destroy() + iconRadioGroup:destroy() + editVipWindow = nil + end + + cancelButton.onClick = cancelFunction + okButton.onClick = saveFunction + + editVipWindow.onEscape = cancelFunction + editVipWindow.onEnter = saveFunction +end + +function destroyAddWindow() + addVipWindow:destroy() + addVipWindow = nil +end + +function addVip() + g_game.addVip(addVipWindow:getChildById('name'):getText()) + destroyAddWindow() +end + +function removeVip(widgetOrName) + if not widgetOrName then + return + end + + local widget + local vipList = vipWindow:getChildById('contentsPanel') + if type(widgetOrName) == 'string' then + local entries = vipList:getChildren() + for i = 1, #entries do + if entries[i]:getText():lower() == widgetOrName:lower() then + widget = entries[i] + break + end + end + if not widget then + return + end + else + widget = widgetOrName + end + + if widget then + local id = widget:getId():sub(4) + g_game.removeVip(id) + vipList:removeChild(widget) + if vipInfo[id] and g_game.getFeature(GameAdditionalVipInfo) then + vipInfo[id] = nil + end + end +end + +function hideOffline(state) + settings = {} + settings['hideOffline'] = state + g_settings.mergeNode('VipList', settings) + + refresh() +end + +function isHiddingOffline() + local settings = g_settings.getNode('VipList') + if not settings then + return false + end + return settings['hideOffline'] +end + +function getSortedBy() + local settings = g_settings.getNode('VipList') + if not settings or not settings['sortedBy'] then + return 'status' + end + return settings['sortedBy'] +end + +function sortBy(state) + settings = {} + settings['sortedBy'] = state + g_settings.mergeNode('VipList', settings) + + refresh() +end + +function onAddVip(id, name, state, description, iconId, notify) + if not name or name:len() == 0 then + return + end + + local vipList = vipWindow:getChildById('contentsPanel') + local childrenCount = vipList:getChildCount() + for i=1,childrenCount do + local child = vipList:getChildByIndex(i) + if child:getText() == name then + return -- don't add duplicated vips + end + end + + local label = g_ui.createWidget('VipListLabel') + label.onMousePress = onVipListLabelMousePress + label:setId('vip' .. id) + label:setText(name) + + if not g_game.getFeature(GameAdditionalVipInfo) then + local tmpVipInfo = vipInfo[tostring(id)] + label.iconId = 0 + label.notifyLogin = false + if tmpVipInfo then + if tmpVipInfo.iconId then + label:setImageClip(torect((tmpVipInfo.iconId * 12) .. ' 0 12 12')) + label.iconId = tmpVipInfo.iconId + end + if tmpVipInfo.description then + label:setTooltip(tmpVipInfo.description) + end + label.notifyLogin = tmpVipInfo.notifyLogin or false + end + else + label:setTooltip(description) + label:setImageClip(torect((iconId * 12) .. ' 0 12 12')) + label.iconId = iconId + label.notifyLogin = notify + end + + if state == VipState.Online then + label:setColor('#00ff00') + elseif state == VipState.Pending then + label:setColor('#ffca38') + else + label:setColor('#ff0000') + end + + label.vipState = state + + label:setPhantom(false) + connect(label, { onDoubleClick = function () g_game.openPrivateChannel(label:getText()) return true end } ) + + if state == VipState.Offline and isHiddingOffline() then + label:setVisible(false) + end + + local nameLower = name:lower() + local childrenCount = vipList:getChildCount() + + for i=1,childrenCount do + local child = vipList:getChildByIndex(i) + if (state == VipState.Online and child.vipState ~= VipState.Online and getSortedBy() == 'status') + or (label.iconId > child.iconId and getSortedBy() == 'type') then + vipList:insertChild(i, label) + return + end + + if (((state ~= VipState.Online and child.vipState ~= VipState.Online) or (state == VipState.Online and child.vipState == VipState.Online)) and getSortedBy() == 'status') + or (label.iconId == child.iconId and getSortedBy() == 'type') or getSortedBy() == 'name' then + + local childText = child:getText():lower() + local length = math.min(childText:len(), nameLower:len()) + + for j=1,length do + if nameLower:byte(j) < childText:byte(j) then + vipList:insertChild(i, label) + return + elseif nameLower:byte(j) > childText:byte(j) then + break + elseif j == nameLower:len() then -- We are at the end of nameLower, and its shorter than childText, thus insert before + vipList:insertChild(i, label) + return + end + end + end + end + + vipList:insertChild(childrenCount+1, label) +end + +function onVipStateChange(id, state) + local vipList = vipWindow:getChildById('contentsPanel') + local label = vipList:getChildById('vip' .. id) + if not label then + return + end + local name = label:getText() + local description = label:getTooltip() + local iconId = label.iconId + local notify = label.notifyLogin + label:destroy() + + onAddVip(id, name, state, description, iconId, notify) + + if notify and state ~= VipState.Pending then + modules.game_textmessage.displayFailureMessage(tr('%s has logged %s.', name, (state == VipState.Online and 'in' or 'out'))) + end +end + +function onVipListMousePress(widget, mousePos, mouseButton) + if mouseButton ~= MouseRightButton then return end + + local vipList = vipWindow:getChildById('contentsPanel') + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Add new VIP'), function() createAddWindow() end) + + menu:addSeparator() + if not isHiddingOffline() then + menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) + else + menu:addOption(tr('Show Offline'), function() hideOffline(false) end) + end + + if not(getSortedBy() == 'name') then + menu:addOption(tr('Sort by name'), function() sortBy('name') end) + end + + if not(getSortedBy() == 'status') then + menu:addOption(tr('Sort by status'), function() sortBy('status') end) + end + + if not(getSortedBy() == 'type') then + menu:addOption(tr('Sort by type'), function() sortBy('type') end) + end + + menu:display(mousePos) + + return true +end + +function onVipListLabelMousePress(widget, mousePos, mouseButton) + if mouseButton ~= MouseRightButton then return end + + local vipList = vipWindow:getChildById('contentsPanel') + + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) end) + menu:addOption(tr('Add new VIP'), function() createAddWindow() end) + menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end) + menu:addOption(tr('Remove %s', widget:getText()), function() if widget then removeVip(widget) end end) + menu:addSeparator() + menu:addOption(tr('Copy Name'), function() g_window.setClipboardText(widget:getText()) end) + + if modules.game_console.getOwnPrivateTab() then + menu:addSeparator() + menu:addOption(tr('Invite to private chat'), function() g_game.inviteToOwnChannel(widget:getText()) end) + menu:addOption(tr('Exclude from private chat'), function() g_game.excludeFromOwnChannel(widget:getText()) end) + end + + if not isHiddingOffline() then + menu:addOption(tr('Hide Offline'), function() hideOffline(true) end) + else + menu:addOption(tr('Show Offline'), function() hideOffline(false) end) + end + + if not(getSortedBy() == 'name') then + menu:addOption(tr('Sort by name'), function() sortBy('name') end) + end + + if not(getSortedBy() == 'status') then + menu:addOption(tr('Sort by status'), function() sortBy('status') end) + end + + menu:display(mousePos) + + return true +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otmod new file mode 100644 index 0000000..c88d44d --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otmod @@ -0,0 +1,9 @@ +Module + name: game_viplist + description: Manage vip list window + author: baxnie, edubart + website: https://github.com/edubart/otclient + sandboxed: true + scripts: [ viplist ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otui new file mode 100644 index 0000000..d3eece3 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_viplist/viplist.otui @@ -0,0 +1,26 @@ +VipListLabel < GameLabel + margin-top: 2 + text-offset: 16 0 + image-rect: 0 0 12 12 + image-clip: 0 0 12 12 + image-source: /images/game/viplist/icons + font: verdana-11px-monochrome + phantom: false + + $first: + margin-top: 5 + +MiniWindow + id: vipWindow + !text: tr('VIP List') + height: 100 + icon: /images/topbuttons/viplist + @onClose: modules.game_viplist.onMiniWindowClose() + &save: true + &autoOpen: false + + MiniWindowContents + layout: verticalBox + padding-left: 5 + padding-right: 5 + &onMousePress: modules.game_viplist.onVipListMousePress diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.lua new file mode 100644 index 0000000..88af196 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.lua @@ -0,0 +1,431 @@ +smartWalkDirs = {} +smartWalkDir = nil +wsadWalking = false +nextWalkDir = nil +lastWalkDir = nil +lastFinishedStep = 0 +autoWalkEvent = nil +firstStep = true +walkLock = 0 +walkEvent = nil +lastWalk = 0 +lastTurn = 0 +lastTurnDirection = 0 +lastStop = 0 +lastManualWalk = 0 +autoFinishNextServerWalk = 0 +turnKeys = {} + +function init() + connect(LocalPlayer, { + onPositionChange = onPositionChange, + onWalk = onWalk, + onTeleport = onTeleport, + onWalkFinish = onWalkFinish, + onCancelWalk = onCancelWalk + }) + + modules.game_interface.getRootPanel().onFocusChange = stopSmartWalk + bindKeys() +end + +function terminate() + disconnect(LocalPlayer, { + onPositionChange = onPositionChange, + onWalk = onWalk, + onTeleport = onTeleport, + onWalkFinish = onWalkFinish + }) + removeEvent(autoWalkEvent) + stopSmartWalk() + unbindKeys() + disableWSAD() +end + +function bindKeys() + bindWalkKey('Up', North) + bindWalkKey('Right', East) + bindWalkKey('Down', South) + bindWalkKey('Left', West) + bindWalkKey('Numpad8', North) + bindWalkKey('Numpad9', NorthEast) + bindWalkKey('Numpad6', East) + bindWalkKey('Numpad3', SouthEast) + bindWalkKey('Numpad2', South) + bindWalkKey('Numpad1', SouthWest) + bindWalkKey('Numpad4', West) + bindWalkKey('Numpad7', NorthWest) + + bindTurnKey('Ctrl+Up', North) + bindTurnKey('Ctrl+Right', East) + bindTurnKey('Ctrl+Down', South) + bindTurnKey('Ctrl+Left', West) + bindTurnKey('Ctrl+Numpad8', North) + bindTurnKey('Ctrl+Numpad6', East) + bindTurnKey('Ctrl+Numpad2', South) + bindTurnKey('Ctrl+Numpad4', West) +end + +function unbindKeys() + unbindWalkKey('Up', North) + unbindWalkKey('Right', East) + unbindWalkKey('Down', South) + unbindWalkKey('Left', West) + unbindWalkKey('Numpad8', North) + unbindWalkKey('Numpad9', NorthEast) + unbindWalkKey('Numpad6', East) + unbindWalkKey('Numpad3', SouthEast) + unbindWalkKey('Numpad2', South) + unbindWalkKey('Numpad1', SouthWest) + unbindWalkKey('Numpad4', West) + unbindWalkKey('Numpad7', NorthWest) + + unbindTurnKey('Ctrl+Up', North) + unbindTurnKey('Ctrl+Right', East) + unbindTurnKey('Ctrl+Down', South) + unbindTurnKey('Ctrl+Left', West) + unbindTurnKey('Ctrl+Numpad8', North) + unbindTurnKey('Ctrl+Numpad6', East) + unbindTurnKey('Ctrl+Numpad2', South) + unbindTurnKey('Ctrl+Numpad4', West) +end + +function enableWSAD() + if wsadWalking then + return + end + wsadWalking = true + local player = g_game.getLocalPlayer() + if player then + player:lockWalk(100) -- 100 ms walk lock for all directions + end + + bindWalkKey("W", North) + bindWalkKey("D", East) + bindWalkKey("S", South) + bindWalkKey("A", West) + + bindTurnKey("Ctrl+W", North) + bindTurnKey("Ctrl+D", East) + bindTurnKey("Ctrl+S", South) + bindTurnKey("Ctrl+A", West) + + bindWalkKey("E", NorthEast) + bindWalkKey("Q", NorthWest) + bindWalkKey("C", SouthEast) + bindWalkKey("Z", SouthWest) +end + +function disableWSAD() + if not wsadWalking then + return + end + wsadWalking = false + + unbindWalkKey("W") + unbindWalkKey("D") + unbindWalkKey("S") + unbindWalkKey("A") + + unbindTurnKey("Ctrl+W") + unbindTurnKey("Ctrl+D") + unbindTurnKey("Ctrl+S") + unbindTurnKey("Ctrl+A") + + unbindWalkKey("E") + unbindWalkKey("Q") + unbindWalkKey("C") + unbindWalkKey("Z") +end + +function bindWalkKey(key, dir) + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown(key, function() changeWalkDir(dir) end, gameRootPanel, true) + g_keyboard.bindKeyUp(key, function() changeWalkDir(dir, true) end, gameRootPanel, true) + g_keyboard.bindKeyPress(key, function(c, k, ticks) smartWalk(dir, ticks) end, gameRootPanel) +end + +function unbindWalkKey(key) + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown(key, gameRootPanel) + g_keyboard.unbindKeyUp(key, gameRootPanel) + g_keyboard.unbindKeyPress(key, gameRootPanel) +end + +function bindTurnKey(key, dir) + turnKeys[key] = dir + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.bindKeyDown(key, function() turn(dir, false) end, gameRootPanel) + g_keyboard.bindKeyPress(key, function() turn(dir, true) end, gameRootPanel) + g_keyboard.bindKeyUp(key, function() local player = g_game.getLocalPlayer() if player then player:lockWalk(200) end end, gameRootPanel) +end + +function unbindTurnKey(key) + turnKeys[key] = nil + local gameRootPanel = modules.game_interface.getRootPanel() + g_keyboard.unbindKeyDown(key, gameRootPanel) + g_keyboard.unbindKeyPress(key, gameRootPanel) + g_keyboard.unbindKeyUp(key, gameRootPanel) +end + +function stopSmartWalk() + smartWalkDirs = {} + smartWalkDir = nil +end + +function changeWalkDir(dir, pop) + while table.removevalue(smartWalkDirs, dir) do end + if pop then + if #smartWalkDirs == 0 then + stopSmartWalk() + return + end + else + table.insert(smartWalkDirs, 1, dir) + end + + smartWalkDir = smartWalkDirs[1] + if modules.client_options.getOption('smartWalk') and #smartWalkDirs > 1 then + for _,d in pairs(smartWalkDirs) do + if (smartWalkDir == North and d == West) or (smartWalkDir == West and d == North) then + smartWalkDir = NorthWest + break + elseif (smartWalkDir == North and d == East) or (smartWalkDir == East and d == North) then + smartWalkDir = NorthEast + break + elseif (smartWalkDir == South and d == West) or (smartWalkDir == West and d == South) then + smartWalkDir = SouthWest + break + elseif (smartWalkDir == South and d == East) or (smartWalkDir == East and d == South) then + smartWalkDir = SouthEast + break + end + end + end +end + +function smartWalk(dir, ticks) + walkEvent = scheduleEvent(function() + if g_keyboard.getModifiers() == KeyboardNoModifier then + local direction = smartWalkDir or dir + walk(direction, ticks) + return true + end + return false + end, 20) +end + +function canChangeFloorDown(pos) + pos.z = pos.z + 1 + toTile = g_map.getTile(pos) + return toTile and toTile:hasElevation(3) +end + +function canChangeFloorUp(pos) + pos.z = pos.z - 1 + toTile = g_map.getTile(pos) + return toTile and toTile:isWalkable() +end + +function onPositionChange(player, newPos, oldPos) +end + +function onWalk(player, newPos, oldPos) + if autoFinishNextServerWalk + 200 > g_clock.millis() then + player:finishServerWalking() + end +end + +function onTeleport(player, newPos, oldPos) + if not newPos or not oldPos then + return + end + -- floor change is also teleport + if math.abs(newPos.x - oldPos.x) >= 3 or math.abs(newPos.y - oldPos.y) >= 3 or math.abs(newPos.z - oldPos.z) >= 2 then + -- far teleport, lock walk for 100ms + walkLock = g_clock.millis() + g_settings.getNumber('walkTeleportDelay') + else + walkLock = g_clock.millis() + g_settings.getNumber('walkStairsDelay') + end + nextWalkDir = nil -- cancel autowalk +end + +function onWalkFinish(player) + lastFinishedStep = g_clock.millis() + if nextWalkDir ~= nil then + removeEvent(autoWalkEvent) + autoWalkEvent = addEvent(function() if nextWalkDir ~= nil then walk(nextWalkDir, 0) end end, false) + end +end + +function onCancelWalk(player) + player:lockWalk(50) +end + +function walk(dir, ticks) + lastManualWalk = g_clock.millis() + local player = g_game.getLocalPlayer() + if not player or g_game.isDead() or player:isDead() then + return + end + + if player:isWalkLocked() then + nextWalkDir = nil + return + end + + if g_game.isFollowing() then + g_game.cancelFollow() + end + + if player:isAutoWalking() then + if lastStop + 100 < g_clock.millis() then + lastStop = g_clock.millis() + player:stopAutoWalk() + g_game.stop() + end + end + + local dash = false + local ignoredCanWalk = false + if not g_game.getFeature(GameNewWalking) then + dash = g_settings.getBoolean("dash", false) + end + + local ticksToNextWalk = player:getStepTicksLeft() + if not player:canWalk(dir) then -- canWalk return false when previous walk is not finished or not confirmed by server + if dash then + ignoredCanWalk = true + else + if ticksToNextWalk < 500 and (lastWalkDir ~= dir or ticks == 0) then + nextWalkDir = dir + end + if ticksToNextWalk < 30 and lastFinishedStep + 400 > g_clock.millis() and nextWalkDir == nil then -- clicked walk 20 ms too early, try to execute again as soon possible to keep smooth walking + nextWalkDir = dir + end + return + end + end + + --if nextWalkDir ~= nil and lastFinishedStep + 200 < g_clock.millis() then + -- print("Cancel " .. nextWalkDir) + -- nextWalkDir = nil + --end + if nextWalkDir ~= nil and nextWalkDir ~= lastWalkDir then + dir = nextWalkDir + end + + local toPos = player:getPrewalkingPosition(true) + if dir == North then + toPos.y = toPos.y - 1 + elseif dir == East then + toPos.x = toPos.x + 1 + elseif dir == South then + toPos.y = toPos.y + 1 + elseif dir == West then + toPos.x = toPos.x - 1 + elseif dir == NorthEast then + toPos.x = toPos.x + 1 + toPos.y = toPos.y - 1 + elseif dir == SouthEast then + toPos.x = toPos.x + 1 + toPos.y = toPos.y + 1 + elseif dir == SouthWest then + toPos.x = toPos.x - 1 + toPos.y = toPos.y + 1 + elseif dir == NorthWest then + toPos.x = toPos.x - 1 + toPos.y = toPos.y - 1 + end + local toTile = g_map.getTile(toPos) + + if walkLock >= g_clock.millis() and lastWalkDir == dir then + nextWalkDir = nil + return + end + + if firstStep and lastWalkDir == dir and lastWalk + g_settings.getNumber('walkFirstStepDelay') > g_clock.millis() then + firstStep = false + walkLock = lastWalk + g_settings.getNumber('walkFirstStepDelay') + return + end + + if dash and lastWalkDir == dir and lastWalk + 50 > g_clock.millis() then + return + end + + firstStep = (not player:isWalking() and lastFinishedStep + 100 < g_clock.millis() and walkLock + 100 < g_clock.millis()) + if player:isServerWalking() and not dash then + walkLock = walkLock + math.max(g_settings.getNumber('walkFirstStepDelay'), 100) + end + + nextWalkDir = nil + removeEvent(autoWalkEvent) + autoWalkEvent = nil + local preWalked = false + if toTile and toTile:isWalkable() then + if not player:isServerWalking() and not ignoredCanWalk then + player:preWalk(dir) + preWalked = true + end + else + local playerTile = player:getTile() + if (playerTile and playerTile:hasElevation(3) and canChangeFloorUp(toPos)) or canChangeFloorDown(toPos) or (toTile and toTile:isEmpty() and not toTile:isBlocking()) then + player:lockWalk(100) + elseif player:isServerWalking() then + g_game.stop() + return + elseif not toTile then + player:lockWalk(100) -- bug fix for missing stairs down on map + else + if g_app.isMobile() and dir <= Directions.West then + turn(dir, ticks > 0) + end + return -- not walkable tile + end + end + + if player:isServerWalking() and not dash then + g_game.stop() + player:finishServerWalking() + autoFinishNextServerWalk = g_clock.millis() + 200 + end + g_game.walk(dir, preWalked) + + if not firstStep and lastWalkDir ~= dir then + walkLock = g_clock.millis() + g_settings.getNumber('walkTurnDelay') + end + + lastWalkDir = dir + lastWalk = g_clock.millis() + return true +end + +function turn(dir, repeated) + local player = g_game.getLocalPlayer() + if player:isWalking() and player:getWalkDirection() == dir and not player:isServerWalking() then + return + end + + removeEvent(walkEvent) + + if not repeated or (lastTurn + 100 < g_clock.millis()) then + g_game.turn(dir) + changeWalkDir(dir) + lastTurn = g_clock.millis() + if not repeated then + lastTurn = g_clock.millis() + 50 + end + lastTurnDirection = dir + nextWalkDir = nil + player:lockWalk(g_settings.getNumber('walkCtrlTurnDelay')) + end +end + +function checkTurn() + for keys, direction in pairs(turnKeys) do + if g_keyboard.areKeysPressed(keys) then + turn(direction, false) + end + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.otmod new file mode 100644 index 0000000..9e4d991 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/game_walking/walking.otmod @@ -0,0 +1,9 @@ +Module + name: game_walking + description: Control walking and turns + author: otclient.ovh + website: http://otclient.ovh + scripts: [ walking ] + dependencies: [ game_interface ] + @onLoad: init() + @onUnload: terminate() diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/const.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/const.lua new file mode 100644 index 0000000..6d3721f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/const.lua @@ -0,0 +1,397 @@ +-- @docconsts @{ + +FloorHigher = 0 +FloorLower = 15 + +SkullNone = 0 +SkullYellow = 1 +SkullGreen = 2 +SkullWhite = 3 +SkullRed = 4 +SkullBlack = 5 +SkullOrange = 6 + +ShieldNone = 0 +ShieldWhiteYellow = 1 +ShieldWhiteBlue = 2 +ShieldBlue = 3 +ShieldYellow = 4 +ShieldBlueSharedExp = 5 +ShieldYellowSharedExp = 6 +ShieldBlueNoSharedExpBlink = 7 +ShieldYellowNoSharedExpBlink = 8 +ShieldBlueNoSharedExp = 9 +ShieldYellowNoSharedExp = 10 +ShieldGray = 11 + +EmblemNone = 0 +EmblemGreen = 1 +EmblemRed = 2 +EmblemBlue = 3 +EmblemMember = 4 +EmblemOther = 5 + +VipIconFirst = 0 +VipIconLast = 10 + +Directions = { + North = 0, + East = 1, + South = 2, + West = 3, + NorthEast = 4, + SouthEast = 5, + SouthWest = 6, + NorthWest = 7 +} + +Skill = { + Fist = 0, + Club = 1, + Sword = 2, + Axe = 3, + Distance = 4, + Shielding = 5, + Fishing = 6, + CriticalChance = 7, + CriticalDamage = 8, + LifeLeechChance = 9, + LifeLeechAmount = 10, + ManaLeechChance = 11, + ManaLeechAmount = 12 +} + +North = Directions.North +East = Directions.East +South = Directions.South +West = Directions.West +NorthEast = Directions.NorthEast +SouthEast = Directions.SouthEast +SouthWest = Directions.SouthWest +NorthWest = Directions.NorthWest + +FightOffensive = 1 +FightBalanced = 2 +FightDefensive = 3 + +DontChase = 0 +ChaseOpponent = 1 + +PVPWhiteDove = 0 +PVPWhiteHand = 1 +PVPYellowHand = 2 +PVPRedFist = 3 + +GameProtocolChecksum = 1 +GameAccountNames = 2 +GameChallengeOnLogin = 3 +GamePenalityOnDeath = 4 +GameNameOnNpcTrade = 5 +GameDoubleFreeCapacity = 6 +GameDoubleExperience = 7 +GameTotalCapacity = 8 +GameSkillsBase = 9 +GamePlayerRegenerationTime = 10 +GameChannelPlayerList = 11 +GamePlayerMounts = 12 +GameEnvironmentEffect = 13 +GameCreatureEmblems = 14 +GameItemAnimationPhase = 15 +GameMagicEffectU16 = 16 +GamePlayerMarket = 17 +GameSpritesU32 = 18 +GameTileAddThingWithStackpos = 19 +GameOfflineTrainingTime = 20 +GamePurseSlot = 21 +GameFormatCreatureName = 22 +GameSpellList = 23 +GameClientPing = 24 +GameExtendedClientPing = 25 +GameDoubleHealth = 28 +GameDoubleSkills = 29 +GameChangeMapAwareRange = 30 +GameMapMovePosition = 31 +GameAttackSeq = 32 +GameBlueNpcNameColor = 33 +GameDiagonalAnimatedText = 34 +GameLoginPending = 35 +GameNewSpeedLaw = 36 +GameForceFirstAutoWalkStep = 37 +GameMinimapRemove = 38 +GameDoubleShopSellAmount = 39 +GameContainerPagination = 40 +GameThingMarks = 41 +GameLooktypeU16 = 42 +GamePlayerStamina = 43 +GamePlayerAddons = 44 +GameMessageStatements = 45 +GameMessageLevel = 46 +GameNewFluids = 47 +GamePlayerStateU16 = 48 +GameNewOutfitProtocol = 49 +GamePVPMode = 50 +GameWritableDate = 51 +GameAdditionalVipInfo = 52 +GameBaseSkillU16 = 53 +GameCreatureIcons = 54 +GameHideNpcNames = 55 +GameSpritesAlphaChannel = 56 +GamePremiumExpiration = 57 +GameBrowseField = 58 +GameEnhancedAnimations = 59 +GameOGLInformation = 60 +GameMessageSizeCheck = 61 +GamePreviewState = 62 +GameLoginPacketEncryption = 63 +GameClientVersion = 64 +GameContentRevision = 65 +GameExperienceBonus = 66 +GameAuthenticator = 67 +GameUnjustifiedPoints = 68 +GameSessionKey = 69 +GameDeathType = 70 +GameIdleAnimations = 71 +GameKeepUnawareTiles = 72 +GameIngameStore = 73 +GameIngameStoreHighlights = 74 +GameIngameStoreServiceType = 75 +GameAdditionalSkills = 76 +GameDistanceEffectU16 = 77 +GamePrey = 78 +GameDoubleMagicLevel = 79 + +GameExtendedOpcode = 80 +GameMinimapLimitedToSingleFloor = 81 +GameSendWorldName = 82 + +GameDoubleLevel = 83 +GameDoubleSoul = 84 +GameDoublePlayerGoodsMoney = 85 +GameCreatureWalkthrough = 86 -- add Walkthrough for versions less than 854, unpass = msg->getU8(); in protocolgameparse.cpp +GameDoubleTradeMoney = 87 +GameSequencedPackets = 88 +GameTibia12Protocol = 89 + +GameNewWalking = 90 +GameSlowerManualWalking = 91 +GameItemTooltip = 93 + +GameBot = 95 +GameBiggerMapCache = 96 +GameForceLight = 97 +GameNoDebug = 98 +GameBotProtection = 99 + +GameCreatureDirectionPassable = 100 +GameFasterAnimations = 101 +GameCenteredOutfits = 102 +GameSendIdentifiers = 103 +GameWingsAndAura = 104 +GamePlayerStateU32 = 105 +GameOutfitShaders = 106 +GameForceAllowItemHotkeys = 107 +GameCountU16 = 108 +GameDrawAuraOnTop = 109 + +GamePacketSizeU32 = 110 +GamePacketCompression = 111 + +GameOldInformationBar = 112 +GameHealthInfoBackground = 113 +GameWingOffset = 114 +GameAuraFrontAndBack = 115 -- To use that: First layer is bottom/back, second (blend layer) is top/front + +GameMapDrawGroundFirst = 116 -- useful for big auras & wings +GameMapIgnoreCorpseCorrection = 117 +GameDontCacheFiles = 118 -- doesn't work with encryption and compression +GameBigAurasCenter = 119 -- Automatic negative offset for aura bigger than 32x32 + +LastGameFeature = 130 + +TextColors = { + red = '#f55e5e', --'#c83200' + orange = '#f36500', --'#c87832' + yellow = '#ffff00', --'#e6c832' + green = '#00EB00', --'#3fbe32' + lightblue = '#5ff7f7', + blue = '#9f9dfd', + --blue1 = '#6e50dc', + --blue2 = '#3264c8', + --blue3 = '#0096c8', + white = '#ffffff', --'#bebebe' +} + +MessageModes = { + None = 0, + Say = 1, + Whisper = 2, + Yell = 3, + PrivateFrom = 4, + PrivateTo = 5, + ChannelManagement = 6, + Channel = 7, + ChannelHighlight = 8, + Spell = 9, + NpcFrom = 10, + NpcTo = 11, + GamemasterBroadcast = 12, + GamemasterChannel = 13, + GamemasterPrivateFrom = 14, + GamemasterPrivateTo = 15, + Login = 16, + Warning = 17, + Game = 18, + Failure = 19, + Look = 20, + DamageDealed = 21, + DamageReceived = 22, + Heal = 23, + Exp = 24, + DamageOthers = 25, + HealOthers = 26, + ExpOthers = 27, + Status = 28, + Loot = 29, + TradeNpc = 30, + Guild = 31, + PartyManagement = 32, + Party = 33, + BarkLow = 34, + BarkLoud = 35, + Report = 36, + HotkeyUse = 37, + TutorialHint = 38, + Thankyou = 39, + Market = 40, + Mana = 41, + BeyondLast = 42, + MonsterYell = 43, + MonsterSay = 44, + Red = 45, + Blue = 46, + RVRChannel = 47, + RVRAnswer = 48, + RVRContinue = 49, + GameHighlight = 50, + NpcFromStartBlock = 51, + Last = 52, + Invalid = 255, +} + +OTSERV_RSA = "1091201329673994292788609605089955415282375029027981291234687579" .. + "3726629149257644633073969600111060390723088861007265581882535850" .. + "3429057592827629436413108566029093628212635953836686562675849720" .. + "6207862794310902180176810615217550567108238764764442605581471797" .. + "07119674283982419152118103759076030616683978566631413" + +CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618" .. + "4334395343554449668205332383339435179772895415509701210392836078" .. + "6959821132214473291575712138800495033169914814069637740318278150" .. + "2907336840325241747827401343576296990629870233111328210165697754" .. + "88792221429527047321331896351555606801473202394175817" + +-- set to the latest Tibia.pic signature to make otclient compatible with official tibia +PIC_SIGNATURE = 0x56C5DDE7 + +OsTypes = { + Linux = 1, + Windows = 2, + Flash = 3, + OtclientLinux = 10, + OtclientWindows = 11, + OtclientMac = 12, +} + +PathFindResults = { + Ok = 0, + Position = 1, + Impossible = 2, + TooFar = 3, + NoWay = 4, +} + +PathFindFlags = { + AllowNullTiles = 1, + AllowCreatures = 2, + AllowNonPathable = 4, + AllowNonWalkable = 8, +} + +VipState = { + Offline = 0, + Online = 1, + Pending = 2, +} + +ExtendedIds = { + Activate = 0, + Locale = 1, + Ping = 2, + Sound = 3, + Game = 4, + Particles = 5, + MapShader = 6, + NeedsUpdate = 7 +} + +PreviewState = { + Default = 0, + Inactive = 1, + Active = 2 +} + +Blessings = { + None = 0, + Adventurer = 1, + SpiritualShielding = 2, + EmbraceOfTibia = 4, + FireOfSuns = 8, + WisdomOfSolitude = 16, + SparkOfPhoenix = 32 +} + +DeathType = { + Regular = 0, + Blessed = 1 +} + +ProductType = { + Other = 0, + NameChange = 1 +} + +StoreErrorType = { + NoError = -1, + PurchaseError = 0, + NetworkError = 1, + HistoryError = 2, + TransferError = 3, + Information = 4 +} + +StoreState = { + None = 0, + New = 1, + Sale = 2, + Timed = 3 +} + +AccountStatus = { + Ok = 0, + Frozen = 1, + Suspended = 2, +} + +SubscriptionStatus = { + Free = 0, + Premium = 1, +} + +ChannelEvent = { + Join = 0, + Leave = 1, + Invite = 2, + Exclude = 3, +} + +-- @} diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/creature.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/creature.lua new file mode 100644 index 0000000..1568b57 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/creature.lua @@ -0,0 +1,176 @@ +-- @docclass Creature + +-- @docconsts @{ + +SkullNone = 0 +SkullYellow = 1 +SkullGreen = 2 +SkullWhite = 3 +SkullRed = 4 +SkullBlack = 5 +SkullOrange = 6 + +ShieldNone = 0 +ShieldWhiteYellow = 1 +ShieldWhiteBlue = 2 +ShieldBlue = 3 +ShieldYellow = 4 +ShieldBlueSharedExp = 5 +ShieldYellowSharedExp = 6 +ShieldBlueNoSharedExpBlink = 7 +ShieldYellowNoSharedExpBlink = 8 +ShieldBlueNoSharedExp = 9 +ShieldYellowNoSharedExp = 10 + +EmblemNone = 0 +EmblemGreen = 1 +EmblemRed = 2 +EmblemBlue = 3 + +NpcIconNone = 0 +NpcIconChat = 1 +NpcIconTrade = 2 +NpcIconQuest = 3 +NpcIconTradeQuest = 4 + +CreatureTypePlayer = 0 +CreatureTypeMonster = 1 +CreatureTypeNpc = 2 +CreatureTypeSummonOwn = 3 +CreatureTypeSummonOther = 4 + +-- @} + +function getNextSkullId(skullId) + if skullId == SkullRed or skullId == SkullBlack then + return SkullBlack + end + return SkullRed +end + +function getSkullImagePath(skullId) + local path + if skullId == SkullYellow then + path = '/images/game/skulls/skull_yellow' + elseif skullId == SkullGreen then + path = '/images/game/skulls/skull_green' + elseif skullId == SkullWhite then + path = '/images/game/skulls/skull_white' + elseif skullId == SkullRed then + path = '/images/game/skulls/skull_red' + elseif skullId == SkullBlack then + path = '/images/game/skulls/skull_black' + elseif skullId == SkullOrange then + path = '/images/game/skulls/skull_orange' + end + return path +end + +function getShieldImagePathAndBlink(shieldId) + local path, blink + if shieldId == ShieldWhiteYellow then + path, blink = '/images/game/shields/shield_yellow_white', false + elseif shieldId == ShieldWhiteBlue then + path, blink = '/images/game/shields/shield_blue_white', false + elseif shieldId == ShieldBlue then + path, blink = '/images/game/shields/shield_blue', false + elseif shieldId == ShieldYellow then + path, blink = '/images/game/shields/shield_yellow', false + elseif shieldId == ShieldBlueSharedExp then + path, blink = '/images/game/shields/shield_blue_shared', false + elseif shieldId == ShieldYellowSharedExp then + path, blink = '/images/game/shields/shield_yellow_shared', false + elseif shieldId == ShieldBlueNoSharedExpBlink then + path, blink = '/images/game/shields/shield_blue_not_shared', true + elseif shieldId == ShieldYellowNoSharedExpBlink then + path, blink = '/images/game/shields/shield_yellow_not_shared', true + elseif shieldId == ShieldBlueNoSharedExp then + path, blink = '/images/game/shields/shield_blue_not_shared', false + elseif shieldId == ShieldYellowNoSharedExp then + path, blink = '/images/game/shields/shield_yellow_not_shared', false + elseif shieldId == ShieldGray then + path, blink = '/images/game/shields/shield_gray', false + end + return path, blink +end + +function getEmblemImagePath(emblemId) + local path + if emblemId == EmblemGreen then + path = '/images/game/emblems/emblem_green' + elseif emblemId == EmblemRed then + path = '/images/game/emblems/emblem_red' + elseif emblemId == EmblemBlue then + path = '/images/game/emblems/emblem_blue' + elseif emblemId == EmblemMember then + path = '/images/game/emblems/emblem_member' + elseif emblemId == EmblemOther then + path = '/images/game/emblems/emblem_other' + end + return path +end + +function getTypeImagePath(creatureType) + local path + if creatureType == CreatureTypeSummonOwn then + path = '/images/game/creaturetype/summon_own' + elseif creatureType == CreatureTypeSummonOther then + path = '/images/game/creaturetype/summon_other' + end + return path +end + +function getIconImagePath(iconId) + local path + if iconId == NpcIconChat then + path = '/images/game/npcicons/icon_chat' + elseif iconId == NpcIconTrade then + path = '/images/game/npcicons/icon_trade' + elseif iconId == NpcIconQuest then + path = '/images/game/npcicons/icon_quest' + elseif iconId == NpcIconTradeQuest then + path = '/images/game/npcicons/icon_tradequest' + end + return path +end + +function Creature:onSkullChange(skullId) + local imagePath = getSkullImagePath(skullId) + if imagePath then + self:setSkullTexture(imagePath) + end +end + +function Creature:onShieldChange(shieldId) + local imagePath, blink = getShieldImagePathAndBlink(shieldId) + if imagePath then + self:setShieldTexture(imagePath, blink) + end +end + +function Creature:onEmblemChange(emblemId) + local imagePath = getEmblemImagePath(emblemId) + if imagePath then + self:setEmblemTexture(imagePath) + end +end + +function Creature:onTypeChange(typeId) + local imagePath = getTypeImagePath(typeId) + if imagePath then + self:setTypeTexture(imagePath) + end +end + +function Creature:onIconChange(iconId) + local imagePath = getIconImagePath(iconId) + if imagePath then + self:setIconTexture(imagePath) + end +end + +function Creature:setOutfitShader(shader) + local outfit = self:getOutfit() + outfit.shader = shader + self:setOutfit(outfit) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/game.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/game.lua new file mode 100644 index 0000000..816685c --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/game.lua @@ -0,0 +1,118 @@ +function g_game.getRsa() + return G.currentRsa +end + +function g_game.findPlayerItem(itemId, subType) + local localPlayer = g_game.getLocalPlayer() + if localPlayer then + for slot = InventorySlotFirst, InventorySlotLast do + local item = localPlayer:getInventoryItem(slot) + if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + return item + end + end + end + + return g_game.findItemInContainers(itemId, subType) +end + +function g_game.chooseRsa(host) + if G.currentRsa ~= CIPSOFT_RSA and G.currentRsa ~= OTSERV_RSA then return end + if host:ends('.tibia.com') or host:ends('.cipsoft.com') then + g_game.setRsa(CIPSOFT_RSA) + + if g_app.getOs() == 'windows' then + g_game.setCustomOs(OsTypes.Windows) + else + g_game.setCustomOs(OsTypes.Linux) + end + else + if G.currentRsa == CIPSOFT_RSA then + g_game.setCustomOs(-1) + end + g_game.setRsa(OTSERV_RSA) + end + + -- Hack fix to resolve some 760 login issues + if g_game.getClientVersion() <= 760 then + g_game.setCustomOs(2) + end +end + +function g_game.setRsa(rsa, e) + e = e or '65537' + g_crypt.rsaSetPublicKey(rsa, e) + G.currentRsa = rsa +end + +function g_game.isOfficialTibia() + return G.currentRsa == CIPSOFT_RSA +end + +function g_game.getSupportedClients() + return { + 740, 741, 750, 760, 770, 772, + 780, 781, 782, 790, 792, + + 800, 810, 811, 820, 821, 822, + 830, 831, 840, 842, 850, 853, + 854, 855, 857, 860, 861, 862, + 870, 871, + + 900, 910, 920, 931, 940, 943, + 944, 951, 952, 953, 954, 960, + 961, 963, 970, 971, 972, 973, + 980, 981, 982, 983, 984, 985, + 986, + + 1000, 1001, 1002, 1010, 1011, + 1012, 1013, 1020, 1021, 1022, + 1030, 1031, 1032, 1033, 1034, + 1035, 1036, 1037, 1038, 1039, + 1040, 1041, 1050, 1051, 1052, + 1053, 1054, 1055, 1056, 1057, + 1058, 1059, 1060, 1061, 1062, + 1063, 1064, 1070, 1071, 1072, + 1073, 1074, 1075, 1076, 1080, + 1081, 1082, 1090, 1091, 1092, + 1093, 1094, 1095, 1096, 1097, + 1098, 1099 + } +end + +-- The client version and protocol version where +-- unsynchronized for some releases, not sure if this +-- will be the normal standard. + +-- Client Version: Publicly given version when +-- downloading Cipsoft client. + +-- Protocol Version: Previously was the same as +-- the client version, but was unsychronized in some +-- releases, now it needs to be verified and added here +-- if it does not match the client version. + +-- Reason for defining both: The server now requires a +-- Client version and Protocol version from the client. + +-- Important: Use getClientVersion for specific protocol +-- features to ensure we are using the proper version. + +function g_game.getClientProtocolVersion(client) + local clients = { + [980] = 971, + [981] = 973, + [982] = 974, + [983] = 975, + [984] = 976, + [985] = 977, + [986] = 978, + [1001] = 979, + [1002] = 980 + } + return clients[client] or client +end + +if not G.currentRsa then + g_game.setRsa(OTSERV_RSA) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/gamelib.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/gamelib.otmod new file mode 100644 index 0000000..ab71364 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/gamelib.otmod @@ -0,0 +1,23 @@ +Module + name: gamelib + description: Contains game related classes + author: OTClient team + website: https://github.com/edubart/otclient + + @onLoad: | + dofile 'const' + dofile 'util' + dofile 'protocol' + dofile 'protocollogin' + dofile 'protocolgame' + dofile 'position' + dofile 'game' + + dofile 'creature' + dofile 'player' + dofile 'market' + dofile 'textmessages' + dofile 'thing' + dofile 'spells' + + dofiles 'ui' diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/market.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/market.lua new file mode 100644 index 0000000..e88e5f8 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/market.lua @@ -0,0 +1,202 @@ +MarketMaxAmount = 2000 +MarketMaxAmountStackable = 64000 +MarketMaxPrice = 999999999 +MarketMaxOffers = 100 + +MarketAction = { + Buy = 0, + Sell = 1 +} + +MarketRequest = { + MyOffers = 0xFFFE, + MyHistory = 0xFFFF +} + +MarketOfferState = { + Active = 0, + Cancelled = 1, + Expired = 2, + Accepted = 3, + AcceptedEx = 255 +} + +MarketCategory = { + All = 0, + Armors = 1, + Amulets = 2, + Boots = 3, + Containers = 4, + Decoration = 5, + Food = 6, + HelmetsHats = 7, + Legs = 8, + Others = 9, + Potions = 10, + Rings = 11, + Runes = 12, + Shields = 13, + Tools = 14, + Valuables = 15, + Ammunition = 16, + Axes = 17, + Clubs = 18, + DistanceWeapons = 19, + Swords = 20, + WandsRods = 21, + PremiumScrolls = 22, + TibiaCoins = 23, + CreatureProducs = 24, + Unknown1 = 25, + Unknown2 = 26, + StashRetrieve = 27, + Unknown3 = 28, + Unknown4 = 29, + Gold = 30, + Unassigned = 31, + MetaWeapons = 255 +} + +MarketCategory.First = MarketCategory.Armors +MarketCategory.Last = MarketCategory.Unassigned + +MarketCategoryWeapons = { + [MarketCategory.Ammunition] = { slots = {255} }, + [MarketCategory.Axes] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.Clubs] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.DistanceWeapons] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.Swords] = { slots = {255, InventorySlotOther, InventorySlotLeft} }, + [MarketCategory.WandsRods] = { slots = {255, InventorySlotOther, InventorySlotLeft} } +} + +MarketCategoryStrings = { + [0] = 'All', + [1] = 'Armors', + [2] = 'Amulets', + [3] = 'Boots', + [4] = 'Containers', + [5] = 'Decoration', + [6] = 'Food', + [7] = 'Helmets and Hats', + [8] = 'Legs', + [9] = 'Others', + [10] = 'Potions', + [11] = 'Rings', + [12] = 'Runes', + [13] = 'Shields', + [14] = 'Tools', + [15] = 'Valuables', + [16] = 'Ammunition', + [17] = 'Axes', + [18] = 'Clubs', + [19] = 'Distance Weapons', + [20] = 'Swords', + [21] = 'Wands and Rods', + [22] = 'Premium Scrolls', + [23] = 'Tibia Coins', + [24] = 'Creature Products', + [25] = 'Unknown 1', + [26] = 'Unknown 2', + [27] = 'Stash Retrieve', + [28] = 'Unknown 3', + [29] = 'Unknown 4', + [30] = 'Gold', + [31] = 'Unassigned', + [255] = 'Weapons' +} + +function getMarketCategoryName(id) + if table.haskey(MarketCategoryStrings, id) then + return MarketCategoryStrings[id] + end +end + +function getMarketCategoryId(name) + local id = table.find(MarketCategoryStrings, name) + if id then + return id + end +end + +MarketItemDescription = { + Armor = 1, + Attack = 2, + Container = 3, + Defense = 4, + General = 5, + DecayTime = 6, + Combat = 7, + MinLevel = 8, + MinMagicLevel = 9, + Vocation = 10, + Rune = 11, + Ability = 12, + Charges = 13, + WeaponName = 14, + Weight = 15, + Imbuements = 16 +} + +MarketItemDescription.First = MarketItemDescription.Armor +MarketItemDescription.Last = MarketItemDescription.Weight + +MarketItemDescriptionStrings = { + [1] = 'Armor', + [2] = 'Attack', + [3] = 'Container', + [4] = 'Defense', + [5] = 'Description', + [6] = 'Use Time', + [7] = 'Combat', + [8] = 'Min Level', + [9] = 'Min Magic Level', + [10] = 'Vocation', + [11] = 'Rune', + [12] = 'Ability', + [13] = 'Charges', + [14] = 'Weapon Type', + [15] = 'Weight', + [16] = 'Imbuements' +} + +function getMarketDescriptionName(id) + if table.haskey(MarketItemDescriptionStrings, id) then + return MarketItemDescriptionStrings[id] + end +end + +function getMarketDescriptionId(name) + local id = table.find(MarketItemDescriptionStrings, name) + if id then + return id + end +end + +MarketSlotFilters = { + [InventorySlotOther] = "Two-Handed", + [InventorySlotLeft] = "One-Handed", + [255] = "Any" +} + +MarketFilters = { + Vocation = 1, + Level = 2, + Depot = 3, + SearchAll = 4 +} + +MarketFilters.First = MarketFilters.Vocation +MarketFilters.Last = MarketFilters.Depot + +function getMarketSlotFilterId(name) + local id = table.find(MarketSlotFilters, name) + if id then + return id + end +end + +function getMarketSlotFilterName(id) + if table.haskey(MarketSlotFilters, id) then + return MarketSlotFilters[id] + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/player.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/player.lua new file mode 100644 index 0000000..1ac7291 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/player.lua @@ -0,0 +1,151 @@ +-- @docclass Player + +PlayerStates = { + None = 0, + Poison = 1, + Burn = 2, + Energy = 4, + Drunk = 8, + ManaShield = 16, + Paralyze = 32, + Haste = 64, + Swords = 128, + Drowning = 256, + Freezing = 512, + Dazzled = 1024, + Cursed = 2048, + PartyBuff = 4096, + PzBlock = 8192, + Pz = 16384, + Bleeding = 32768, + Hungry = 65536 +} + +InventorySlotOther = 0 +InventorySlotHead = 1 +InventorySlotNeck = 2 +InventorySlotBack = 3 +InventorySlotBody = 4 +InventorySlotRight = 5 +InventorySlotLeft = 6 +InventorySlotLeg = 7 +InventorySlotFeet = 8 +InventorySlotFinger = 9 +InventorySlotAmmo = 10 +InventorySlotPurse = 11 + +InventorySlotFirst = 1 +InventorySlotLast = 10 + +function Player:isPartyLeader() + local shield = self:getShield() + return (shield == ShieldWhiteYellow or + shield == ShieldYellow or + shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp) +end + +function Player:isPartyMember() + local shield = self:getShield() + return (shield == ShieldWhiteYellow or + shield == ShieldYellow or + shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp or + shield == ShieldBlueSharedExp or + shield == ShieldBlueNoSharedExpBlink or + shield == ShieldBlueNoSharedExp or + shield == ShieldBlue) +end + +function Player:isPartySharedExperienceActive() + local shield = self:getShield() + return (shield == ShieldYellowSharedExp or + shield == ShieldYellowNoSharedExpBlink or + shield == ShieldYellowNoSharedExp or + shield == ShieldBlueSharedExp or + shield == ShieldBlueNoSharedExpBlink or + shield == ShieldBlueNoSharedExp) +end + +function Player:hasVip(creatureName) + for id, vip in pairs(g_game.getVips()) do + if (vip[1] == creatureName) then return true end + end + return false +end + +function Player:isMounted() + local outfit = self:getOutfit() + return outfit.mount ~= nil and outfit.mount > 0 +end + +function Player:toggleMount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(not self:isMounted()) + end +end + +function Player:mount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(true) + end +end + +function Player:dismount() + if g_game.getFeature(GamePlayerMounts) then + g_game.mount(false) + end +end + +function Player:getItem(itemId, subType) + return g_game.findPlayerItem(itemId, subType or -1) +end + +function Player:getItems(itemId, subType) + local subType = subType or -1 + + local items = {} + for i=InventorySlotFirst,InventorySlotLast do + local item = self:getInventoryItem(i) + if item and item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + table.insert(items, item) + end + end + + for i, container in pairs(g_game.getContainers()) do + for j, item in pairs(container:getItems()) do + if item:getId() == itemId and (subType == -1 or item:getSubType() == subType) then + item.container = container + table.insert(items, item) + end + end + end + return items +end + +function Player:getItemsCount(itemId) + local items, count = self:getItems(itemId), 0 + for i=1,#items do + count = count + items[i]:getCount() + end + return count +end + +function Player:hasState(state, states) + if not states then + states = self:getStates() + end + + for i = 1, 32 do + local pow = math.pow(2, i-1) + if pow > states then break end + + local states = bit32.band(states, pow) + if states == state then + return true + end + end + return false +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/position.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/position.lua new file mode 100644 index 0000000..b41a0fa --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/position.lua @@ -0,0 +1,37 @@ +Position = {} + +function Position.equals(pos1, pos2) + return pos1.x == pos2.x and pos1.y == pos2.y and pos1.z == pos2.z +end + +function Position.greaterThan(pos1, pos2, orEqualTo) + if orEqualTo then + return pos1.x >= pos2.x or pos1.y >= pos2.y or pos1.z >= pos2.z + else + return pos1.x > pos2.x or pos1.y > pos2.y or pos1.z > pos2.z + end +end + +function Position.lessThan(pos1, pos2, orEqualTo) + if orEqualTo then + return pos1.x <= pos2.x or pos1.y <= pos2.y or pos1.z <= pos2.z + else + return pos1.x < pos2.x or pos1.y < pos2.y or pos1.z < pos2.z + end +end + +function Position.isInRange(pos1, pos2, xRange, yRange) + return math.abs(pos1.x-pos2.x) <= xRange and math.abs(pos1.y-pos2.y) <= yRange and pos1.z == pos2.z; +end + +function Position.isValid(pos) + return not (pos.x == 65535 and pos.y == 65535 and pos.z == 255) +end + +function Position.distance(pos1, pos2) + return math.sqrt(math.pow((pos2.x - pos1.x), 2) + math.pow((pos2.y - pos1.y), 2)) +end + +function Position.manhattanDistance(pos1, pos2) + return math.abs(pos2.x - pos1.x) + math.abs(pos2.y - pos1.y) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocol.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocol.lua new file mode 100644 index 0000000..205a938 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocol.lua @@ -0,0 +1,206 @@ +GameServerOpcodes = { + GameServerInitGame = 10, + GameServerGMActions = 11, + GameServerEnterGame = 15, + GameServerLoginError = 20, + GameServerLoginAdvice = 21, + GameServerLoginWait = 22, + GameServerAddCreature = 23, + GameServerPingBack = 29, + GameServerPing = 30, + GameServerChallenge = 31, + GameServerDeath = 40, + + -- all in game opcodes must be greater than 50 + GameServerFirstGameOpcode = 50, + + -- otclient ONLY + GameServerExtendedOpcode = 50, + + -- NOTE: add any custom opcodes in this range + -- 51 - 99 + + -- original tibia ONLY + GameServerFullMap = 100, + GameServerMapTopRow = 101, + GameServerMapRightRow = 102, + GameServerMapBottomRow = 103, + GameServerMapLeftRow = 104, + GameServerUpdateTile = 105, + GameServerCreateOnMap = 106, + GameServerChangeOnMap = 107, + GameServerDeleteOnMap = 108, + GameServerMoveCreature = 109, + GameServerOpenContainer = 110, + GameServerCloseContainer = 111, + GameServerCreateContainer = 112, + GameServerChangeInContainer = 113, + GameServerDeleteInContainer = 114, + GameServerSetInventory = 120, + GameServerDeleteInventory = 121, + GameServerOpenNpcTrade = 122, + GameServerPlayerGoods = 123, + GameServerCloseNpcTrade = 124, + GameServerOwnTrade = 125, + GameServerCounterTrade = 126, + GameServerCloseTrade = 127, + GameServerAmbient = 130, + GameServerGraphicalEffect = 131, + GameServerTextEffect = 132, + GameServerMissleEffect = 133, + GameServerMarkCreature = 134, + GameServerTrappers = 135, + GameServerCreatureHealth = 140, + GameServerCreatureLight = 141, + GameServerCreatureOutfit = 142, + GameServerCreatureSpeed = 143, + GameServerCreatureSkull = 144, + GameServerCreatureParty = 145, + GameServerCreatureUnpass = 146, + GameServerEditText = 150, + GameServerEditList = 151, + GameServerPlayerDataBasic = 159, -- 910 + GameServerPlayerData = 160, + GameServerPlayerSkills = 161, + GameServerPlayerState = 162, + GameServerClearTarget = 163, + GameServerSpellDelay = 164, -- 870 + GameServerSpellGroupDelay = 165, -- 870 + GameServerMultiUseDelay = 166, -- 870 + GameServerTalk = 170, + GameServerChannels = 171, + GameServerOpenChannel = 172, + GameServerOpenPrivateChannel = 173, + GameServerRuleViolationChannel = 174, + GameServerRuleViolationRemove = 175, + GameServerRuleViolationCancel = 176, + GameServerRuleViolationLock = 177, + GameServerOpenOwnChannel = 178, + GameServerCloseChannel = 179, + GameServerTextMessage = 180, + GameServerCancelWalk = 181, + GameServerWalkWait = 182, + GameServerFloorChangeUp = 190, + GameServerFloorChangeDown = 191, + GameServerChooseOutfit = 200, + GameServerVipAdd = 210, + GameServerVipLogin = 211, + GameServerVipLogout = 212, + GameServerTutorialHint = 220, + GameServerAutomapFlag = 221, + GameServerCoinBalance = 223, -- 1080 + GameServerStoreError = 224, -- 1080 + GameServerRequestPurchaseData = 225, -- 1080 + GameServerQuestLog = 240, + GameServerQuestLine = 241, + GameServerCoinBalanceUpdating = 242, -- 1080 + GameServerChannelEvent = 243, -- 910 + GameServerItemInfo = 244, -- 910 + GameServerPlayerInventory = 245, -- 910 + GameServerMarketEnter = 246, -- 944 + GameServerMarketLeave = 247, -- 944 + GameServerMarketDetail = 248, -- 944 + GameServerMarketBrowse = 249, -- 944 + GameServerShowModalDialog = 250, -- 960 + GameServerStore = 251, -- 1080 + GameServerStoreOffers = 252, -- 1080 + GameServerStoreTransactionHistory = 253, -- 1080 + GameServerStoreCompletePurchase = 254 -- 1080 +} + +ClientOpcodes = { + ClientEnterAccount = 1, + ClientEnterGame = 10, + ClientLeaveGame = 20, + ClientPing = 29, + ClientPingBack = 30, + + -- all in game opcodes must be equal or greater than 50 + ClientFirstGameOpcode = 50, + + -- otclient ONLY + ClientExtendedOpcode = 50, + + -- NOTE: add any custom opcodes in this range + -- 51 - 99 + + -- original tibia ONLY + ClientAutoWalk = 100, + ClientWalkNorth = 101, + ClientWalkEast = 102, + ClientWalkSouth = 103, + ClientWalkWest = 104, + ClientStop = 105, + ClientWalkNorthEast = 106, + ClientWalkSouthEast = 107, + ClientWalkSouthWest = 108, + ClientWalkNorthWest = 109, + ClientTurnNorth = 111, + ClientTurnEast = 112, + ClientTurnSouth = 113, + ClientTurnWest = 114, + ClientEquipItem = 119, -- 910 + ClientMove = 120, + ClientInspectNpcTrade = 121, + ClientBuyItem = 122, + ClientSellItem = 123, + ClientCloseNpcTrade = 124, + ClientRequestTrade = 125, + ClientInspectTrade = 126, + ClientAcceptTrade = 127, + ClientRejectTrade = 128, + ClientUseItem = 130, + ClientUseItemWith = 131, + ClientUseOnCreature = 132, + ClientRotateItem = 133, + ClientCloseContainer = 135, + ClientUpContainer = 136, + ClientEditText = 137, + ClientEditList = 138, + ClientLook = 140, + ClientTalk = 150, + ClientRequestChannels = 151, + ClientJoinChannel = 152, + ClientLeaveChannel = 153, + ClientOpenPrivateChannel = 154, + ClientCloseNpcChannel = 158, + ClientChangeFightModes = 160, + ClientAttack = 161, + ClientFollow = 162, + ClientInviteToParty = 163, + ClientJoinParty = 164, + ClientRevokeInvitation = 165, + ClientPassLeadership = 166, + ClientLeaveParty = 167, + ClientShareExperience = 168, + ClientDisbandParty = 169, + ClientOpenOwnChannel = 170, + ClientInviteToOwnChannel = 171, + ClientExcludeFromOwnChannel = 172, + ClientCancelAttackAndFollow = 190, + ClientRefreshContainer = 202, + ClientRequestOutfit = 210, + ClientChangeOutfit = 211, + ClientMount = 212, -- 870 + ClientAddVip = 220, + ClientRemoveVip = 221, + ClientBugReport = 230, + ClientRuleViolation = 231, + ClientDebugReport = 232, + ClientTransferCoins = 239, -- 1080 + ClientRequestQuestLog = 240, + ClientRequestQuestLine = 241, + ClientNewRuleViolation = 242, -- 910 + ClientRequestItemInfo = 243, -- 910 + ClientMarketLeave = 244, -- 944 + ClientMarketBrowse = 245, -- 944 + ClientMarketCreate = 246, -- 944 + ClientMarketCancel = 247, -- 944 + ClientMarketAccept = 248, -- 944 + ClientAnswerModalDialog = 249, -- 960 + ClientOpenStore = 250, -- 1080 + ClientRequestStoreOffers = 251, -- 1080 + ClientBuyStoreOffer = 252, -- 1080 + ClientOpenTransactionHistory = 253, -- 1080 + ClientRequestTransactionHistory = 254 -- 1080 +} diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocolgame.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocolgame.lua new file mode 100644 index 0000000..e340aaa --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocolgame.lua @@ -0,0 +1,138 @@ +local opcodeCallbacks = {} +local extendedCallbacks = {} +local extendedJSONCallbacks = {} +local extendedJSONData = {} +local maxPacketSize = 65000 + +function ProtocolGame:onOpcode(opcode, msg) + for i, callback in pairs(opcodeCallbacks) do + if i == opcode then + callback(self, msg) + return true + end + end + return false +end + +function ProtocolGame:onExtendedOpcode(opcode, buffer) + local callback = extendedCallbacks[opcode] + if callback then + callback(self, opcode, buffer) + end + + callback = extendedJSONCallbacks[opcode] + if callback then + local status = buffer:sub(1,1) -- O - just one message, S - start, P - part, E - end + local data = buffer:sub(2) + if status ~= "E" and status ~= "P" then + extendedJSONData[opcode] = "" + end + if status ~= "S" and status ~= "P" and status ~= "E" then + extendedJSONData[opcode] = buffer + else + extendedJSONData[opcode] = extendedJSONData[opcode] .. data + end + if status ~= "S" and status ~= "P" then + local json_status, json_data = pcall(function() return json.decode(extendedJSONData[opcode]) end) + extendedJSONData[opcode] = nil + if not json_status then + error("Invalid data in extended JSON opcode (" .. json_status .. "): " .. json_data) + return + end + callback(self, opcode, json_data) + end + end +end + +function ProtocolGame.registerOpcode(opcode, callback) + if opcodeCallbacks[opcode] then + error('opcode ' .. opcode .. ' already registered will be overriden') + end + + opcodeCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterOpcode(opcode) + opcodeCallbacks[opcode] = nil +end + +function ProtocolGame.registerExtendedOpcode(opcode, callback) + if not callback or type(callback) ~= 'function' then + error('Invalid callback.') + end + + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if extendedCallbacks[opcode] then + error('Opcode is already taken.') + end + + extendedCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterExtendedOpcode(opcode) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if not extendedCallbacks[opcode] then + error('Opcode is not registered.') + end + + extendedCallbacks[opcode] = nil +end + +function ProtocolGame.registerExtendedJSONOpcode(opcode, callback) + if not callback or type(callback) ~= 'function' then + error('Invalid callback.') + end + + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if extendedJSONCallbacks[opcode] then + error('Opcode is already taken.') + end + + extendedJSONCallbacks[opcode] = callback +end + +function ProtocolGame.unregisterExtendedJSONOpcode(opcode) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if not extendedJSONCallbacks[opcode] then + error('Opcode is not registered.') + end + + extendedJSONCallbacks[opcode] = nil +end + +function ProtocolGame:sendExtendedJSONOpcode(opcode, data) + if opcode < 0 or opcode > 255 then + error('Invalid opcode. Range: 0-255') + end + + if type(data) ~= "table" then + error('Invalid data type, should be table') + end + + local buffer = json.encode(data) + local s = {} + for i=1, #buffer, maxPacketSize do + s[#s+1] = buffer:sub(i,i+maxPacketSize - 1) + end + if #s == 1 then + self:sendExtendedOpcode(opcode, s[1]) + return + end + self:sendExtendedOpcode(opcode, "S" .. s[1]) + for i=2,#s - 1 do + self:sendExtendedOpcode(opcode, "P" .. s[i]) + end + self:sendExtendedOpcode(opcode, "E" .. s[#s]) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocollogin.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocollogin.lua new file mode 100644 index 0000000..d83a963 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/protocollogin.lua @@ -0,0 +1,300 @@ +-- @docclass +ProtocolLogin = extends(Protocol, "ProtocolLogin") + +LoginServerError = 10 +LoginServerTokenSuccess = 12 +LoginServerTokenError = 13 +LoginServerUpdate = 17 +LoginServerMotd = 20 +LoginServerUpdateNeeded = 30 +LoginServerSessionKey = 40 +LoginServerCharacterList = 100 +LoginServerExtendedCharacterList = 101 +LoginServerProxyList = 110 + +-- Since 10.76 +LoginServerRetry = 10 +LoginServerErrorNew = 11 + +function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken, stayLogged) + if string.len(host) == 0 or port == nil or port == 0 then + signalcall(self.onLoginError, self, tr("You must enter a valid server address and port.")) + return + end + + self.accountName = accountName + self.accountPassword = accountPassword + self.authenticatorToken = authenticatorToken + self.stayLogged = stayLogged + self.connectCallback = self.sendLoginPacket + + self:connect(host, port) +end + +function ProtocolLogin:cancelLogin() + self:disconnect() +end + +function ProtocolLogin:sendLoginPacket() + local msg = OutputMessage.create() + msg:addU8(ClientOpcodes.ClientEnterAccount) + msg:addU16(g_game.getOs()) + if g_game.getCustomProtocolVersion() > 0 then + msg:addU16(g_game.getCustomProtocolVersion()) + else + msg:addU16(g_game.getProtocolVersion()) + end + + if g_game.getFeature(GameClientVersion) then + msg:addU32(g_game.getClientVersion()) + end + + if g_game.getFeature(GameContentRevision) then + msg:addU16(g_things.getContentRevision()) + msg:addU16(0) + else + msg:addU32(g_things.getDatSignature()) + end + msg:addU32(g_sprites.getSprSignature()) + msg:addU32(PIC_SIGNATURE) + + if g_game.getFeature(GamePreviewState) then + msg:addU8(0) + end + + local offset = msg:getMessageSize() + if g_game.getFeature(GameLoginPacketEncryption) then + -- first RSA byte must be 0 + msg:addU8(0) + + -- xtea key + self:generateXteaKey() + local xteaKey = self:getXteaKey() + msg:addU32(xteaKey[1]) + msg:addU32(xteaKey[2]) + msg:addU32(xteaKey[3]) + msg:addU32(xteaKey[4]) + end + + if g_game.getFeature(GameAccountNames) then + msg:addString(self.accountName) + else + msg:addU32(tonumber(self.accountName)) + msg:addU32(tonumber(self.accountName)) + end + + msg:addString(self.accountPassword) + + if self.getLoginExtendedData then + local data = self:getLoginExtendedData() + msg:addString(data) + else + msg:addString("OTCv8") + local version = g_app.getVersion():split(" ")[1]:gsub("%.", "") + if version:len() == 2 then + version = version .. "0" + end + msg:addU16(tonumber(version)) + end + + local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) + assert(paddingBytes >= 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + + if g_game.getFeature(GameLoginPacketEncryption) then + msg:encryptRsa() + end + + if g_game.getFeature(GameOGLInformation) then + msg:addU8(1) --unknown + msg:addU8(1) --unknown + + if g_game.getClientVersion() >= 1072 then + msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer())) + else + msg:addString(g_graphics.getRenderer()) + end + msg:addString(g_graphics.getVersion()) + end + + -- add RSA encrypted auth token + if g_game.getFeature(GameAuthenticator) then + offset = msg:getMessageSize() + + -- first RSA byte must be 0 + msg:addU8(0) + msg:addString(self.authenticatorToken) + + if g_game.getFeature(GameSessionKey) then + msg:addU8(booleantonumber(self.stayLogged)) + end + + paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset) + assert(paddingBytes >= 0) + for i = 1, paddingBytes do + msg:addU8(math.random(0, 0xff)) + end + + msg:encryptRsa() + end + + if g_game.getFeature(GamePacketSizeU32) then + self:enableBigPackets() + end + + if g_game.getFeature(GameProtocolChecksum) then + self:enableChecksum() + end + + self:send(msg) + if g_game.getFeature(GameLoginPacketEncryption) then + self:enableXteaEncryption() + end + self:recv() +end + +function ProtocolLogin:onConnect() + self.gotConnection = true + self:connectCallback() + self.connectCallback = nil +end + +function ProtocolLogin:onRecv(msg) + while not msg:eof() do + local opcode = msg:getU8() + if opcode == LoginServerErrorNew then + self:parseError(msg) + elseif opcode == LoginServerError then + self:parseError(msg) + elseif opcode == LoginServerMotd then + self:parseMotd(msg) + elseif opcode == LoginServerUpdateNeeded then + signalcall(self.onLoginError, self, tr("Client needs update.")) + elseif opcode == LoginServerTokenSuccess then + local unknown = msg:getU8() + elseif opcode == LoginServerTokenError then + -- TODO: prompt for token here + local unknown = msg:getU8() + signalcall(self.onLoginError, self, tr("Invalid authentification token.")) + elseif opcode == LoginServerCharacterList then + self:parseCharacterList(msg) + elseif opcode == LoginServerExtendedCharacterList then + self:parseExtendedCharacterList(msg) + elseif opcode == LoginServerUpdate then + local signature = msg:getString() + signalcall(self.onUpdateNeeded, self, signature) + elseif opcode == LoginServerSessionKey then + self:parseSessionKey(msg) + elseif opcode == LoginServerProxyList then + local proxies = {} + local proxiesCount = msg:getU8() + for i=1, proxiesCount do + local host = msg:getString() + local port = msg:getU16() + local priority = msg:getU16() + table.insert(proxies, {host=host, port=port, priority=priority}) + end + signalcall(self.onProxyList, self, proxies) + else + self:parseOpcode(opcode, msg) + end + end + self:disconnect() +end + +function ProtocolLogin:parseError(msg) + local errorMessage = msg:getString() + signalcall(self.onLoginError, self, errorMessage) +end + +function ProtocolLogin:parseMotd(msg) + local motd = msg:getString() + signalcall(self.onMotd, self, motd) +end + +function ProtocolLogin:parseSessionKey(msg) + local sessionKey = msg:getString() + signalcall(self.onSessionKey, self, sessionKey) +end + +function ProtocolLogin:parseCharacterList(msg) + local characters = {} + + if g_game.getClientVersion() > 1010 then + local worlds = {} + + local worldsCount = msg:getU8() + for i=1, worldsCount do + local world = {} + local worldId = msg:getU8() + world.worldName = msg:getString() + world.worldIp = msg:getString() + world.worldPort = msg:getU16() + world.previewState = msg:getU8() + worlds[worldId] = world + end + + local charactersCount = msg:getU8() + for i=1, charactersCount do + local character = {} + local worldId = msg:getU8() + character.name = msg:getString() + character.worldName = worlds[worldId].worldName + character.worldIp = worlds[worldId].worldIp + character.worldPort = worlds[worldId].worldPort + character.previewState = worlds[worldId].previewState + characters[i] = character + end + + else + local charactersCount = msg:getU8() + for i=1,charactersCount do + local character = {} + character.name = msg:getString() + character.worldName = msg:getString() + character.worldIp = iptostring(msg:getU32()) + character.worldPort = msg:getU16() + + if g_game.getFeature(GamePreviewState) then + character.previewState = msg:getU8() + end + + characters[i] = character + end + end + + local account = {} + if g_game.getProtocolVersion() > 1077 then + account.status = msg:getU8() + account.subStatus = msg:getU8() + + account.premDays = msg:getU32() + if account.premDays ~= 0 and account.premDays ~= 65535 then + account.premDays = math.floor((account.premDays - os.time()) / 86400) + end + else + account.status = AccountStatus.Ok + account.premDays = msg:getU16() + account.subStatus = account.premDays > 0 and SubscriptionStatus.Premium or SubscriptionStatus.Free + end + + signalcall(self.onCharacterList, self, characters, account) +end + +function ProtocolLogin:parseExtendedCharacterList(msg) + local characters = msg:getTable() + local account = msg:getTable() + local otui = msg:getString() + signalcall(self.onCharacterList, self, characters, account, otui) +end + +function ProtocolLogin:parseOpcode(opcode, msg) + signalcall(self.onOpcode, self, opcode, msg) +end + +function ProtocolLogin:onError(msg, code) + local text = translateNetworkError(code, self:isConnecting(), msg) + signalcall(self.onLoginError, self, text) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/spells.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/spells.lua new file mode 100644 index 0000000..8757083 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/spells.lua @@ -0,0 +1,441 @@ +SpelllistSettings = { + ['Default'] = { + iconFile = '/images/game/spells/defaultspells', + iconSize = {width = 32, height = 32}, + spellListWidth = 210, + spellWindowWidth = 550, + spellOrder = {'Animate Dead', 'Annihilation', 'Avalanche', 'Berserk', 'Blood Rage', 'Brutal Strike', 'Cancel Invisibility', 'Challenge', 'Chameleon', 'Charge', 'Conjure Arrow', 'Conjure Bolt', 'Conjure Explosive Arrow', 'Conjure Piercing Bolt', 'Conjure Poisoned Arrow', 'Conjure Power Bolt', 'Conjure Sniper Arrow', 'Convince Creature', 'Creature Illusion', 'Cure Bleeding', 'Cure Burning', 'Cure Curse', 'Cure Electrification', 'Cure Poison', 'Cure Poison Rune', 'Curse', 'Death Strike', 'Desintegrate', 'Destroy Field', 'Divine Caldera', 'Divine Healing', 'Divine Missile', 'Electrify', 'Enchant Party', 'Enchant Spear', 'Enchant Staff', 'Energy Beam', 'Energy Field', 'Energy Strike', 'Energy Wall', 'Energy Wave', 'Energybomb', 'Envenom', 'Eternal Winter', 'Ethereal Spear', 'Explosion', 'Fierce Berserk', 'Find Person', 'Fire Field', 'Fire Wall', 'Fire Wave', 'Fireball', 'Firebomb', 'Flame Strike', 'Food', 'Front Sweep', 'Great Energy Beam', 'Great Fireball', 'Great Light', 'Groundshaker', 'Haste', 'Heal Friend', 'Heal Party', 'Heavy Magic Missile', 'Hells Core', 'Holy Flash', 'Holy Missile', 'Ice Strike', 'Ice Wave', 'Icicle', 'Ignite', 'Inflict Wound', 'Intense Healing', 'Intense Healing Rune', 'Intense Recovery', 'Intense Wound Cleansing', 'Invisibility', 'Levitate', 'Light', 'Light Healing', 'Light Magic Missile', 'Lightning', 'Magic Rope', 'Magic Shield', 'Magic Wall', 'Mass Healing', 'Paralyze', 'Physical Strike', 'Poison Bomb', 'Poison Field', 'Poison Wall', 'Protect Party', 'Protector', 'Rage of the Skies', 'Recovery', 'Salvation', 'Sharpshooter', 'Soulfire', 'Stalagmite', 'Stone Shower', 'Strong Energy Strike', 'Strong Ethereal Spear', 'Strong Flame Strike', 'Strong Haste', 'Strong Ice Strike', 'Strong Ice Wave', 'Strong Terra Strike', 'Sudden Death', 'Summon Creature', 'Swift Foot', 'Terra Strike', 'Terra Wave', 'Thunderstorm', 'Train Party', 'Ultimate Energy Strike', 'Ultimate Flame Strike', 'Ultimate Healing', 'Ultimate Healing Rune', 'Ultimate Ice Strike', 'Ultimate Light', 'Ultimate Terra Strike', 'Whirlwind Throw', 'Wild Growth', 'Wound Cleansing', 'Wrath of Nature'} + }--[[, + + ['Sample'] = { + iconFile = '/images/game/spells/sample', + iconSize = {width = 64, height = 64}, + spellOrder = {'Critical Strike', 'Firefly', 'Fire Breath', 'Moonglaives', 'Wind Walk'} + }]] +} + +SpellInfo = { + ['Default'] = { + ['Death Strike'] = {id = 87, words = 'exori mort', exhaustion = 2000, premium = true, type = 'Instant', icon = 'deathstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Flame Strike'] = {id = 89, words = 'exori flam', exhaustion = 2000, premium = true, type = 'Instant', icon = 'flamestrike', mana = 20, level = 14, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, + ['Strong Flame Strike'] = {id = 150, words = 'exori gran flam', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongflamestrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Ultimate Flame Strike'] = {id = 154, words = 'exori max flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateflamestrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Energy Strike'] = {id = 88, words = 'exori vis', exhaustion = 2000, premium = true, type = 'Instant', icon = 'energystrike', mana = 20, level = 12, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}}, + ['Strong Energy Strike'] = {id = 151, words = 'exori gran vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongenergystrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Ultimate Energy Strike'] = {id = 155, words = 'exori max vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateenergystrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Whirlwind Throw'] = {id = 107, words = 'exori hur', exhaustion = 6000, premium = true, type = 'Instant', icon = 'whirlwindthrow', mana = 40, level = 28, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Fire Wave'] = {id = 19, words = 'exevo flam hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'firewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Ethereal Spear'] = {id = 111, words = 'exori con', exhaustion = 2000, premium = true, type = 'Instant', icon = 'etherealspear', mana = 25, level = 23, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Strong Ethereal Spear'] = {id = 57, words = 'exori gran con', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongetherealspear', mana = 55, level = 90, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Energy Beam'] = {id = 22, words = 'exevo vis lux', exhaustion = 4000, premium = false, type = 'Instant', icon = 'energybeam', mana = 40, level = 23, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Great Energy Beam'] = {id = 23, words = 'exevo gran vis lux', exhaustion = 6000, premium = false, type = 'Instant', icon = 'greatenergybeam', mana = 110, level = 29, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Groundshaker'] = {id = 106, words = 'exori mas', exhaustion = 8000, premium = true, type = 'Instant', icon = 'groundshaker', mana = 160, level = 33, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Berserk'] = {id = 80, words = 'exori', exhaustion = 4000, premium = true, type = 'Instant', icon = 'berserk', mana = 115, level = 35, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Annihilation'] = {id = 62, words = 'exori gran ico', exhaustion = 30000, premium = true, type = 'Instant', icon = 'annihilation', mana = 300, level = 110,soul = 0, group = {[1] = 4000}, vocations = {4, 8}}, + ['Brutal Strike'] = {id = 61, words = 'exori ico', exhaustion = 6000, premium = true, type = 'Instant', icon = 'brutalstrike', mana = 30, level = 16, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Front Sweep'] = {id = 59, words = 'exori min', exhaustion = 6000, premium = true, type = 'Instant', icon = 'frontsweep', mana = 200, level = 70, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Inflict Wound'] = {id = 141, words = 'utori kor', exhaustion = 30000, premium = true, type = 'Instant', icon = 'inflictwound', mana = 30, level = 40, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Ignite'] = {id = 138, words = 'utori flam', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ignite', mana = 30, level = 26, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Lightning'] = {id = 149, words = 'exori amp vis', exhaustion = 8000, premium = true, type = 'Instant', icon = 'lightning', mana = 60, level = 55, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {1, 5}}, + ['Curse'] = {id = 139, words = 'utori mort', exhaustion = 50000, premium = true, type = 'Instant', icon = 'curse', mana = 30, level = 75, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Electrify'] = {id = 140, words = 'utori vis', exhaustion = 30000, premium = true, type = 'Instant', icon = 'electrify', mana = 30, level = 34, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Energy Wave'] = {id = 13, words = 'exevo mort hur', exhaustion = 8000, premium = false, type = 'Instant', icon = 'energywave', mana = 170, level = 38, soul = 0, group = {[1] = 2000}, vocations = {1, 5}}, + ['Rage of the Skies'] = {id = 119, words = 'exevo gran mas flam', exhaustion = 40000, premium = true, type = 'Instant', icon = 'rageoftheskies', mana = 600, level = 55, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Fierce Berserk'] = {id = 105, words = 'exori gran', exhaustion = 6000, premium = true, type = 'Instant', icon = 'fierceberserk', mana = 340, level = 90, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Hells Core'] = {id = 24, words = 'exevo gran mas vis', exhaustion = 40000, premium = true, type = 'Instant', icon = 'hellscore', mana = 1100, level = 60, soul = 0, group = {[1] = 4000}, vocations = {1, 5}}, + ['Holy Flash'] = {id = 143, words = 'utori san', exhaustion = 40000, premium = true, type = 'Instant', icon = 'holyflash', mana = 30, level = 70, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Divine Missile'] = {id = 122, words = 'exori san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'divinemissile', mana = 20, level = 40, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Divine Caldera'] = {id = 124, words = 'exevo mas san', exhaustion = 4000, premium = true, type = 'Instant', icon = 'divinecaldera', mana = 160, level = 50, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Physical Strike'] = {id = 148, words = 'exori moe ico', exhaustion = 2000, premium = true, type = 'Instant', icon = 'physicalstrike', mana = 20, level = 16, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Eternal Winter'] = {id = 118, words = 'exevo gran mas frigo', exhaustion = 40000, premium = true, type = 'Instant', icon = 'eternalwinter', mana = 1050, level = 60, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Ice Strike'] = {id = 112, words = 'exori frigo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'icestrike', mana = 20, level = 15, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, + ['Strong Ice Strike'] = {id = 152, words = 'exori gran frigo', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicestrike', mana = 60, level = 80, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, + ['Ultimate Ice Strike'] = {id = 156, words = 'exori max frigo', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateicestrike', mana = 100, level = 100,soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Ice Wave'] = {id = 121, words = 'exevo frigo hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'icewave', mana = 25, level = 18, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Strong Ice Wave'] = {id = 43, words = 'exevo gran frigo hur', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongicewave', mana = 170, level = 40, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Envenom'] = {id = 142, words = 'utori pox', exhaustion = 40000, premium = true, type = 'Instant', icon = 'envenom', mana = 30, level = 50, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Terra Strike'] = {id = 113, words = 'exori tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'terrastrike', mana = 20, level = 13, soul = 0, group = {[1] = 2000}, vocations = {1, 5, 2, 6}}, + ['Strong Terra Strike'] = {id = 153, words = 'exori gran tera', exhaustion = 8000, premium = true, type = 'Instant', icon = 'strongterrastrike', mana = 60, level = 70, soul = 0, group = {[1] = 2000, [4] = 8000}, vocations = {2, 6}}, + ['Ultimate Terra Strike'] = {id = 157, words = 'exori max tera', exhaustion = 30000, premium = true, type = 'Instant', icon = 'ultimateterrastrike', mana = 100, level = 90, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Terra Wave'] = {id = 120, words = 'exevo tera hur', exhaustion = 4000, premium = false, type = 'Instant', icon = 'terrawave', mana = 210, level = 38, soul = 0, group = {[1] = 2000}, vocations = {2, 6}}, + ['Wrath of Nature'] = {id = 56, words = 'exevo gran mas tera', exhaustion = 40000, premium = true, type = 'Instant', icon = 'wrathofnature', mana = 700, level = 55, soul = 0, group = {[1] = 4000}, vocations = {2, 6}}, + ['Light Healing'] = {id = 1, words = 'exura', exhaustion = 1000, premium = false, type = 'Instant', icon = 'lighthealing', mana = 20, level = 9, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Wound Cleansing'] = {id = 123, words = 'exura ico', exhaustion = 1000, premium = false, type = 'Instant', icon = 'woundcleansing', mana = 40, level = 10, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Intense Wound Cleansing'] = {id = 158, words = 'exura gran ico', exhaustion = 600000,premium = true, type = 'Instant', icon = 'intensewoundcleansing', mana = 200, level = 80, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Cure Bleeding'] = {id = 144, words = 'exana kor', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curebleeding', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {4, 8}}, + ['Cure Electrification'] = {id = 146, words = 'exana vis', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curseelectrification', mana = 30, level = 22, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Cure Poison'] = {id = 29, words = 'exana pox', exhaustion = 6000, premium = false, type = 'Instant', icon = 'curepoison', mana = 30, level = 10, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Cure Burning'] = {id = 145, words = 'exana flam', exhaustion = 6000, premium = true, type = 'Instant', icon = 'cureburning', mana = 30, level = 30, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Cure Curse'] = {id = 147, words = 'exana mort', exhaustion = 6000, premium = true, type = 'Instant', icon = 'curecurse', mana = 40, level = 80, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Recovery'] = {id = 159, words = 'utura', exhaustion = 60000, premium = true, type = 'Instant', icon = 'recovery', mana = 75, level = 50, soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, + ['Intense Recovery'] = {id = 160, words = 'utura gran', exhaustion = 60000, premium = true, type = 'Instant', icon = 'intenserecovery', mana = 165, level = 100,soul = 0, group = {[2] = 1000}, vocations = {4, 8, 3, 7}}, + ['Salvation'] = {id = 36, words = 'exura gran san', exhaustion = 1000, premium = true, type = 'Instant', icon = 'salvation', mana = 210, level = 60, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Intense Healing'] = {id = 2, words = 'exura gran', exhaustion = 1000, premium = false, type = 'Instant', icon = 'intensehealing', mana = 70, level = 20, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Heal Friend'] = {id = 84, words = 'exura sio', exhaustion = 1000, premium = true, type = 'Instant', icon = 'healfriend', mana = 140, level = 18, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Ultimate Healing'] = {id = 3, words = 'exura vita', exhaustion = 1000, premium = false, type = 'Instant', icon = 'ultimatehealing', mana = 160, level = 30, soul = 0, group = {[2] = 1000}, vocations = {1, 2, 5, 6}}, + ['Mass Healing'] = {id = 82, words = 'exura gran mas res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'masshealing', mana = 150, level = 36, soul = 0, group = {[2] = 1000}, vocations = {2, 6}}, + ['Divine Healing'] = {id = 125, words = 'exura san', exhaustion = 1000, premium = false, type = 'Instant', icon = 'divinehealing', mana = 160, level = 35, soul = 0, group = {[2] = 1000}, vocations = {3, 7}}, + ['Light'] = {id = 10, words = 'utevo lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'light', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Find Person'] = {id = 20, words = 'exiva', exhaustion = 2000, premium = false, type = 'Instant', icon = 'findperson', mana = 20, level = 8, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Magic Rope'] = {id = 76, words = 'exani tera', exhaustion = 2000, premium = true, type = 'Instant', icon = 'magicrope', mana = 20, level = 9, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Levitate'] = {id = 81, words = 'exani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'levitate', mana = 50, level = 12, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Great Light'] = {id = 11, words = 'utevo gran lux', exhaustion = 2000, premium = false, type = 'Instant', icon = 'greatlight', mana = 60, level = 13, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Magic Shield'] = {id = 44, words = 'utamo vita', exhaustion = 2000, premium = false, type = 'Instant', icon = 'magicshield', mana = 50, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Haste'] = {id = 6, words = 'utani hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'haste', mana = 60, level = 14, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 3, 4, 5, 6, 7, 8}}, + ['Charge'] = {id = 131, words = 'utani tempo hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'charge', mana = 100, level = 25, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, + ['Swift Foot'] = {id = 134, words = 'utamo tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'swiftfoot', mana = 400, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {3, 7}}, + ['Challenge'] = {id = 93, words = 'exeta res', exhaustion = 2000, premium = true, type = 'Instant', icon = 'challenge', mana = 30, level = 20, soul = 0, group = {[3] = 2000}, vocations = {8}}, + ['Strong Haste'] = {id = 39, words = 'utani gran hur', exhaustion = 2000, premium = true, type = 'Instant', icon = 'stronghaste', mana = 100, level = 20, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Creature Illusion'] = {id = 38, words = 'utevo res ina', exhaustion = 2000, premium = false, type = 'Instant', icon = 'creatureillusion', mana = 100, level = 23, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Ultimate Light'] = {id = 75, words = 'utevo vis lux', exhaustion = 2000, premium = true, type = 'Instant', icon = 'ultimatelight', mana = 140, level = 26, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Cancel Invisibility'] = {id = 90, words = 'exana ina', exhaustion = 2000, premium = true, type = 'Instant', icon = 'cancelinvisibility', mana = 200, level = 26, soul = 0, group = {[3] = 2000}, vocations = {3, 7}}, + ['Invisibility'] = {id = 45, words = 'utana vid', exhaustion = 2000, premium = false, type = 'Instant', icon = 'invisible', mana = 440, level = 35, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Sharpshooter'] = {id = 135, words = 'utito tempo san', exhaustion = 2000, premium = true, type = 'Instant', icon = 'sharpshooter', mana = 450, level = 60, soul = 0, group = {[2] = 10000, [3] = 10000}, vocations = {3, 7}}, + ['Protector'] = {id = 132, words = 'utamo tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protector', mana = 200, level = 55, soul = 0, group = {[1] = 10000, [3] = 2000}, vocations = {4, 8}}, + ['Blood Rage'] = {id = 133, words = 'utito tempo', exhaustion = 2000, premium = true, type = 'Instant', icon = 'bloodrage', mana = 290, level = 60, soul = 0, group = {[3] = 2000}, vocations = {4, 8}}, + ['Train Party'] = {id = 126, words = 'utito mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'trainparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {8}}, + ['Protect Party'] = {id = 127, words = 'utamo mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'protectparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {7}}, + ['Heal Party'] = {id = 128, words = 'utura mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'healparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {6}}, + ['Enchant Party'] = {id = 129, words = 'utori mas sio', exhaustion = 2000, premium = true, type = 'Instant', icon = 'enchantparty', mana = 'Var.', level = 32, soul = 0, group = {[3] = 2000}, vocations = {5}}, + ['Summon Creature'] = {id = 9, words = 'utevo res', exhaustion = 2000, premium = false, type = 'Instant', icon = 'summoncreature', mana = 'Var.', level = 25, soul = 0, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Conjure Arrow'] = {id = 51, words = 'exevo con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurearrow', mana = 100, level = 13, soul = 1, group = {[3] = 2000}, vocations = {3, 7}}, + ['Food'] = {id = 42, words = 'exevo pan', exhaustion = 2000, premium = false, type = 'Instant', icon = 'food', mana = 120, level = 14, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, + ['Conjure Poisoned Arrow'] = {id = 48, words = 'exevo con pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonedarrow', mana = 130, level = 16, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Bolt'] = {id = 79, words = 'exevo con mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'conjurebolt', mana = 140, level = 17, soul = 2, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Sniper Arrow'] = {id = 108, words = 'exevo con hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'sniperarrow', mana = 160, level = 24, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Explosive Arrow'] = {id = 49, words = 'exevo con flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosivearrow', mana = 290, level = 25, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Piercing Bolt'] = {id = 109, words = 'exevo con grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'piercingbolt', mana = 180, level = 33, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Enchant Staff'] = {id = 92, words = 'exeta vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantstaff', mana = 80, level = 41, soul = 0, group = {[3] = 2000}, vocations = {5}}, + ['Enchant Spear'] = {id = 110, words = 'exeta con', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'enchantspear', mana = 350, level = 45, soul = 3, group = {[3] = 2000}, vocations = {3, 7}}, + ['Conjure Power Bolt'] = {id = 95, words = 'exevo con vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'powerbolt', mana = 800, level = 59, soul = 3, group = {[3] = 2000}, vocations = {7}}, + ['Poison Field'] = {id = 26, words = 'adevo grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonfield', mana = 200, level = 14, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Light Magic Missile'] = {id = 7, words = 'adori min vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'lightmagicmissile', mana = 120, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fire Field'] = {id = 25, words = 'adevo grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firefield', mana = 240, level = 15, soul = 1, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fireball'] = {id = 15, words = 'adori flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'fireball', mana = 460, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Energy Field'] = {id = 27, words = 'adevo grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energyfield', mana = 320, level = 18, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Stalagmite'] = {id = 77, words = 'adori tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stalagmite', mana = 400, level = 24, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, + ['Great Fireball'] = {id = 16, words = 'adori mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'greatfireball', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Heavy Magic Missile'] = {id = 8, words = 'adori vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'heavymagicmissile', mana = 350, level = 25, soul = 2, group = {[3] = 2000}, vocations = {1, 5, 2, 6}}, + ['Poison Bomb'] = {id = 91, words = 'adevo mas pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonbomb', mana = 520, level = 25, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Firebomb'] = {id = 17, words = 'adevo mas flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firebomb', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Soulfire'] = {id = 50, words = 'adevo res flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'soulfire', mana = 600, level = 27, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Poison Wall'] = {id = 32, words = 'adevo mas grav pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'poisonwall', mana = 640, level = 29, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Explosion'] = {id = 18, words = 'adevo mas hur', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'explosion', mana = 570, level = 31, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Fire Wall'] = {id = 28, words = 'adevo mas grav flam', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'firewall', mana = 780, level = 33, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Energybomb'] = {id = 55, words = 'adevo mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energybomb', mana = 880, level = 37, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Energy Wall'] = {id = 33, words = 'adevo mas grav vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'energywall', mana = 1000, level = 41, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Sudden Death'] = {id = 21, words = 'adori gran mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'suddendeath', mana = 985, level = 45, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Cure Poison Rune'] = {id = 31, words = 'adana pox', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'antidote', mana = 200, level = 15, soul = 1, group = {[3] = 2000}, vocations = {2, 6}}, + ['Intense Healing Rune'] = {id = 4, words = 'adura gran', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'intensehealingrune', mana = 240, level = 15, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Ultimate Healing Rune'] = {id = 5, words = 'adura vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'ultimatehealingrune', mana = 400, level = 24, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Convince Creature'] = {id = 12, words = 'adeta sio', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'convincecreature', mana = 200, level = 16, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Animate Dead'] = {id = 83, words = 'adana mort', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'animatedead', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {1, 2, 5, 6}}, + ['Chameleon'] = {id = 14, words = 'adevo ina', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'chameleon', mana = 600, level = 27, soul = 2, group = {[3] = 2000}, vocations = {2, 6}}, + ['Destroy Field'] = {id = 30, words = 'adito grav', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'destroyfield', mana = 120, level = 17, soul = 2, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Desintegrate'] = {id = 78, words = 'adito tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'desintegrate', mana = 200, level = 21, soul = 3, group = {[3] = 2000}, vocations = {1, 2, 3, 5, 6, 7}}, + ['Magic Wall'] = {id = 86, words = 'adevo grav tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'magicwall', mana = 750, level = 32, soul = 5, group = {[3] = 2000}, vocations = {1, 5}}, + ['Wild Growth'] = {id = 94, words = 'adevo grav vita', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'wildgrowth', mana = 600, level = 27, soul = 5, group = {[3] = 2000}, vocations = {2, 6}}, + ['Paralyze'] = {id = 54, words = 'adana ani', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'paralyze', mana = 1400, level = 54, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Icicle'] = {id = 114, words = 'adori frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'icicle', mana = 460, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Avalanche'] = {id = 115, words = 'adori mas frigo', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'avalanche', mana = 530, level = 30, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Stone Shower'] = {id = 116, words = 'adori mas tera', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'stoneshower', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {2, 6}}, + ['Thunderstorm'] = {id = 117, words = 'adori mas vis', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'thunderstorm', mana = 430, level = 28, soul = 3, group = {[3] = 2000}, vocations = {1, 5}}, + ['Holy Missile'] = {id = 130, words = 'adori san', exhaustion = 2000, premium = false, type = 'Conjure', icon = 'holymissile', mana = 350, level = 27, soul = 3, group = {[3] = 2000}, vocations = {3, 7}} + }--[[, + + ['Sample'] = { + ['Wind Walk'] = {id = 1, words = 'windwalk', description = 'Run at enormous speed.', exhaustion = 2000, premium = false, type = 'Instant', icon = 1, mana = 50, level = 10, soul = 0, group = {[3] = 2000}, vocations = {1, 2}}, + ['Fire Breath'] = {id = 2, words = 'firebreath', description = 'A strong firewave.', exhaustion = 2000, premium = false, type = 'Instant', icon = 2, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {4, 8}}, + ['Moonglaives'] = {id = 3, words = 'moonglaives', description = 'Throw moonglaives around you.', exhaustion = 2000, premium = false, type = 'Instant', icon = 3, mana = 90, level = 55, soul = 0, group = {[1] = 2000}, vocations = {3, 7}}, + ['Critical Strike'] = {id = 4, words = 'criticalstrike', description = 'Land a critical strike.', exhaustion = 2000, premium = false, type = 'Instant', icon = 4, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {3, 4, 7, 8}}, + ['Firefly'] = {id = 5, words = 'firefly', description = 'Summon a angry firefly', exhaustion = 2000, premium = false, type = 'Instant', icon = 5, mana = 350, level = 27, soul = 0, group = {[1] = 2000}, vocations = {1, 2, 5, 6}} + }]] +} + +-- ['const_name'] = {client_id, TFS_id} +-- Conversion from TFS icon id to the id used by client (icons.png order) +SpellIcons = { + ['intenserecovery'] = {16, 160}, + ['recovery'] = {15, 159}, + ['intensewoundcleansing'] = {4, 158}, + ['ultimateterrastrike'] = {37, 157}, + ['ultimateicestrike'] = {34, 156}, + ['ultimateenergystrike'] = {31, 155}, + ['ultimateflamestrike'] = {28, 154}, + ['strongterrastrike'] = {36, 153}, + ['strongicestrike'] = {33, 152}, + ['strongenergystrike'] = {30, 151}, + ['strongflamestrike'] = {27, 150}, + ['lightning'] = {51, 149}, + ['physicalstrike'] = {17, 148}, + ['curecurse'] = {11, 147}, + ['curseelectrification'] = {14, 146}, + ['cureburning'] = {13, 145}, + ['curebleeding'] = {12, 144}, + ['holyflash'] = {53, 143}, + ['envenom'] = {58, 142}, + ['inflictwound'] = {57, 141}, + ['electrify'] = {56, 140}, + ['curse'] = {54, 139}, + ['ignite'] = {55, 138}, + -- [[ 136 / 137 Unknown ]] + ['sharpshooter'] = {121, 135}, + ['swiftfoot'] = {119, 134}, + ['bloodrage'] = {96, 133}, + ['protector'] = {122, 132}, + ['charge'] = {98, 131}, + ['holymissile'] = {76, 130}, + ['enchantparty'] = {113, 129}, + ['healparty'] = {126, 128}, + ['protectparty'] = {123, 127}, + ['trainparty'] = {120, 126}, + ['divinehealing'] = {2, 125}, + ['divinecaldera'] = {40, 124}, + ['woundcleansing'] = {3, 123}, + ['divinemissile'] = {39, 122}, + ['icewave'] = {45, 121}, + ['terrawave'] = {47, 120}, + ['rageoftheskies'] = {52, 119}, + ['eternalwinter'] = {50, 118}, + ['thunderstorm'] = {63, 117}, + ['stoneshower'] = {65, 116}, + ['avalanche'] = {92, 115}, + ['icicle'] = {75, 114}, + ['terrastrike'] = {35, 113}, + ['icestrike'] = {32, 112}, + ['etherealspear'] = {18, 111}, + ['enchantspear'] = {104, 110}, + ['piercingbolt'] = {110, 109}, + ['sniperarrow'] = {112, 108}, + ['whirlwindthrow'] = {19, 107}, + ['groundshaker'] = {25, 106}, + ['fierceberserk'] = {22, 105}, + -- [[ 96 - 104 Unknown ]] + ['powerbolt'] = {108, 95}, + ['wildgrowth'] = {61, 94}, + ['challenge'] = {97, 93}, + ['enchantstaff'] = {103, 92}, + ['poisonbomb'] = {70, 91}, + ['cancelinvisibility'] = {95, 90}, + ['flamestrike'] = {26, 89}, + ['energystrike'] = {29, 88}, + ['deathstrike'] = {38, 87}, + ['magicwall'] = {72, 86}, + ['healfriend'] = {8, 84}, + ['animatedead'] = {93, 83}, + ['masshealing'] = {9, 82}, + ['levitate'] = {125, 81}, + ['berserk'] = {21, 80}, + ['conjurebolt'] = {107, 79}, + ['desintegrate'] = {88, 78}, + ['stalagmite'] = {66, 77}, + ['magicrope'] = {105, 76}, + ['ultimatelight'] = {115, 75}, + -- [[ 71 - 64 TFS House Commands ]] + -- [[ 63 - 70 Unknown ]] + ['annihilation'] = {24, 62}, + ['brutalstrike'] = {23, 61}, + -- [[ 60 Unknown ]] + ['frontsweep'] = {20, 59}, + -- [[ 58 Unknown ]] + ['strongetherealspear'] = {59, 57}, + ['wrathofnature'] = {48, 56}, + ['energybomb'] = {86, 55}, + ['paralyze'] = {71, 54}, + -- [[ 53 Unknown ]] + -- [[ 52 TFS Retrieve Friend ]] + ['conjurearrow'] = {106, 51}, + ['soulfire'] = {67, 50}, + ['explosivearrow'] = {109, 49}, + ['poisonedarrow'] = {111, 48}, + -- [[ 46 / 47 Unknown ]] + ['invisible'] = {94, 45}, + ['magicshield'] = {124, 44}, + ['strongicewave'] = {46, 43}, + ['food'] = {99, 42}, + -- [[ 40 / 41 Unknown ]] + ['stronghaste'] = {102, 39}, + ['creatureillusion'] = {100, 38}, + -- [[ 37 TFS Move ]] + ['salvation'] = {60, 36}, + -- [[ 34 / 35 Unknown ]] + ['energywall'] = {84, 33}, + ['poisonwall'] = {68, 32}, + ['antidote'] = {10, 31}, + ['destroyfield'] = {87, 30}, + ['curepoison'] = {10, 29}, + ['firewall'] = {80, 28}, + ['energyfield'] = {85, 27}, + ['poisonfield'] = {69, 26}, + ['firefield'] = {81, 25}, + ['hellscore'] = {49, 24}, + ['greatenergybeam'] = {42, 23}, + ['energybeam'] = {41, 22}, + ['suddendeath'] = {64, 21}, + ['findperson'] = {114, 20}, + ['firewave'] = {44, 19}, + ['explosion'] = {83, 18}, + ['firebomb'] = {82, 17}, + ['greatfireball'] = {78, 16}, + ['fireball'] = {79, 15}, + ['chameleon'] = {91, 14}, + ['energywave'] = {43, 13}, + ['convincecreature'] = {90, 12}, + ['greatlight'] = {116, 11}, + ['light'] = {117, 10}, + ['summoncreature'] = {118, 9}, + ['heavymagicmissile'] = {77, 8}, + ['lightmagicmissile'] = {73, 7}, + ['haste'] = {101, 6}, + ['ultimatehealingrune'] = {62, 5}, + ['intensehealingrune'] = {74, 4}, + ['ultimatehealing'] = {1, 3}, + ['intensehealing'] = {7, 2}, + ['lighthealing'] = {6, 1} +} + +VocationNames = { + [1] = 'Sorcerer', + [2] = 'Druid', + [3] = 'Paladin', + [4] = 'Knight', + [5] = 'Master Sorcerer', + [6] = 'Elder Druid', + [7] = 'Royal Paladin', + [8] = 'Elite Knight' +} + +SpellGroups = { + [1] = 'Attack', + [2] = 'Healing', + [3] = 'Support', + [4] = 'Special' +} + +Spells = {} + +function Spells.getClientId(spellName) + local profile = Spells.getSpellProfileByName(spellName) + + local id = SpellInfo[profile][spellName].icon + if not tonumber(id) and SpellIcons[id] then + return SpellIcons[id][1] + end + return tonumber(id) +end + +function Spells.getServerId(spellName) + local profile = Spells.getSpellProfileByName(spellName) + + local id = SpellInfo[profile][spellName].icon + if not tonumber(id) and SpellIcons[id] then + return SpellIcons[id][2] + end + return tonumber(id) +end + +function Spells.getSpellByName(name) + return SpellInfo[Spells.getSpellProfileByName(name)][name] +end + +function Spells.getSpellByWords(words) + local words = words:lower():trim() + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.words == words then + return spell, profile, k + end + end + end + return nil +end + +function Spells.getSpellByIcon(iconId) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.id == iconId then + return spell, profile, k + end + end + end + return nil +end + +function Spells.getSpellIconIds() + local ids = {} + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + table.insert(ids, spell.id) + end + end + return ids +end + +function Spells.getSpellProfileById(id) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.id == id then + return profile + end + end + end + return nil +end + +function Spells.getSpellProfileByWords(words) + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if spell.words == words then + return profile + end + end + end + return nil +end + +function Spells.getSpellProfileByName(spellName) + for profile,data in pairs(SpellInfo) do + if table.findbykey(data, spellName:trim(), true) then + return profile + end + end + return nil +end + +function Spells.getSpellsByVocationId(vocId) + local spells = {} + for profile,data in pairs(SpellInfo) do + for k,spell in pairs(data) do + if table.contains(spell.vocations, vocId) then + table.insert(spells, spell) + end + end + end + return spells +end + +function Spells.filterSpellsByGroups(spells, groups) + local filtered = {} + for v,spell in pairs(spells) do + local spellGroups = Spells.getGroupIds(spell) + if table.equals(spellGroups, groups) then + table.insert(filtered, spell) + end + end + return filtered +end + +function Spells.getGroupIds(spell) + local groups = {} + for k,_ in pairs(spell.group) do + table.insert(groups, k) + end + return groups +end + +function Spells.getImageClip(id, profile) + return (((id-1)%12)*SpelllistSettings[profile].iconSize.width) .. ' ' + .. ((math.ceil(id/12)-1)*SpelllistSettings[profile].iconSize.height) .. ' ' + .. SpelllistSettings[profile].iconSize.width .. ' ' + .. SpelllistSettings[profile].iconSize.height +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/textmessages.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/textmessages.lua new file mode 100644 index 0000000..43c0a6f --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/textmessages.lua @@ -0,0 +1,30 @@ +local messageModeCallbacks = {} + +function g_game.onTextMessage(messageMode, message) + local callbacks = messageModeCallbacks[messageMode] + if not callbacks or #callbacks == 0 then + perror(string.format('Unhandled onTextMessage message mode %i: %s', messageMode, message)) + return + end + + for _, callback in pairs(callbacks) do + callback(messageMode, message) + end +end + +function registerMessageMode(messageMode, callback) + if not messageModeCallbacks[messageMode] then + messageModeCallbacks[messageMode] = {} + end + + table.insert(messageModeCallbacks[messageMode], callback) + return true +end + +function unregisterMessageMode(messageMode, callback) + if not messageModeCallbacks[messageMode] then + return false + end + + return table.removevalue(messageModeCallbacks[messageMode], callback) +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/thing.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/thing.lua new file mode 100644 index 0000000..5dfa477 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/thing.lua @@ -0,0 +1,49 @@ +ThingCategoryItem = 0 +ThingCategoryCreature = 1 +ThingCategoryEffect = 2 +ThingCategoryMissile = 3 +ThingInvalidCategory = 4 +ThingLastCategory = ThingInvalidCategory + +ThingAttrGround = 0 +ThingAttrGroundBorder = 1 +ThingAttrOnBottom = 2 +ThingAttrOnTop = 3 +ThingAttrContainer = 4 +ThingAttrStackable = 5 +ThingAttrForceUse = 6 +ThingAttrMultiUse = 7 +ThingAttrWritable = 8 +ThingAttrWritableOnce = 9 +ThingAttrFluidContainer = 10 +ThingAttrSplash = 11 +ThingAttrNotWalkable = 12 +ThingAttrNotMoveable = 13 +ThingAttrBlockProjectile = 14 +ThingAttrNotPathable = 15 +ThingAttrPickupable = 16 +ThingAttrHangable = 17 +ThingAttrHookSouth = 18 +ThingAttrHookEast = 19 +ThingAttrRotateable = 20 +ThingAttrLight = 21 +ThingAttrDontHide = 22 +ThingAttrTranslucent = 23 +ThingAttrDisplacement = 24 +ThingAttrElevation = 25 +ThingAttrLyingCorpse = 26 +ThingAttrAnimateAlways = 27 +ThingAttrMinimapColor = 28 +ThingAttrLensHelp = 29 +ThingAttrFullGround = 30 +ThingAttrLook = 31 +ThingAttrCloth = 32 +ThingAttrMarket = 33 +ThingAttrNoMoveAnimation = 253 -- >= 1010, value = 16 +ThingAttrChargeable = 254 -- deprecated +ThingLastAttr = 255 + +SpriteMaskRed = 1 +SpriteMaskGreen = 2 +SpriteMaskBlue = 3 +SpriteMaskYellow = 4 \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uicreaturebutton.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uicreaturebutton.lua new file mode 100644 index 0000000..2517833 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uicreaturebutton.lua @@ -0,0 +1,156 @@ +-- @docclass +UICreatureButton = extends(UIWidget, "UICreatureButton") + +local CreatureButtonColors = { + onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, + onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, + onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' } +} + +local LifeBarColors = {} -- Must be sorted by percentAbove +table.insert(LifeBarColors, {percentAbove = 92, color = '#00BC00' } ) +table.insert(LifeBarColors, {percentAbove = 60, color = '#50A150' } ) +table.insert(LifeBarColors, {percentAbove = 30, color = '#A1A100' } ) +table.insert(LifeBarColors, {percentAbove = 8, color = '#BF0A0A' } ) +table.insert(LifeBarColors, {percentAbove = 3, color = '#910F0F' } ) +table.insert(LifeBarColors, {percentAbove = -1, color = '#850C0C' } ) + +function UICreatureButton.create() + local button = UICreatureButton.internalCreate() + button:setFocusable(false) + button.creature = nil + button.isHovered = false + return button +end + +function UICreatureButton:setCreature(creature) + self.creature = creature +end + +function UICreatureButton:getCreature() + return self.creature +end + +function UICreatureButton:getCreatureId() + return self.creature:getId() +end + +function UICreatureButton:setup(id) + self.lifeBarWidget = self:getChildById('lifeBar') + self.creatureWidget = self:getChildById('creature') + self.labelWidget = self:getChildById('label') + self.skullWidget = self:getChildById('skull') + self.emblemWidget = self:getChildById('emblem') +end + +function UICreatureButton:update() + local color = CreatureButtonColors.onIdle + local show = false + if self.creature == g_game.getAttackingCreature() then + color = CreatureButtonColors.onTargeted + elseif self.creature == g_game.getFollowingCreature() then + color = CreatureButtonColors.onFollowed + end + color = self.isHovered and color.hovered or color.notHovered + + if self.color == color then + return + end + self.color = color + + if color ~= CreatureButtonColors.onIdle.notHovered then + self.creatureWidget:setBorderWidth(1) + self.creatureWidget:setBorderColor(color) + self.labelWidget:setColor(color) + else + self.creatureWidget:setBorderWidth(0) + self.labelWidget:setColor(color) + end +end + +function UICreatureButton:creatureSetup(creature) + if self.creature ~= creature then + self.creature = creature + self.creatureWidget:setCreature(creature) + if self.creatureName ~= creature:getName() then + self.creatureName = creature:getName() + self.labelWidget:setText(creature:getName()) + end + end + + self:updateLifeBarPercent() + self:updateSkull() + self:updateEmblem() + self:update() +end + +function UICreatureButton:updateSkull() + if not self.creature then + return + end + local skullId = self.creature:getSkull() + if skullId == self.skullId then + return + end + self.skullId = skullId + + if skullId ~= SkullNone then + self.skullWidget:setWidth(self.skullWidget:getHeight()) + local imagePath = getSkullImagePath(skullId) + self.skullWidget:setImageSource(imagePath) + self.labelWidget:setMarginLeft(5) + else + self.skullWidget:setWidth(0) + if self.creature:getEmblem() == EmblemNone then + self.labelWidget:setMarginLeft(2) + end + end +end + +function UICreatureButton:updateEmblem() + if not self.creature then + return + end + local emblemId = self.creature:getEmblem() + if self.emblemId == emblemId then + return + end + self.emblemId = emblemId + + if emblemId ~= EmblemNone then + self.emblemWidget:setWidth(self.emblemWidget:getHeight()) + local imagePath = getEmblemImagePath(emblemId) + self.emblemWidget:setImageSource(imagePath) + self.emblemWidget:setMarginLeft(5) + self.labelWidget:setMarginLeft(5) + else + self.emblemWidget:setWidth(0) + self.emblemWidget:setMarginLeft(0) + if self.creature:getSkull() == SkullNone then + self.labelWidget:setMarginLeft(2) + end + end +end + +function UICreatureButton:updateLifeBarPercent() + if not self.creature then + return + end + local percent = self.creature:getHealthPercent() + if self.percent == percent then + return + end + + self.percent = percent + self.lifeBarWidget:setPercent(percent) + + local color + for i, v in pairs(LifeBarColors) do + if percent > v.percentAbove then + color = v.color + break + end + end + + self.lifeBarWidget:setBackgroundColor(color) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiitem.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiitem.lua new file mode 100644 index 0000000..05ab126 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiitem.lua @@ -0,0 +1,137 @@ +function UIItem:onDragEnter(mousePos) + if self:isVirtual() then return false end + + local item = self:getItem() + if not item then return false end + + self:setBorderWidth(1) + self.currentDragThing = item + g_mouse.pushCursor('target') + return true +end + +function UIItem:onDragLeave(droppedWidget, mousePos) + if self:isVirtual() then return false end + self.currentDragThing = nil + g_mouse.popCursor('target') + self:setBorderWidth(0) + self.hoveredWho = nil + return true +end + +function UIItem:onDrop(widget, mousePos, forced) + if not self:canAcceptDrop(widget, mousePos) and not forced then return false end + + local item = widget.currentDragThing + if not item or not item:isItem() then return false end + + if self.selectable then + if item:isPickupable() then + self:setItem(Item.create(item:getId(), item:getCountOrSubType())) + return true + end + return false + end + + local toPos = self.position + + local itemPos = item:getPosition() + if itemPos.x == toPos.x and itemPos.y == toPos.y and itemPos.z == toPos.z then return false end + + if item:getCount() > 1 then + modules.game_interface.moveStackableItem(item, toPos) + else + g_game.move(item, toPos, 1) + end + + self:setBorderWidth(0) + return true +end + +function UIItem:onDestroy() + if self == g_ui.getDraggingWidget() and self.hoveredWho then + self.hoveredWho:setBorderWidth(0) + end + + if self.hoveredWho then + self.hoveredWho = nil + end +end + +function UIItem:onHoverChange(hovered) + UIWidget.onHoverChange(self, hovered) + + if self:isVirtual() or not self:isDraggable() then return end + + local draggingWidget = g_ui.getDraggingWidget() + if draggingWidget and self ~= draggingWidget then + local gotMap = draggingWidget:getClassName() == 'UIGameMap' + local gotItem = draggingWidget:getClassName() == 'UIItem' and not draggingWidget:isVirtual() + if hovered and (gotItem or gotMap) then + self:setBorderWidth(1) + draggingWidget.hoveredWho = self + else + self:setBorderWidth(0) + draggingWidget.hoveredWho = nil + end + end +end + +function UIItem:onMouseRelease(mousePosition, mouseButton) + if self.cancelNextRelease then + self.cancelNextRelease = false + return true + end + + if self:isVirtual() then return false end + + local item = self:getItem() + if not item or not self:containsPoint(mousePosition) then return false end + + if modules.client_options.getOption('classicControl') and not g_app.isMobile() and + ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or + (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then + g_game.look(item) + self.cancelNextRelease = true + return true + elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, nil) then + return true + end + return false +end + +function UIItem:canAcceptDrop(widget, mousePos) + if not self.selectable and (self:isVirtual() or not self:isDraggable()) then return false end + if not widget or not widget.currentDragThing then return false end + + local children = rootWidget:recursiveGetChildrenByPos(mousePos) + for i=1,#children do + local child = children[i] + if child == self then + return true + elseif not child:isPhantom() then + return false + end + end + + error('Widget ' .. self:getId() .. ' not in drop list.') + return false +end + +function UIItem:onClick(mousePos) + if not self.selectable or not self.editable then + return + end + + if modules.game_itemselector then + modules.game_itemselector.show(self) + end +end + +function UIItem:onItemChange() + local tooltip = nil + if self:getItem() and self:getItem():getTooltip():len() > 0 then + tooltip = self:getItem():getTooltip() + end + self:setTooltip(tooltip) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiminimap.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiminimap.lua new file mode 100644 index 0000000..3672280 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/ui/uiminimap.lua @@ -0,0 +1,316 @@ +function UIMinimap:onCreate() + self.autowalk = true +end + +function UIMinimap:onSetup() + self.flagWindow = nil + self.flags = {} + self.alternatives = {} + self.onAddAutomapFlag = function(pos, icon, description) self:addFlag(pos, icon, description) end + self.onRemoveAutomapFlag = function(pos, icon, description) self:removeFlag(pos, icon, description) end + connect(g_game, { + onAddAutomapFlag = self.onAddAutomapFlag, + onRemoveAutomapFlag = self.onRemoveAutomapFlag, + }) +end + +function UIMinimap:onDestroy() + for _,widget in pairs(self.alternatives) do + widget:destroy() + end + self.alternatives = {} + disconnect(g_game, { + onAddAutomapFlag = self.onAddAutomapFlag, + onRemoveAutomapFlag = self.onRemoveAutomapFlag, + }) + self:destroyFlagWindow() + self.flags = {} +end + +function UIMinimap:onVisibilityChange() + if not self:isVisible() then + self:destroyFlagWindow() + end +end + +function UIMinimap:onCameraPositionChange(cameraPos) + if self.cross then + self:setCrossPosition(self.cross.pos) + end +end + +function UIMinimap:hideFloor() + self.floorUpWidget:hide() + self.floorDownWidget:hide() +end + +function UIMinimap:hideZoom() + self.zoomInWidget:hide() + self.zoomOutWidget:hide() +end + +function UIMinimap:disableAutoWalk() + self.autowalk = false +end + +function UIMinimap:load() + local settings = g_settings.getNode('Minimap') + if settings then + if settings.flags then + for _,flag in pairs(settings.flags) do + self:addFlag(flag.position, flag.icon, flag.description) + end + end + self:setZoom(settings.zoom) + end +end + +function UIMinimap:save() + local settings = { flags={} } + for _,flag in pairs(self.flags) do + if not flag.temporary then + table.insert(settings.flags, { + position = flag.pos, + icon = flag.icon, + description = flag.description, + }) + end + end + settings.zoom = self:getZoom() + g_settings.setNode('Minimap', settings) +end + +local function onFlagMouseRelease(widget, pos, button) + if button == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Delete mark'), function() widget:destroy() end) + menu:display(pos) + return true + end + return false +end + +function UIMinimap:setCrossPosition(pos) + local cross = self.cross + if not self.cross then + cross = g_ui.createWidget('MinimapCross', self) + cross:setIcon('/images/game/minimap/cross') + self.cross = cross + end + + pos.z = self:getCameraPosition().z + cross.pos = pos + if pos then + self:centerInPosition(cross, pos) + else + cross:breakAnchors() + end +end + +function UIMinimap:addFlag(pos, icon, description, temporary) + if not pos or not icon then return end + local flag = self:getFlag(pos, icon, description) + if flag or not icon then + return + end + temporary = temporary or false + + flag = g_ui.createWidget('MinimapFlag') + self:insertChild(1, flag) + flag.pos = pos + flag.description = description + flag.icon = icon + flag.temporary = temporary + if type(tonumber(icon)) == 'number' then + flag:setIcon('/images/game/minimap/flag' .. icon) + else + flag:setIcon(resolvepath(icon, 1)) + end + flag:setTooltip(description) + flag.onMouseRelease = onFlagMouseRelease + flag.onDestroy = function() table.removevalue(self.flags, flag) end + table.insert(self.flags, flag) + self:centerInPosition(flag, pos) +end + +function UIMinimap:addAlternativeWidget(widget, pos, maxZoom) + widget.pos = pos + widget.maxZoom = maxZoom or 0 + widget.minZoom = minZoom + table.insert(self.alternatives, widget) +end + +function UIMinimap:setAlternativeWidgetsVisible(show) + local layout = self:getLayout() + layout:disableUpdates() + for _,widget in pairs(self.alternatives) do + if show then + self:insertChild(1, widget) + self:centerInPosition(widget, widget.pos) + else + self:removeChild(widget) + end + end + layout:enableUpdates() + layout:update() +end + +function UIMinimap:onZoomChange(zoom) + for _,widget in pairs(self.alternatives) do + if (not widget.minZoom or widget.minZoom >= zoom) and widget.maxZoom <= zoom then + widget:show() + else + widget:hide() + end + end +end + +function UIMinimap:getFlag(pos) + for _,flag in pairs(self.flags) do + if flag.pos.x == pos.x and flag.pos.y == pos.y and flag.pos.z == pos.z then + return flag + end + end + return nil +end + +function UIMinimap:removeFlag(pos, icon, description) + local flag = self:getFlag(pos) + if flag then + flag:destroy() + end +end + +function UIMinimap:reset() + self:setZoom(0) + if self.cross then + self:setCameraPosition(self.cross.pos) + end +end + +function UIMinimap:move(x, y) + local cameraPos = self:getCameraPosition() + local scale = self:getScale() + if scale > 1 then scale = 1 end + local dx = x/scale + local dy = y/scale + local pos = {x = cameraPos.x - dx, y = cameraPos.y - dy, z = cameraPos.z} + self:setCameraPosition(pos) +end + +function UIMinimap:onMouseWheel(mousePos, direction) + local keyboardModifiers = g_keyboard.getModifiers() + if direction == MouseWheelUp and keyboardModifiers == KeyboardNoModifier then + self:zoomIn() + elseif direction == MouseWheelDown and keyboardModifiers == KeyboardNoModifier then + self:zoomOut() + elseif direction == MouseWheelDown and keyboardModifiers == KeyboardCtrlModifier then + self:floorUp(1) + elseif direction == MouseWheelUp and keyboardModifiers == KeyboardCtrlModifier then + self:floorDown(1) + end +end + +function UIMinimap:onMousePress(pos, button) + if not self:isDragging() then + self.allowNextRelease = true + end +end + +function UIMinimap:onMouseRelease(pos, button) + if not self.allowNextRelease then return true end + self.allowNextRelease = false + + local mapPos = self:getTilePosition(pos) + if not mapPos then return end + + if button == MouseLeftButton then + local player = g_game.getLocalPlayer() + if self.autowalk then + player:autoWalk(mapPos) + end + return true + elseif button == MouseRightButton then + local menu = g_ui.createWidget('PopupMenu') + menu:setGameMenu(true) + menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end) + menu:display(pos) + return true + end + return false +end + +function UIMinimap:onDragEnter(pos) + self.dragReference = pos + self.dragCameraReference = self:getCameraPosition() + return true +end + +function UIMinimap:onDragMove(pos, moved) + local scale = self:getScale() + local dx = (self.dragReference.x - pos.x)/scale + local dy = (self.dragReference.y - pos.y)/scale + local pos = {x = self.dragCameraReference.x + dx, y = self.dragCameraReference.y + dy, z = self.dragCameraReference.z} + self:setCameraPosition(pos) + return true +end + +function UIMinimap:onDragLeave(widget, pos) + return true +end + +function UIMinimap:onStyleApply(styleName, styleNode) + for name,value in pairs(styleNode) do + if name == 'autowalk' then + self.autowalk = value + end + end +end + +function UIMinimap:createFlagWindow(pos) + if self.flagWindow then return end + if not pos then return end + + self.flagWindow = g_ui.createWidget('MinimapFlagWindow', rootWidget) + + local positionLabel = self.flagWindow:getChildById('position') + local description = self.flagWindow:getChildById('description') + local okButton = self.flagWindow:getChildById('okButton') + local cancelButton = self.flagWindow:getChildById('cancelButton') + + positionLabel:setText(string.format('%i, %i, %i', pos.x, pos.y, pos.z)) + + local flagRadioGroup = UIRadioGroup.create() + for i=0,19 do + local checkbox = self.flagWindow:getChildById('flag' .. i) + checkbox.icon = i + flagRadioGroup:addWidget(checkbox) + end + + flagRadioGroup:selectWidget(flagRadioGroup:getFirstWidget()) + + local successFunc = function() + self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText()) + self:destroyFlagWindow() + end + + local cancelFunc = function() + self:destroyFlagWindow() + end + + okButton.onClick = successFunc + cancelButton.onClick = cancelFunc + + self.flagWindow.onEnter = successFunc + self.flagWindow.onEscape = cancelFunc + + self.flagWindow.onDestroy = function() flagRadioGroup:destroy() end +end + +function UIMinimap:destroyFlagWindow() + if self.flagWindow then + self.flagWindow:destroy() + self.flagWindow = nil + end +end diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/util.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/util.lua new file mode 100644 index 0000000..e3abf07 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/gamelib/util.lua @@ -0,0 +1,11 @@ +function postostring(pos) + return pos.x .. " " .. pos.y .. " " .. pos.z +end + +function dirtostring(dir) + for k,v in pairs(Directions) do + if v == dir then + return k + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.lua b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.lua new file mode 100644 index 0000000..5e9aa9e --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.lua @@ -0,0 +1,278 @@ +Updater = { } + +Updater.maxRetries = 5 + +--[[ + +]]-- + +local updaterWindow +local loadModulesFunction +local scheduledEvent +local httpOperationId = 0 + +local function onLog(level, message, time) + if level == LogError then + Updater.error(message) + g_logger.setOnLog(nil) + end +end + +local function initAppWindow() + if g_resources.getLayout() == "mobile" then + g_window.setMinimumSize({ width = 640, height = 360 }) + else + g_window.setMinimumSize({ width = 800, height = 640 }) + end + + -- window size + local size = { width = 1024, height = 600 } + size = g_settings.getSize('window-size', size) + g_window.resize(size) + + -- window position, default is the screen center + local displaySize = g_window.getDisplaySize() + local defaultPos = { x = (displaySize.width - size.width)/2, + y = (displaySize.height - size.height)/2 } + local pos = g_settings.getPoint('window-pos', defaultPos) + pos.x = math.max(pos.x, 0) + pos.y = math.max(pos.y, 0) + g_window.move(pos) + + -- window maximized? + local maximized = g_settings.getBoolean('window-maximized', false) + if maximized then g_window.maximize() end + + g_window.setTitle(g_app.getName()) + g_window.setIcon('/images/clienticon') + + if g_app.isMobile() then + scheduleEvent(function() + g_app.scale(5.0) + end, 10) + end +end + +local function loadModules() + if loadModulesFunction then + local tmpLoadFunc = loadModulesFunction + loadModulesFunction = nil + tmpLoadFunc() + end +end + +local function downloadFiles(url, files, index, retries, doneCallback) + if not updaterWindow then return end + local entry = files[index] + if not entry then -- finished + return doneCallback() + end + local file = entry[1] + local file_checksum = entry[2] + + if retries > 0 then + updaterWindow.downloadStatus:setText(tr("Downloading (%i retry):\n%s", retries, file)) + else + updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file)) + end + updaterWindow.downloadProgress:setPercent(0) + updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files)) + + httpOperationId = HTTP.download(url .. file, file, + function (file, checksum, err) + if not err and checksum ~= file_checksum then + err = "Invalid checksum of: " .. file .. ".\nShould be " .. file_checksum .. ", is: " .. checksum + end + if err then + if retries >= Updater.maxRetries then + Updater.error("Can't download file: " .. file .. ".\nError: " .. err) + else + scheduledEvent = scheduleEvent(function() + downloadFiles(url, files, index, retries + 1, doneCallback) + end, 250) + end + return + end + downloadFiles(url, files, index + 1, 0, doneCallback) + end, + function (progress, speed) + updaterWindow.downloadProgress:setPercent(progress) + updaterWindow.downloadProgress:setText(speed .. " kbps") + end) +end + +local function updateFiles(data, keepCurrentFiles) + if not updaterWindow then return end + if type(data) ~= "table" then + return Updater.error("Invalid data from updater api (not table)") + end + if type(data["error"]) == 'string' and data["error"]:len() > 0 then + return Updater.error(data["error"]) + end + if not data["files"] or type(data["url"]) ~= 'string' or data["url"]:len() < 4 then + return Updater.error("Invalid data from updater api: " .. json.encode(data, 2)) + end + if data["keepFiles"] then + keepCurrentFiles = true + end + + local newFiles = false + local finalFiles = {} + local localFiles = g_resources.filesChecksums() + local toUpdate = {} + -- keep all files or files from data/things + for file, checksum in pairs(localFiles) do + if keepCurrentFiles or string.find(file, "data/things") then + table.insert(finalFiles, file) + end + end + -- update files + for file, checksum in pairs(data["files"]) do + table.insert(finalFiles, file) + if not localFiles[file] or localFiles[file] ~= checksum then + table.insert(toUpdate, {file, checksum}) + newFiles = true + end + end + -- update binary + local binary = nil + if type(data["binary"]) == "table" and data["binary"]["file"]:len() > 1 then + local selfChecksum = g_resources.selfChecksum() + if selfChecksum:len() > 0 and selfChecksum ~= data["binary"]["checksum"] then + binary = data["binary"]["file"] + table.insert(toUpdate, {binary, data["binary"]["checksum"]}) + end + end + + if #toUpdate == 0 then -- nothing to update + updaterWindow.mainProgress:setPercent(100) + scheduledEvent = scheduleEvent(Updater.abort, 20) + return + end + + -- update of some files require full client restart + local forceRestart = false + local reloadModules = false + local forceRestartPattern = {"init.lua", "corelib", "updater", "otmod"} + for _, file in ipairs(toUpdate) do + for __, pattern in ipairs(forceRestartPattern) do + if string.find(file[1], pattern) then + forceRestart = true + end + if not string.find(file[1], "data/things") then + reloadModules = true + end + end + end + + updaterWindow.status:setText(tr("Updating %i files", #toUpdate)) + updaterWindow.mainProgress:setPercent(0) + updaterWindow.downloadProgress:setPercent(0) + updaterWindow.downloadProgress:show() + updaterWindow.downloadStatus:show() + updaterWindow.changeUrlButton:hide() + downloadFiles(data["url"], toUpdate, 1, 0, function() + updaterWindow.status:setText(tr("Updating client (may take few seconds)")) + updaterWindow.mainProgress:setPercent(100) + updaterWindow.downloadProgress:hide() + updaterWindow.downloadStatus:hide() + scheduledEvent = scheduleEvent(function() + local restart = binary or (not loadModulesFunction and reloadModules) or forceRestart + if newFiles then + g_resources.updateData(finalFiles, not restart) + end + if binary then + g_resources.updateExecutable(binary) + end + if restart then + g_app.restart() + else + if reloadModules then + g_modules.reloadModules() + end + Updater.abort() + end + end, 100) + end) +end + +-- public functions +function Updater.init(loadModulesFunc) + g_logger.setOnLog(onLog) + loadModulesFunction = loadModulesFunc + initAppWindow() + Updater.check() +end + +function Updater.terminate() + loadModulesFunction = nil + Updater.abort() +end + +function Updater.abort() + HTTP.cancel(httpOperationId) + removeEvent(scheduledEvent) + if updaterWindow then + updaterWindow:destroy() + updaterWindow = nil + end + loadModules() +end + +function Updater.check(args) + if updaterWindow then return end + + updaterWindow = g_ui.displayUI('updater') + updaterWindow:show() + updaterWindow:focus() + updaterWindow:raise() + + local updateData = nil + local function progressUpdater(value) + removeEvent(scheduledEvent) + if value == 100 then + return Updater.error(tr("Timeout")) + end + if updateData and (value > 60 or (not g_app.isMobile() or not ALLOW_CUSTOM_SERVERS or not loadModulesFunc)) then -- gives 3s to set custom updater for mobile version + return updateFiles(updateData) + end + scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 50) + updaterWindow.mainProgress:setPercent(value) + end + progressUpdater(0) + + httpOperationId = HTTP.postJSON(Services.updater, { + version = APP_VERSION, + build = g_app.getVersion(), + os = g_app.getOs(), + platform = g_window.getPlatformType(), + args = args or {} + }, function(data, err) + if err then + return Updater.error(err) + end + updateData = data + end) +end + +function Updater.error(message) + removeEvent(scheduledEvent) + if not updaterWindow then return end + displayErrorBox(tr("Updater Error"), message).onOk = function() + Updater.abort() + end +end + +function Updater.changeUrl() + removeEvent(scheduledEvent) + modules.client_textedit.edit(Services.updater, {title="Enter updater url", width=300}, function(newUrl) + if updaterWindow and newUrl:len() > 4 then + Services.updater = newUrl + end + if updaterWindow then + updaterWindow:destroy() + updaterWindow = nil + end + Updater.check() + end) +end \ No newline at end of file diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otmod b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otmod new file mode 100644 index 0000000..85d4959 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otmod @@ -0,0 +1,12 @@ +Module + name: updater + description: Updates client + author: otclient@otclient.ovh + website: otclient.ovh + reloadable: false + scripts: [ updater ] + dependencies: [ client_locales, client_styles, client_textedit ] + @onUnload: Updater.terminate() + + load-later: + - client_background diff --git a/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otui b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otui new file mode 100644 index 0000000..ddb9f95 --- /dev/null +++ b/app/SabrehavenServer/SabrehavenOTClient/modules/updater/updater.otui @@ -0,0 +1,53 @@ +StaticMainWindow + id: updaterWindow + !text: tr('Updater') + width: 350 + + layout: + type: verticalBox + fit-children: true + + Label + id: status + !text: tr('Checking for updates') + text-align: center + + ProgressBar + id: mainProgress + height: 15 + background-color: #4444ff + margin-bottom: 5 + margin-top: 5 + + Label + id: downloadStatus + text-align: center + text-auto-resize: true + text-wrap: true + visible: false + + ProgressBar + id: downloadProgress + height: 15 + background-color: #4444ff + margin-bottom: 5 + margin-top: 5 + visible: false + + Button + id: changeUrlButton + margin-left: 50 + margin-right: 50 + margin-top: 5 + margin-bottom: 10 + !text: tr('Change updater URL') + @onClick: Updater.changeUrl() + $!mobile: + visible: false + + Button + margin-left: 90 + margin-right: 90 + !text: tr('Cancel') + @onClick: Updater.abort() + diff --git a/app/SabrehavenServer/SabrehavenOTClient/otclientv8.apk b/app/SabrehavenServer/SabrehavenOTClient/otclientv8.apk new file mode 100644 index 0000000..b95481b Binary files /dev/null and b/app/SabrehavenServer/SabrehavenOTClient/otclientv8.apk differ diff --git a/app/SabrehavenServer/cmake/FindCXX11.cmake b/app/SabrehavenServer/cmake/FindCXX11.cmake new file mode 100644 index 0000000..2b37e50 --- /dev/null +++ b/app/SabrehavenServer/cmake/FindCXX11.cmake @@ -0,0 +1,21 @@ +if(__FIND_CXX11_CMAKE__) + return() +endif() +set(__FIND_CXX11_CMAKE__ TRUE) + +include(CheckCXXCompilerFlag) +enable_language(CXX) + +check_cxx_compiler_flag("-std=c++11" COMPILER_KNOWS_CXX11) +if(COMPILER_KNOWS_CXX11) + add_compile_options(-std=c++11) + + # Tested on Mac OS X 10.8.2 with XCode 4.6 Command Line Tools + # Clang requires this to find the correct c++11 headers + check_cxx_compiler_flag("-stdlib=libc++" COMPILER_KNOWS_STDLIB) + if(APPLE AND COMPILER_KNOWS_STDLIB) + add_compile_options(-stdlib=libc++) + endif() +else() + message(FATAL_ERROR "Your C++ compiler does not support C++11.") +endif() diff --git a/app/SabrehavenServer/cmake/FindCrypto++.cmake b/app/SabrehavenServer/cmake/FindCrypto++.cmake new file mode 100644 index 0000000..5f4cb92 --- /dev/null +++ b/app/SabrehavenServer/cmake/FindCrypto++.cmake @@ -0,0 +1,13 @@ +# Locate Crypto++ library +# This module defines +# Crypto++_FOUND +# Crypto++_INCLUDE_DIR +# Crypto++_LIBRARIES + +find_path(Crypto++_INCLUDE_DIR NAMES cryptopp/cryptlib.h) +find_library(Crypto++_LIBRARIES NAMES cryptopp libcryptopp) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Crypto++ DEFAULT_MSG Crypto++_INCLUDE_DIR Crypto++_LIBRARIES) + +mark_as_advanced(Crypto++_INCLUDE_DIR Crypto++_LIBRARIES) diff --git a/app/SabrehavenServer/cmake/FindGMP.cmake b/app/SabrehavenServer/cmake/FindGMP.cmake new file mode 100644 index 0000000..8080544 --- /dev/null +++ b/app/SabrehavenServer/cmake/FindGMP.cmake @@ -0,0 +1,14 @@ +# Locate GMP library +# This module defines +# GMP_FOUND +# GMP_INCLUDE_DIR +# GMP_LIBRARIES + +find_path(GMP_INCLUDE_DIR NAMES gmp.h) +find_library(GMP_LIBRARIES NAMES gmp libgmp) +find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES GMPXX_LIBRARIES) + +mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES GMPXX_LIBRARIES) diff --git a/app/SabrehavenServer/cmake/FindLTO.cmake b/app/SabrehavenServer/cmake/FindLTO.cmake new file mode 100644 index 0000000..06906ef --- /dev/null +++ b/app/SabrehavenServer/cmake/FindLTO.cmake @@ -0,0 +1,12 @@ +if(__FIND_LTO_CMAKE__) + return() +endif() +set(__FIND_LTO_CMAKE__ TRUE) + +include(CheckCXXCompilerFlag) +enable_language(CXX) + +check_cxx_compiler_flag("-flto" COMPILER_KNOWS_LTO) +if(COMPILER_KNOWS_LTO) + add_compile_options(-flto) +endif() diff --git a/app/SabrehavenServer/cmake/FindLua.cmake b/app/SabrehavenServer/cmake/FindLua.cmake new file mode 100644 index 0000000..57a48eb --- /dev/null +++ b/app/SabrehavenServer/cmake/FindLua.cmake @@ -0,0 +1,118 @@ +# Locate Lua library +# This module defines +# LUA_EXECUTABLE, if found +# LUA_FOUND, if false, do not try to link to Lua +# LUA_LIBRARIES +# LUA_INCLUDE_DIR, where to find lua.h +# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8) +# +# Note that the expected include convention is +# #include "lua.h" +# and not +# #include +# This is because, the lua location is not standardized and may exist +# in locations other than lua/ + +#============================================================================= +# Copyright 2007-2009 Kitware, Inc. +# Modified to support Lua 5.2 by LuaDist 2012 +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) +# +# The required version of Lua can be specified using the +# standard syntax, e.g. FIND_PACKAGE(Lua 5.1) +# Otherwise the module will search for any available Lua implementation + +# Always search for non-versioned lua first (recommended) +SET(_POSSIBLE_LUA_INCLUDE include include/lua) +SET(_POSSIBLE_LUA_EXECUTABLE lua) +SET(_POSSIBLE_LUA_LIBRARY lua) + +# Determine possible naming suffixes (there is no standard for this) +IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}") +ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "52" "5.2" "-5.2" "51" "5.1" "-5.1") +ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + +# Set up possible search names and locations +FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES}) + LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}") + LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}") + LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}") +ENDFOREACH(_SUFFIX) + +# Find the lua executable +FIND_PROGRAM(LUA_EXECUTABLE + NAMES ${_POSSIBLE_LUA_EXECUTABLE} +) + +# Find the lua header +FIND_PATH(LUA_INCLUDE_DIR lua.h + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE} + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +# Find the lua library +FIND_LIBRARY(LUA_LIBRARY + NAMES ${_POSSIBLE_LUA_LIBRARY} + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES lib64 lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt +) + +IF(LUA_LIBRARY) + # include the math library for Unix + IF(UNIX AND NOT APPLE) + FIND_LIBRARY(LUA_MATH_LIBRARY m) + SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + ELSE(UNIX AND NOT APPLE) + SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") + ENDIF(UNIX AND NOT APPLE) +ENDIF(LUA_LIBRARY) + +# Determine Lua version +IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h") + FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"") + + STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}") + UNSET(lua_version_str) +ENDIF() + +INCLUDE(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua + REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR + VERSION_VAR LUA_VERSION_STRING) + +MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE) + diff --git a/app/SabrehavenServer/cmake/FindLuaJIT.cmake b/app/SabrehavenServer/cmake/FindLuaJIT.cmake new file mode 100644 index 0000000..47d986c --- /dev/null +++ b/app/SabrehavenServer/cmake/FindLuaJIT.cmake @@ -0,0 +1,63 @@ +# Locate LuaJIT library +# This module defines +# LUAJIT_FOUND, if false, do not try to link to Lua +# LUA_LIBRARIES +# LUA_INCLUDE_DIR, where to find lua.h +# LUAJIT_VERSION_STRING, the version of Lua found (since CMake 2.8.8) + +## Copied from default CMake FindLua51.cmake + +find_path(LUA_INCLUDE_DIR luajit.h + HINTS + ENV LUA_DIR + PATH_SUFFIXES include/luajit-2.0 include/luajit-2.1 include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +find_library(LUA_LIBRARY + NAMES luajit-5.1 + HINTS + ENV LUA_DIR + PATH_SUFFIXES lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw + /opt/local + /opt/csw + /opt +) + +if(LUA_LIBRARY) + # include the math library for Unix + if(UNIX AND NOT APPLE) + find_library(LUA_MATH_LIBRARY m) + set( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + else() + set( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") + endif() +endif() + +if(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h") + file(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" luajit_version_str REGEX "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT .+\"") + + string(REGEX REPLACE "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUAJIT_VERSION_STRING "${luajit_version_str}") + unset(luajit_version_str) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJIT + REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR + VERSION_VAR LUAJIT_VERSION_STRING) + +mark_as_advanced(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY) + diff --git a/app/SabrehavenServer/cmake/FindMySQL.cmake b/app/SabrehavenServer/cmake/FindMySQL.cmake new file mode 100644 index 0000000..b3f1a0c --- /dev/null +++ b/app/SabrehavenServer/cmake/FindMySQL.cmake @@ -0,0 +1,118 @@ +#-------------------------------------------------------- +# Copyright (C) 1995-2007 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# There are special exceptions to the terms and conditions of the GPL +# as it is applied to this software. View the full text of the exception +# in file LICENSE.exceptions in the top-level directory of this software +# distribution. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# The MySQL Connector/ODBC is licensed under the terms of the +# GPL, like most MySQL Connectors. There are special exceptions +# to the terms and conditions of the GPL as it is applied to +# this software, see the FLOSS License Exception available on +# mysql.com. + +########################################################################## + + +#-------------- FIND MYSQL_INCLUDE_DIR ------------------ +FIND_PATH(MYSQL_INCLUDE_DIR mysql.h + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + /usr/include/mysql + /usr/local/include/mysql + /opt/mysql/mysql/include + /opt/mysql/mysql/include/mysql + /opt/mysql/include + /opt/local/include/mysql5 + /usr/local/mysql/include + /usr/local/mysql/include/mysql + $ENV{ProgramFiles}/MySQL/*/include + $ENV{SystemDrive}/MySQL/*/include) + +#----------------- FIND MYSQL_LIB_DIR ------------------- +IF (WIN32) + # Set lib path suffixes + # dist = for mysql binary distributions + # build = for custom built tree + IF (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(libsuffixDist debug) + SET(libsuffixBuild Debug) + ELSE (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(libsuffixDist opt) + SET(libsuffixBuild Release) + ADD_DEFINITIONS(-DDBUG_OFF) + ENDIF (CMAKE_BUILD_TYPE STREQUAL Debug) + + FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient + PATHS + $ENV{MYSQL_DIR}/lib/${libsuffixDist} + $ENV{MYSQL_DIR}/libmysql + $ENV{MYSQL_DIR}/libmysql/${libsuffixBuild} + $ENV{MYSQL_DIR}/client/${libsuffixBuild} + $ENV{MYSQL_DIR}/libmysql/${libsuffixBuild} + $ENV{ProgramFiles}/MySQL/*/lib/${libsuffixDist} + $ENV{SystemDrive}/MySQL/*/lib/${libsuffixDist}) +ELSE (WIN32) + FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient mariadbclient + PATHS + $ENV{MYSQL_DIR}/libmysql/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/lib/mysql + /usr/local/lib/mysql + /usr/local/mysql/lib + /usr/local/mysql/lib/mysql + /opt/local/mysql5/lib + /opt/local/lib/mysql5/mysql + /opt/mysql/mysql/lib/mysql + /opt/mysql/lib/mysql) +ENDIF (WIN32) + +IF(MYSQL_LIB) + GET_FILENAME_COMPONENT(MYSQL_LIB_DIR ${MYSQL_LIB} PATH) +ENDIF(MYSQL_LIB) + +IF (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + SET(MYSQL_FOUND TRUE) + + INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIR}) + LINK_DIRECTORIES(${MYSQL_LIB_DIR}) + + FIND_LIBRARY(MYSQL_ZLIB zlib PATHS ${MYSQL_LIB_DIR}) + FIND_LIBRARY(MYSQL_YASSL yassl PATHS ${MYSQL_LIB_DIR}) + FIND_LIBRARY(MYSQL_TAOCRYPT taocrypt PATHS ${MYSQL_LIB_DIR}) + SET(MYSQL_CLIENT_LIBS ${MYSQL_LIB}) + IF (MYSQL_ZLIB) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} zlib) + ENDIF (MYSQL_ZLIB) + IF (MYSQL_YASSL) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} yassl) + ENDIF (MYSQL_YASSL) + IF (MYSQL_TAOCRYPT) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} taocrypt) + ENDIF (MYSQL_TAOCRYPT) + # Added needed mysqlclient dependencies on Windows + IF (WIN32) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} ws2_32) + ENDIF (WIN32) + + MESSAGE(STATUS "MySQL Include dir: ${MYSQL_INCLUDE_DIR} library dir: ${MYSQL_LIB_DIR}") + MESSAGE(STATUS "MySQL client libraries: ${MYSQL_CLIENT_LIBS}") +ELSE (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + MESSAGE(FATAL_ERROR "Cannot find MySQL. Include dir: ${MYSQL_INCLUDE_DIR} library dir: ${MYSQL_LIB_DIR}") +ENDIF (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + diff --git a/app/SabrehavenServer/cmake/FindPugiXML.cmake b/app/SabrehavenServer/cmake/FindPugiXML.cmake new file mode 100644 index 0000000..2085b4e --- /dev/null +++ b/app/SabrehavenServer/cmake/FindPugiXML.cmake @@ -0,0 +1,15 @@ +if(APPLE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(PC_PUGIXML QUIET pugixml) + set(PUGIXML_DEFINITIONS ${PC_PUGIXML_CFLAGS_OTHER}) + + find_path(PUGIXML_INCLUDE_DIR pugixml.hpp HINTS ${PC_PUGIXML_INCLUDEDIR} ${PC_PUGIXML_INCLUDE_DIRS}) + find_library(PUGIXML_LIBRARIES NAMES pugixml HINTS ${PC_PUGIXML_LIBDIR} ${PC_PUGIXML_LIBRARY_DIRS}) +else() + find_path(PUGIXML_INCLUDE_DIR NAMES pugixml.hpp) + find_library(PUGIXML_LIBRARIES NAMES pugixml) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PugiXML REQUIRED_VARS PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) +mark_as_advanced(PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) diff --git a/app/SabrehavenServer/cmake/cotire.cmake b/app/SabrehavenServer/cmake/cotire.cmake new file mode 100644 index 0000000..a4fb533 --- /dev/null +++ b/app/SabrehavenServer/cmake/cotire.cmake @@ -0,0 +1,3827 @@ +# - cotire (compile time reducer) +# +# See the cotire manual for usage hints. +# +#============================================================================= +# Copyright 2012-2015 Sascha Kratky +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +#============================================================================= + +if(__COTIRE_INCLUDED) + return() +endif() +set(__COTIRE_INCLUDED TRUE) + +# call cmake_minimum_required, but prevent modification of the CMake policy stack in include mode +# cmake_minimum_required also sets the policy version as a side effect, which we have to avoid +if (NOT CMAKE_SCRIPT_MODE_FILE) + cmake_policy(PUSH) +endif() +cmake_minimum_required(VERSION 2.8.12) +if (NOT CMAKE_SCRIPT_MODE_FILE) + cmake_policy(POP) +endif() + +set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}") +set (COTIRE_CMAKE_MODULE_VERSION "1.7.6") + +# activate select policies +if (POLICY CMP0025) + # Compiler id for Apple Clang is now AppleClang + cmake_policy(SET CMP0025 NEW) +endif() + +if (POLICY CMP0026) + # disallow use of the LOCATION target property + cmake_policy(SET CMP0026 NEW) +endif() + +if (POLICY CMP0038) + # targets may not link directly to themselves + cmake_policy(SET CMP0038 NEW) +endif() + +if (POLICY CMP0039) + # utility targets may not have link dependencies + cmake_policy(SET CMP0039 NEW) +endif() + +if (POLICY CMP0040) + # target in the TARGET signature of add_custom_command() must exist + cmake_policy(SET CMP0040 NEW) +endif() + +if (POLICY CMP0045) + # error on non-existent target in get_target_property + cmake_policy(SET CMP0045 NEW) +endif() + +if (POLICY CMP0046) + # error on non-existent dependency in add_dependencies + cmake_policy(SET CMP0046 NEW) +endif() + +if (POLICY CMP0049) + # do not expand variables in target source entries + cmake_policy(SET CMP0049 NEW) +endif() + +if (POLICY CMP0050) + # disallow add_custom_command SOURCE signatures + cmake_policy(SET CMP0050 NEW) +endif() + +if (POLICY CMP0051) + # include TARGET_OBJECTS expressions in a target's SOURCES property + cmake_policy(SET CMP0051 NEW) +endif() + +if (POLICY CMP0053) + # simplify variable reference and escape sequence evaluation + cmake_policy(SET CMP0053 NEW) +endif() + +if (POLICY CMP0054) + # only interpret if() arguments as variables or keywords when unquoted + cmake_policy(SET CMP0054 NEW) +endif() + +include(CMakeParseArguments) +include(ProcessorCount) + +function (cotire_get_configuration_types _configsVar) + set (_configs "") + if (CMAKE_CONFIGURATION_TYPES) + list (APPEND _configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + if (CMAKE_BUILD_TYPE) + list (APPEND _configs "${CMAKE_BUILD_TYPE}") + endif() + if (_configs) + list (REMOVE_DUPLICATES _configs) + set (${_configsVar} ${_configs} PARENT_SCOPE) + else() + set (${_configsVar} "None" PARENT_SCOPE) + endif() +endfunction() + +function (cotire_get_source_file_extension _sourceFile _extVar) + # get_filename_component returns extension from first occurrence of . in file name + # this function computes the extension from last occurrence of . in file name + string (FIND "${_sourceFile}" "." _index REVERSE) + if (_index GREATER -1) + math (EXPR _index "${_index} + 1") + string (SUBSTRING "${_sourceFile}" ${_index} -1 _sourceExt) + else() + set (_sourceExt "") + endif() + set (${_extVar} "${_sourceExt}" PARENT_SCOPE) +endfunction() + +macro (cotire_check_is_path_relative_to _path _isRelativeVar) + set (${_isRelativeVar} FALSE) + if (IS_ABSOLUTE "${_path}") + foreach (_dir ${ARGN}) + file (RELATIVE_PATH _relPath "${_dir}" "${_path}") + if (NOT _relPath OR (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.")) + set (${_isRelativeVar} TRUE) + break() + endif() + endforeach() + endif() +endmacro() + +function (cotire_filter_language_source_files _language _target _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) + if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) + set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}") + else() + set (_languageExtensions "") + endif() + if (CMAKE_${_language}_IGNORE_EXTENSIONS) + set (_ignoreExtensions "${CMAKE_${_language}_IGNORE_EXTENSIONS}") + else() + set (_ignoreExtensions "") + endif() + if (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS) + set (_excludeExtensions "${COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS}") + else() + set (_excludeExtensions "") + endif() + if (COTIRE_DEBUG AND _languageExtensions) + message (STATUS "${_language} source file extensions: ${_languageExtensions}") + endif() + if (COTIRE_DEBUG AND _ignoreExtensions) + message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}") + endif() + if (COTIRE_DEBUG AND _excludeExtensions) + message (STATUS "${_language} exclude extensions: ${_excludeExtensions}") + endif() + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_allSourceFiles ${ARGN}) + else() + # as of CMake 3.1 target sources may contain generator expressions + # since we cannot obtain required property information about source files added + # through generator expressions at configure time, we filter them out + string (GENEX_STRIP "${ARGN}" _allSourceFiles) + endif() + set (_filteredSourceFiles "") + set (_excludedSourceFiles "") + foreach (_sourceFile ${_allSourceFiles}) + get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY) + get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT) + get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC) + if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic) + cotire_get_source_file_extension("${_sourceFile}" _sourceExt) + if (_sourceExt) + list (FIND _ignoreExtensions "${_sourceExt}" _ignoreIndex) + if (_ignoreIndex LESS 0) + list (FIND _excludeExtensions "${_sourceExt}" _excludeIndex) + if (_excludeIndex GREATER -1) + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (FIND _languageExtensions "${_sourceExt}" _sourceIndex) + if (_sourceIndex GREATER -1) + # consider source file unless it is excluded explicitly + get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) + if (_sourceIsExcluded) + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (APPEND _filteredSourceFiles "${_sourceFile}") + endif() + else() + get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) + if ("${_sourceLanguage}" STREQUAL "${_language}") + # add to excluded sources, if file is not ignored and has correct language without having the correct extension + list (APPEND _excludedSourceFiles "${_sourceFile}") + endif() + endif() + endif() + endif() + endif() + endif() + endforeach() + # separate filtered source files from already cotired ones + # the COTIRE_TARGET property of a source file may be set while a target is being processed by cotire + set (_sourceFiles "") + set (_cotiredSourceFiles "") + foreach (_sourceFile ${_filteredSourceFiles}) + get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) + if (_sourceIsCotired) + list (APPEND _cotiredSourceFiles "${_sourceFile}") + else() + get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS) + if (_sourceCompileFlags) + # add to excluded sources, if file has custom compile flags + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (APPEND _sourceFiles "${_sourceFile}") + endif() + endif() + endforeach() + if (COTIRE_DEBUG) + if (_sourceFiles) + message (STATUS "Filtered ${_target} ${_language} sources: ${_sourceFiles}") + endif() + if (_excludedSourceFiles) + message (STATUS "Excluded ${_target} ${_language} sources: ${_excludedSourceFiles}") + endif() + if (_cotiredSourceFiles) + message (STATUS "Cotired ${_target} ${_language} sources: ${_cotiredSourceFiles}") + endif() + endif() + set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE) + set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE) + set (${_cotiredSourceFilesVar} ${_cotiredSourceFiles} PARENT_SCOPE) +endfunction() + +function (cotire_get_objects_with_property_on _filteredObjectsVar _property _type) + set (_filteredObjects "") + foreach (_object ${ARGN}) + get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (_propertyValue) + list (APPEND _filteredObjects "${_object}") + endif() + endif() + endforeach() + set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) +endfunction() + +function (cotire_get_objects_with_property_off _filteredObjectsVar _property _type) + set (_filteredObjects "") + foreach (_object ${ARGN}) + get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (NOT _propertyValue) + list (APPEND _filteredObjects "${_object}") + endif() + endif() + endforeach() + set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_file_property_values _valuesVar _property) + set (_values "") + foreach (_sourceFile ${ARGN}) + get_source_file_property(_propertyValue "${_sourceFile}" ${_property}) + if (_propertyValue) + list (APPEND _values "${_propertyValue}") + endif() + endforeach() + set (${_valuesVar} ${_values} PARENT_SCOPE) +endfunction() + +function (cotire_resolve_config_properites _configurations _propertiesVar) + set (_properties "") + foreach (_property ${ARGN}) + if ("${_property}" MATCHES "") + foreach (_config ${_configurations}) + string (TOUPPER "${_config}" _upperConfig) + string (REPLACE "" "${_upperConfig}" _configProperty "${_property}") + list (APPEND _properties ${_configProperty}) + endforeach() + else() + list (APPEND _properties ${_property}) + endif() + endforeach() + set (${_propertiesVar} ${_properties} PARENT_SCOPE) +endfunction() + +function (cotire_copy_set_properites _configurations _type _source _target) + cotire_resolve_config_properites("${_configurations}" _properties ${ARGN}) + foreach (_property ${_properties}) + get_property(_isSet ${_type} ${_source} PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} ${_source} PROPERTY ${_property}) + set_property(${_type} ${_target} PROPERTY ${_property} "${_propertyValue}") + endif() + endforeach() +endfunction() + +function (cotire_get_target_usage_requirements _target _targetRequirementsVar) + set (_targetRequirements "") + get_target_property(_librariesToProcess ${_target} LINK_LIBRARIES) + while (_librariesToProcess) + # remove from head + list (GET _librariesToProcess 0 _library) + list (REMOVE_AT _librariesToProcess 0) + if (TARGET ${_library}) + list (FIND _targetRequirements ${_library} _index) + if (_index LESS 0) + list (APPEND _targetRequirements ${_library}) + # process transitive libraries + get_target_property(_libraries ${_library} INTERFACE_LINK_LIBRARIES) + if (_libraries) + list (APPEND _librariesToProcess ${_libraries}) + list (REMOVE_DUPLICATES _librariesToProcess) + endif() + endif() + endif() + endwhile() + set (${_targetRequirementsVar} ${_targetRequirements} PARENT_SCOPE) +endfunction() + +function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + set (_flagPrefix "[/-]") + else() + set (_flagPrefix "--?") + endif() + set (_optionFlag "") + set (_matchedOptions "") + set (_unmatchedOptions "") + foreach (_compileFlag ${ARGN}) + if (_compileFlag) + if (_optionFlag AND NOT "${_compileFlag}" MATCHES "^${_flagPrefix}") + # option with separate argument + list (APPEND _matchedOptions "${_compileFlag}") + set (_optionFlag "") + elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})$") + # remember option + set (_optionFlag "${CMAKE_MATCH_2}") + elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})(.+)$") + # option with joined argument + list (APPEND _matchedOptions "${CMAKE_MATCH_3}") + set (_optionFlag "") + else() + # flush remembered option + if (_optionFlag) + list (APPEND _matchedOptions "${_optionFlag}") + set (_optionFlag "") + endif() + # add to unfiltered options + list (APPEND _unmatchedOptions "${_compileFlag}") + endif() + endif() + endforeach() + if (_optionFlag) + list (APPEND _matchedOptions "${_optionFlag}") + endif() + if (COTIRE_DEBUG AND _matchedOptions) + message (STATUS "Filter ${_flagFilter} matched: ${_matchedOptions}") + endif() + if (COTIRE_DEBUG AND _unmatchedOptions) + message (STATUS "Filter ${_flagFilter} unmatched: ${_unmatchedOptions}") + endif() + set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE) + set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compile_flags _config _language _target _flagsVar) + string (TOUPPER "${_config}" _upperConfig) + # collect options from CMake language variables + set (_compileFlags "") + if (CMAKE_${_language}_FLAGS) + set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS}") + endif() + if (CMAKE_${_language}_FLAGS_${_upperConfig}) + set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}") + endif() + if (_target) + # add target compile flags + get_target_property(_targetflags ${_target} COMPILE_FLAGS) + if (_targetflags) + set (_compileFlags "${_compileFlags} ${_targetflags}") + endif() + endif() + if (UNIX) + separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}") + elseif(WIN32) + separate_arguments(_compileFlags WINDOWS_COMMAND "${_compileFlags}") + else() + separate_arguments(_compileFlags) + endif() + # target compile options + if (_target) + get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endif() + # interface compile options from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetOptions ${_linkedTarget} INTERFACE_COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endforeach() + endif() + # handle language standard properties + if (_target) + get_target_property(_targetLanguageStandard ${_target} ${_language}_STANDARD) + get_target_property(_targetLanguageExtensions ${_target} ${_language}_EXTENSIONS) + get_target_property(_targetLanguageStandardRequired ${_target} ${_language}_STANDARD_REQUIRED) + if (_targetLanguageExtensions) + if (CMAKE_${_language}${_targetLanguageExtensions}_EXTENSION_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageExtensions}_EXTENSION_COMPILE_OPTION}") + endif() + elseif (_targetLanguageStandard) + if (_targetLanguageStandardRequired) + if (CMAKE_${_language}${_targetLanguageStandard}_STANDARD_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_STANDARD_COMPILE_OPTION}") + endif() + else() + if (CMAKE_${_language}${_targetLanguageStandard}_EXTENSION_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_EXTENSION_COMPILE_OPTION}") + endif() + endif() + endif() + endif() + # handle the POSITION_INDEPENDENT_CODE target property + if (_target) + get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) + if (_targetPIC) + get_target_property(_targetType ${_target} TYPE) + if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") + elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") + endif() + endif() + endif() + # handle visibility target properties + if (_target) + get_target_property(_targetVisibility ${_target} ${_language}_VISIBILITY_PRESET) + if (_targetVisibility AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY}${_targetVisibility}") + endif() + get_target_property(_targetVisibilityInlines ${_target} VISIBILITY_INLINES_HIDDEN) + if (_targetVisibilityInlines AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN}") + endif() + endif() + # platform specific flags + if (APPLE) + get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig}) + if (NOT _architectures) + get_target_property(_architectures ${_target} OSX_ARCHITECTURES) + endif() + if (_architectures) + foreach (_arch ${_architectures}) + list (APPEND _compileFlags "-arch" "${_arch}") + endforeach() + endif() + if (CMAKE_OSX_SYSROOT) + if (CMAKE_${_language}_SYSROOT_FLAG) + list (APPEND _compileFlags "${CMAKE_${_language}_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") + else() + list (APPEND _compileFlags "-isysroot" "${CMAKE_OSX_SYSROOT}") + endif() + endif() + if (CMAKE_OSX_DEPLOYMENT_TARGET) + if (CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG) + list (APPEND _compileFlags "${CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + list (APPEND _compileFlags "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + endif() + endif() + if (COTIRE_DEBUG AND _compileFlags) + message (STATUS "Target ${_target} compile flags: ${_compileFlags}") + endif() + set (${_flagsVar} ${_compileFlags} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_include_directories _config _language _target _includeDirsVar _systemIncludeDirsVar) + set (_includeDirs "") + set (_systemIncludeDirs "") + # default include dirs + if (CMAKE_INCLUDE_CURRENT_DIR) + list (APPEND _includeDirs "${CMAKE_CURRENT_BINARY_DIR}") + list (APPEND _includeDirs "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + # parse additional include directories from target compile flags + if (CMAKE_INCLUDE_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_dirs "") + cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) + if (_dirs) + list (APPEND _includeDirs ${_dirs}) + endif() + endif() + endif() + # parse additional system include directories from target compile flags + if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_dirs "") + cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) + if (_dirs) + list (APPEND _systemIncludeDirs ${_dirs}) + endif() + endif() + endif() + # target include directories + get_directory_property(_dirs DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" INCLUDE_DIRECTORIES) + if (_target) + get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _dirs ${_targetDirs}) + endif() + get_target_property(_targetDirs ${_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endif() + # interface include directories from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _dirs ${_targetDirs}) + endif() + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endforeach() + endif() + if (dirs) + list (REMOVE_DUPLICATES _dirs) + endif() + list (LENGTH _includeDirs _projectInsertIndex) + foreach (_dir ${_dirs}) + if (CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE) + cotire_check_is_path_relative_to("${_dir}" _isRelative "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") + if (_isRelative) + list (LENGTH _includeDirs _len) + if (_len EQUAL _projectInsertIndex) + list (APPEND _includeDirs "${_dir}") + else() + list (INSERT _includeDirs _projectInsertIndex "${_dir}") + endif() + math (EXPR _projectInsertIndex "${_projectInsertIndex} + 1") + else() + list (APPEND _includeDirs "${_dir}") + endif() + else() + list (APPEND _includeDirs "${_dir}") + endif() + endforeach() + list (REMOVE_DUPLICATES _includeDirs) + list (REMOVE_DUPLICATES _systemIncludeDirs) + if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES) + list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() + if (COTIRE_DEBUG AND _includeDirs) + message (STATUS "Target ${_target} include dirs: ${_includeDirs}") + endif() + set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE) + if (COTIRE_DEBUG AND _systemIncludeDirs) + message (STATUS "Target ${_target} system include dirs: ${_systemIncludeDirs}") + endif() + set (${_systemIncludeDirsVar} ${_systemIncludeDirs} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_export_symbol _target _exportSymbolVar) + set (_exportSymbol "") + get_target_property(_targetType ${_target} TYPE) + get_target_property(_enableExports ${_target} ENABLE_EXPORTS) + if (_targetType MATCHES "(SHARED|MODULE)_LIBRARY" OR + (_targetType STREQUAL "EXECUTABLE" AND _enableExports)) + get_target_property(_exportSymbol ${_target} DEFINE_SYMBOL) + if (NOT _exportSymbol) + set (_exportSymbol "${_target}_EXPORTS") + endif() + string (MAKE_C_IDENTIFIER "${_exportSymbol}" _exportSymbol) + endif() + set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compile_definitions _config _language _target _definitionsVar) + string (TOUPPER "${_config}" _upperConfig) + set (_configDefinitions "") + # CMAKE_INTDIR for multi-configuration build systems + if (NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".") + list (APPEND _configDefinitions "CMAKE_INTDIR=\"${_config}\"") + endif() + # target export define symbol + cotire_get_target_export_symbol("${_target}" _defineSymbol) + if (_defineSymbol) + list (APPEND _configDefinitions "${_defineSymbol}") + endif() + # directory compile definitions + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + # target compile definitions + get_target_property(_definitions ${_target} COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + get_target_property(_definitions ${_target} COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + # interface compile definitions from linked library targets + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_definitions ${_linkedTarget} INTERFACE_COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + endforeach() + # parse additional compile definitions from target compile flags + # and don't look at directory compile definitions, which we already handled + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + list (REMOVE_DUPLICATES _configDefinitions) + if (COTIRE_DEBUG AND _configDefinitions) + message (STATUS "Target ${_target} compile definitions: ${_configDefinitions}") + endif() + set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compiler_flags _config _language _target _compilerFlagsVar) + # parse target compile flags omitting compile definitions and include directives + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + set (_flagFilter "D") + if (CMAKE_INCLUDE_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_flagFilter "${_flagFilter}|${_includeFlag}") + endif() + endif() + if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_flagFilter "${_flagFilter}|${_includeFlag}") + endif() + endif() + set (_compilerFlags "") + cotire_filter_compile_flags("${_language}" "${_flagFilter}" _ignore _compilerFlags ${_targetFlags}) + if (COTIRE_DEBUG AND _compilerFlags) + message (STATUS "Target ${_target} compiler flags: ${_compilerFlags}") + endif() + set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE) +endfunction() + +function (cotire_add_sys_root_paths _pathsVar) + if (APPLE) + if (CMAKE_OSX_SYSROOT AND CMAKE_${_language}_HAS_ISYSROOT) + foreach (_path IN LISTS ${_pathsVar}) + if (IS_ABSOLUTE "${_path}") + get_filename_component(_path "${CMAKE_OSX_SYSROOT}/${_path}" ABSOLUTE) + if (EXISTS "${_path}") + list (APPEND ${_pathsVar} "${_path}") + endif() + endif() + endforeach() + endif() + endif() + set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar) + set (_extraProperties ${ARGN}) + set (_result "") + if (_extraProperties) + list (FIND _extraProperties "${_sourceFile}" _index) + if (_index GREATER -1) + math (EXPR _index "${_index} + 1") + list (LENGTH _extraProperties _len) + math (EXPR _len "${_len} - 1") + foreach (_index RANGE ${_index} ${_len}) + list (GET _extraProperties ${_index} _value) + if (_value MATCHES "${_pattern}") + list (APPEND _result "${_value}") + else() + break() + endif() + endforeach() + endif() + endif() + set (${_resultVar} ${_result} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_compile_definitions _config _language _sourceFile _definitionsVar) + set (_compileDefinitions "") + if (NOT CMAKE_SCRIPT_MODE_FILE) + string (TOUPPER "${_config}" _upperConfig) + get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + endif() + cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+(=.*)?$" _definitions ${ARGN}) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + if (COTIRE_DEBUG AND _compileDefinitions) + message (STATUS "Source ${_sourceFile} compile definitions: ${_compileDefinitions}") + endif() + set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_files_compile_definitions _config _language _definitionsVar) + set (_configDefinitions "") + foreach (_sourceFile ${ARGN}) + cotire_get_source_compile_definitions("${_config}" "${_language}" "${_sourceFile}" _sourceDefinitions) + if (_sourceDefinitions) + list (APPEND _configDefinitions "${_sourceFile}" ${_sourceDefinitions} "-") + endif() + endforeach() + set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar) + set (_sourceUndefs "") + if (NOT CMAKE_SCRIPT_MODE_FILE) + get_source_file_property(_undefs "${_sourceFile}" ${_property}) + if (_undefs) + list (APPEND _sourceUndefs ${_undefs}) + endif() + endif() + cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+$" _undefs ${ARGN}) + if (_undefs) + list (APPEND _sourceUndefs ${_undefs}) + endif() + if (COTIRE_DEBUG AND _sourceUndefs) + message (STATUS "Source ${_sourceFile} ${_property} undefs: ${_sourceUndefs}") + endif() + set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_files_undefs _property _sourceUndefsVar) + set (_sourceUndefs "") + foreach (_sourceFile ${ARGN}) + cotire_get_source_undefs("${_sourceFile}" ${_property} _undefs) + if (_undefs) + list (APPEND _sourceUndefs "${_sourceFile}" ${_undefs} "-") + endif() + endforeach() + set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) +endfunction() + +macro (cotire_set_cmd_to_prologue _cmdVar) + set (${_cmdVar} "${CMAKE_COMMAND}") + if (COTIRE_DEBUG) + list (APPEND ${_cmdVar} "--warn-uninitialized") + endif() + list (APPEND ${_cmdVar} "-DCOTIRE_BUILD_TYPE:STRING=$") + if (COTIRE_VERBOSE) + list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=ON") + elseif("${CMAKE_GENERATOR}" MATCHES "Makefiles") + list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=$(VERBOSE)") + endif() +endmacro() + +function (cotire_init_compile_cmd _cmdVar _language _compilerExe _compilerArg1) + if (NOT _compilerExe) + set (_compilerExe "${CMAKE_${_language}_COMPILER}") + endif() + if (NOT _compilerArg1) + set (_compilerArg1 ${CMAKE_${_language}_COMPILER_ARG1}) + endif() + string (STRIP "${_compilerArg1}" _compilerArg1) + set (${_cmdVar} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) +endfunction() + +macro (cotire_add_definitions_to_cmd _cmdVar _language) + foreach (_definition ${ARGN}) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + list (APPEND ${_cmdVar} "/D${_definition}") + else() + list (APPEND ${_cmdVar} "-D${_definition}") + endif() + endforeach() +endmacro() + +function (cotire_add_includes_to_cmd _cmdVar _language _includesVar _systemIncludesVar) + set (_includeDirs ${${_includesVar}} ${${_systemIncludesVar}}) + if (_includeDirs) + list (REMOVE_DUPLICATES _includeDirs) + foreach (_include ${_includeDirs}) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + file (TO_NATIVE_PATH "${_include}" _include) + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_${_language}_SEP}${_include}") + else() + set (_index -1) + if ("${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" MATCHES ".+") + list (FIND ${_systemIncludesVar} "${_include}" _index) + endif() + if (_index GREATER -1) + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}${_include}") + else() + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_${_language}_SEP}${_include}") + endif() + endif() + endforeach() + endif() + set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) +endfunction() + +function (cotire_add_frameworks_to_cmd _cmdVar _language _includesVar _systemIncludesVar) + if (APPLE) + set (_frameworkDirs "") + foreach (_include ${${_includesVar}}) + if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") + get_filename_component(_frameworkDir "${_include}" DIRECTORY) + list (APPEND _frameworkDirs "${_frameworkDir}") + endif() + endforeach() + set (_systemFrameworkDirs "") + foreach (_include ${${_systemIncludesVar}}) + if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") + get_filename_component(_frameworkDir "${_include}" DIRECTORY) + list (APPEND _systemFrameworkDirs "${_frameworkDir}") + endif() + endforeach() + if (_systemFrameworkDirs) + list (APPEND _frameworkDirs ${_systemFrameworkDirs}) + endif() + if (_frameworkDirs) + list (REMOVE_DUPLICATES _frameworkDirs) + foreach (_frameworkDir ${_frameworkDirs}) + set (_index -1) + if ("${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}" MATCHES ".+") + list (FIND _systemFrameworkDirs "${_frameworkDir}" _index) + endif() + if (_index GREATER -1) + list (APPEND ${_cmdVar} "${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") + else() + list (APPEND ${_cmdVar} "${CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") + endif() + endforeach() + endif() + endif() + set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) +endfunction() + +macro (cotire_add_compile_flags_to_cmd _cmdVar) + foreach (_flag ${ARGN}) + list (APPEND ${_cmdVar} "${_flag}") + endforeach() +endmacro() + +function (cotire_check_file_up_to_date _fileIsUpToDateVar _file) + if (EXISTS "${_file}") + set (_triggerFile "") + foreach (_dependencyFile ${ARGN}) + if (EXISTS "${_dependencyFile}") + # IS_NEWER_THAN returns TRUE if both files have the same timestamp + # thus we do the comparison in both directions to exclude ties + if ("${_dependencyFile}" IS_NEWER_THAN "${_file}" AND + NOT "${_file}" IS_NEWER_THAN "${_dependencyFile}") + set (_triggerFile "${_dependencyFile}") + break() + endif() + endif() + endforeach() + if (_triggerFile) + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} update triggered by ${_triggerFile} change.") + endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) + else() + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} is up-to-date.") + endif() + set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE) + endif() + else() + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} does not exist yet.") + endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) + endif() +endfunction() + +macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar) + set (${_relPathVar} "") + foreach (_includeDir ${_includeDirs}) + if (IS_DIRECTORY "${_includeDir}") + file (RELATIVE_PATH _relPath "${_includeDir}" "${_headerFile}") + if (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.") + string (LENGTH "${${_relPathVar}}" _closestLen) + string (LENGTH "${_relPath}" _relLen) + if (_closestLen EQUAL 0 OR _relLen LESS _closestLen) + set (${_relPathVar} "${_relPath}") + endif() + endif() + elseif ("${_includeDir}" STREQUAL "${_headerFile}") + # if path matches exactly, return short non-empty string + set (${_relPathVar} "1") + break() + endif() + endforeach() +endmacro() + +macro (cotire_check_header_file_location _headerFile _insideIncludeDirs _outsideIncludeDirs _headerIsInside) + # check header path against ignored and honored include directories + cotire_find_closest_relative_path("${_headerFile}" "${_insideIncludeDirs}" _insideRelPath) + if (_insideRelPath) + # header is inside, but could be become outside if there is a shorter outside match + cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncludeDirs}" _outsideRelPath) + if (_outsideRelPath) + string (LENGTH "${_insideRelPath}" _insideRelPathLen) + string (LENGTH "${_outsideRelPath}" _outsideRelPathLen) + if (_outsideRelPathLen LESS _insideRelPathLen) + set (${_headerIsInside} FALSE) + else() + set (${_headerIsInside} TRUE) + endif() + else() + set (${_headerIsInside} TRUE) + endif() + else() + # header is outside + set (${_headerIsInside} FALSE) + endif() +endmacro() + +macro (cotire_check_ignore_header_file_path _headerFile _headerIsIgnoredVar) + if (NOT EXISTS "${_headerFile}") + set (${_headerIsIgnoredVar} TRUE) + elseif (IS_DIRECTORY "${_headerFile}") + set (${_headerIsIgnoredVar} TRUE) + elseif ("${_headerFile}" MATCHES "\\.\\.|[_-]fixed" AND "${_headerFile}" MATCHES "\\.h$") + # heuristic: ignore C headers with embedded parent directory references or "-fixed" or "_fixed" in path + # these often stem from using GCC #include_next tricks, which may break the precompiled header compilation + # with the error message "error: no include path in which to search for header.h" + set (${_headerIsIgnoredVar} TRUE) + else() + set (${_headerIsIgnoredVar} FALSE) + endif() +endmacro() + +macro (cotire_check_ignore_header_file_ext _headerFile _ignoreExtensionsVar _headerIsIgnoredVar) + # check header file extension + cotire_get_source_file_extension("${_headerFile}" _headerFileExt) + set (${_headerIsIgnoredVar} FALSE) + if (_headerFileExt) + list (FIND ${_ignoreExtensionsVar} "${_headerFileExt}" _index) + if (_index GREATER -1) + set (${_headerIsIgnoredVar} TRUE) + endif() + endif() +endmacro() + +macro (cotire_parse_line _line _headerFileVar _headerDepthVar) + if (MSVC) + # cl.exe /showIncludes output looks different depending on the language pack used, e.g.: + # English: "Note: including file: C:\directory\file" + # German: "Hinweis: Einlesen der Datei: C:\directory\file" + # We use a very general regular expression, relying on the presence of the : characters + if (_line MATCHES "( +)([a-zA-Z]:[^:]+)$") + # Visual Studio compiler output + string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) + get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE) + else() + set (${_headerFileVar} "") + set (${_headerDepthVar} 0) + endif() + else() + if (_line MATCHES "^(\\.+) (.*)$") + # GCC like output + string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) + if (IS_ABSOLUTE "${CMAKE_MATCH_2}") + set (${_headerFileVar} "${CMAKE_MATCH_2}") + else() + get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" REALPATH) + endif() + else() + set (${_headerFileVar} "") + set (${_headerDepthVar} 0) + endif() + endif() +endmacro() + +function (cotire_parse_includes _language _scanOutput _ignoredIncludeDirs _honoredIncludeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) + if (WIN32) + # prevent CMake macro invocation errors due to backslash characters in Windows paths + string (REPLACE "\\" "/" _scanOutput "${_scanOutput}") + endif() + # canonize slashes + string (REPLACE "//" "/" _scanOutput "${_scanOutput}") + # prevent semicolon from being interpreted as a line separator + string (REPLACE ";" "\\;" _scanOutput "${_scanOutput}") + # then separate lines + string (REGEX REPLACE "\n" ";" _scanOutput "${_scanOutput}") + list (LENGTH _scanOutput _len) + # remove duplicate lines to speed up parsing + list (REMOVE_DUPLICATES _scanOutput) + list (LENGTH _scanOutput _uniqueLen) + if (COTIRE_VERBOSE OR COTIRE_DEBUG) + message (STATUS "Scanning ${_uniqueLen} unique lines of ${_len} for includes") + if (_ignoredExtensions) + message (STATUS "Ignored extensions: ${_ignoredExtensions}") + endif() + if (_ignoredIncludeDirs) + message (STATUS "Ignored paths: ${_ignoredIncludeDirs}") + endif() + if (_honoredIncludeDirs) + message (STATUS "Included paths: ${_honoredIncludeDirs}") + endif() + endif() + set (_sourceFiles ${ARGN}) + set (_selectedIncludes "") + set (_unparsedLines "") + # stack keeps track of inside/outside project status of processed header files + set (_headerIsInsideStack "") + foreach (_line IN LISTS _scanOutput) + if (_line) + cotire_parse_line("${_line}" _headerFile _headerDepth) + if (_headerFile) + cotire_check_header_file_location("${_headerFile}" "${_ignoredIncludeDirs}" "${_honoredIncludeDirs}" _headerIsInside) + if (COTIRE_DEBUG) + message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}") + endif() + # update stack + list (LENGTH _headerIsInsideStack _stackLen) + if (_headerDepth GREATER _stackLen) + math (EXPR _stackLen "${_stackLen} + 1") + foreach (_index RANGE ${_stackLen} ${_headerDepth}) + list (APPEND _headerIsInsideStack ${_headerIsInside}) + endforeach() + else() + foreach (_index RANGE ${_headerDepth} ${_stackLen}) + list (REMOVE_AT _headerIsInsideStack -1) + endforeach() + list (APPEND _headerIsInsideStack ${_headerIsInside}) + endif() + if (COTIRE_DEBUG) + message (STATUS "${_headerIsInsideStack}") + endif() + # header is a candidate if it is outside project + if (NOT _headerIsInside) + # get parent header file's inside/outside status + if (_headerDepth GREATER 1) + math (EXPR _index "${_headerDepth} - 2") + list (GET _headerIsInsideStack ${_index} _parentHeaderIsInside) + else() + set (_parentHeaderIsInside TRUE) + endif() + # select header file if parent header file is inside project + # (e.g., a project header file that includes a standard header file) + if (_parentHeaderIsInside) + cotire_check_ignore_header_file_path("${_headerFile}" _headerIsIgnored) + if (NOT _headerIsIgnored) + cotire_check_ignore_header_file_ext("${_headerFile}" _ignoredExtensions _headerIsIgnored) + if (NOT _headerIsIgnored) + list (APPEND _selectedIncludes "${_headerFile}") + else() + # fix header's inside status on stack, it is ignored by extension now + list (REMOVE_AT _headerIsInsideStack -1) + list (APPEND _headerIsInsideStack TRUE) + endif() + endif() + if (COTIRE_DEBUG) + message (STATUS "${_headerFile} ${_ignoredExtensions} ${_headerIsIgnored}") + endif() + endif() + endif() + else() + if (MSVC) + # for cl.exe do not keep unparsed lines which solely consist of a source file name + string (FIND "${_sourceFiles}" "${_line}" _index) + if (_index LESS 0) + list (APPEND _unparsedLines "${_line}") + endif() + else() + list (APPEND _unparsedLines "${_line}") + endif() + endif() + endif() + endforeach() + list (REMOVE_DUPLICATES _selectedIncludes) + set (${_selectedIncludesVar} ${_selectedIncludes} PARENT_SCOPE) + set (${_unparsedLinesVar} ${_unparsedLines} PARENT_SCOPE) +endfunction() + +function (cotire_scan_includes _includesVar) + set(_options "") + set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_VERSION LANGUAGE UNPARSED_LINES) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES + IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + if (NOT _option_LANGUAGE) + set (_option_LANGUAGE "CXX") + endif() + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") + cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) + cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd) + # only consider existing source files for scanning + set (_existingSourceFiles "") + foreach (_sourceFile ${_sourceFiles}) + if (EXISTS "${_sourceFile}") + list (APPEND _existingSourceFiles "${_sourceFile}") + endif() + endforeach() + if (NOT _existingSourceFiles) + set (${_includesVar} "" PARENT_SCOPE) + return() + endif() + list (APPEND _cmd ${_existingSourceFiles}) + if (COTIRE_VERBOSE) + message (STATUS "execute_process: ${_cmd}") + endif() + if (_option_COMPILER_ID MATCHES "MSVC") + # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared + unset (ENV{VS_UNICODE_OUTPUT}) + endif() + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result + OUTPUT_QUIET + ERROR_VARIABLE _output) + if (_result) + message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.") + endif() + cotire_parse_includes( + "${_option_LANGUAGE}" "${_output}" + "${_option_IGNORE_PATH}" "${_option_INCLUDE_PATH}" + "${_option_IGNORE_EXTENSIONS}" + _includes _unparsedLines + ${_sourceFiles}) + if (_option_INCLUDE_PRIORITY_PATH) + set (_sortedIncludes "") + foreach (_priorityPath ${_option_INCLUDE_PRIORITY_PATH}) + foreach (_include ${_includes}) + string (FIND ${_include} ${_priorityPath} _position) + if (_position GREATER -1) + list (APPEND _sortedIncludes ${_include}) + endif() + endforeach() + endforeach() + if (_sortedIncludes) + list (INSERT _includes 0 ${_sortedIncludes}) + list (REMOVE_DUPLICATES _includes) + endif() + endif() + set (${_includesVar} ${_includes} PARENT_SCOPE) + if (_option_UNPARSED_LINES) + set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE) + endif() +endfunction() + +macro (cotire_append_undefs _contentsVar) + set (_undefs ${ARGN}) + if (_undefs) + list (REMOVE_DUPLICATES _undefs) + foreach (_definition ${_undefs}) + list (APPEND ${_contentsVar} "#undef ${_definition}") + endforeach() + endif() +endmacro() + +macro (cotire_comment_str _language _commentText _commentVar) + if ("${_language}" STREQUAL "CMAKE") + set (${_commentVar} "# ${_commentText}") + else() + set (${_commentVar} "/* ${_commentText} */") + endif() +endmacro() + +function (cotire_write_file _language _file _contents _force) + get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) + cotire_comment_str("${_language}" "${_moduleName} ${COTIRE_CMAKE_MODULE_VERSION} generated file" _header1) + cotire_comment_str("${_language}" "${_file}" _header2) + set (_contents "${_header1}\n${_header2}\n${_contents}") + if (COTIRE_DEBUG) + message (STATUS "${_contents}") + endif() + if (_force OR NOT EXISTS "${_file}") + file (WRITE "${_file}" "${_contents}") + else() + file (READ "${_file}" _oldContents) + if (NOT "${_oldContents}" STREQUAL "${_contents}") + file (WRITE "${_file}" "${_contents}") + else() + if (COTIRE_DEBUG) + message (STATUS "${_file} unchanged") + endif() + endif() + endif() +endfunction() + +function (cotire_generate_unity_source _unityFile) + set(_options "") + set(_oneValueArgs LANGUAGE) + set(_multiValueArgs + DEPENDS SOURCES_COMPILE_DEFINITIONS + PRE_UNDEFS SOURCES_PRE_UNDEFS POST_UNDEFS SOURCES_POST_UNDEFS PROLOGUE EPILOGUE) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (_option_DEPENDS) + cotire_check_file_up_to_date(_unityFileIsUpToDate "${_unityFile}" ${_option_DEPENDS}) + if (_unityFileIsUpToDate) + return() + endif() + endif() + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + if (NOT _option_PRE_UNDEFS) + set (_option_PRE_UNDEFS "") + endif() + if (NOT _option_SOURCES_PRE_UNDEFS) + set (_option_SOURCES_PRE_UNDEFS "") + endif() + if (NOT _option_POST_UNDEFS) + set (_option_POST_UNDEFS "") + endif() + if (NOT _option_SOURCES_POST_UNDEFS) + set (_option_SOURCES_POST_UNDEFS "") + endif() + set (_contents "") + if (_option_PROLOGUE) + list (APPEND _contents ${_option_PROLOGUE}) + endif() + if (_option_LANGUAGE AND _sourceFiles) + if ("${_option_LANGUAGE}" STREQUAL "CXX") + list (APPEND _contents "#ifdef __cplusplus") + elseif ("${_option_LANGUAGE}" STREQUAL "C") + list (APPEND _contents "#ifndef __cplusplus") + endif() + endif() + set (_compileUndefinitions "") + foreach (_sourceFile ${_sourceFiles}) + cotire_get_source_compile_definitions( + "${_option_CONFIGURATION}" "${_option_LANGUAGE}" "${_sourceFile}" _compileDefinitions + ${_option_SOURCES_COMPILE_DEFINITIONS}) + cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_PRE_UNDEFS _sourcePreUndefs ${_option_SOURCES_PRE_UNDEFS}) + cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_POST_UNDEFS _sourcePostUndefs ${_option_SOURCES_POST_UNDEFS}) + if (_option_PRE_UNDEFS) + list (APPEND _compileUndefinitions ${_option_PRE_UNDEFS}) + endif() + if (_sourcePreUndefs) + list (APPEND _compileUndefinitions ${_sourcePreUndefs}) + endif() + if (_compileUndefinitions) + cotire_append_undefs(_contents ${_compileUndefinitions}) + set (_compileUndefinitions "") + endif() + if (_sourcePostUndefs) + list (APPEND _compileUndefinitions ${_sourcePostUndefs}) + endif() + if (_option_POST_UNDEFS) + list (APPEND _compileUndefinitions ${_option_POST_UNDEFS}) + endif() + foreach (_definition ${_compileDefinitions}) + if (_definition MATCHES "^([a-zA-Z0-9_]+)=(.+)$") + list (APPEND _contents "#define ${CMAKE_MATCH_1} ${CMAKE_MATCH_2}") + list (INSERT _compileUndefinitions 0 "${CMAKE_MATCH_1}") + else() + list (APPEND _contents "#define ${_definition}") + list (INSERT _compileUndefinitions 0 "${_definition}") + endif() + endforeach() + # use absolute path as source file location + get_filename_component(_sourceFileLocation "${_sourceFile}" ABSOLUTE) + if (WIN32) + file (TO_NATIVE_PATH "${_sourceFileLocation}" _sourceFileLocation) + endif() + list (APPEND _contents "#include \"${_sourceFileLocation}\"") + endforeach() + if (_compileUndefinitions) + cotire_append_undefs(_contents ${_compileUndefinitions}) + set (_compileUndefinitions "") + endif() + if (_option_LANGUAGE AND _sourceFiles) + list (APPEND _contents "#endif") + endif() + if (_option_EPILOGUE) + list (APPEND _contents ${_option_EPILOGUE}) + endif() + list (APPEND _contents "") + string (REPLACE ";" "\n" _contents "${_contents}") + if (COTIRE_VERBOSE) + message ("${_contents}") + endif() + cotire_write_file("${_option_LANGUAGE}" "${_unityFile}" "${_contents}" TRUE) +endfunction() + +function (cotire_generate_prefix_header _prefixFile) + set(_options "") + set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION) + set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS + INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH + IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + if (_option_DEPENDS) + cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS}) + if (_prefixFileIsUpToDate) + # create empty log file + set (_unparsedLinesFile "${_prefixFile}.log") + file (WRITE "${_unparsedLinesFile}" "") + return() + endif() + endif() + set (_prologue "") + set (_epilogue "") + if (_option_COMPILER_ID MATCHES "Clang") + set (_prologue "#pragma clang system_header") + elseif (_option_COMPILER_ID MATCHES "GNU") + set (_prologue "#pragma GCC system_header") + elseif (_option_COMPILER_ID MATCHES "MSVC") + set (_prologue "#pragma warning(push, 0)") + set (_epilogue "#pragma warning(pop)") + elseif (_option_COMPILER_ID MATCHES "Intel") + # Intel compiler requires hdrstop pragma to stop generating PCH file + set (_epilogue "#pragma hdrstop") + endif() + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + cotire_scan_includes(_selectedHeaders ${_sourceFiles} + LANGUAGE "${_option_LANGUAGE}" + COMPILER_EXECUTABLE "${_option_COMPILER_EXECUTABLE}" + COMPILER_ID "${_option_COMPILER_ID}" + COMPILER_VERSION "${_option_COMPILER_VERSION}" + COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS} + COMPILE_FLAGS ${_option_COMPILE_FLAGS} + INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES} + SYSTEM_INCLUDE_DIRECTORIES ${_option_SYSTEM_INCLUDE_DIRECTORIES} + IGNORE_PATH ${_option_IGNORE_PATH} + INCLUDE_PATH ${_option_INCLUDE_PATH} + IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS} + INCLUDE_PRIORITY_PATH ${_option_INCLUDE_PRIORITY_PATH} + UNPARSED_LINES _unparsedLines) + cotire_generate_unity_source("${_prefixFile}" + PROLOGUE ${_prologue} EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders}) + set (_unparsedLinesFile "${_prefixFile}.log") + if (_unparsedLines) + if (COTIRE_VERBOSE OR NOT _selectedHeaders) + list (LENGTH _unparsedLines _skippedLineCount) + message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesFile}") + endif() + string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}") + endif() + file (WRITE "${_unparsedLinesFile}" "${_unparsedLines}") +endfunction() + +function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + # cl.exe options used + # /nologo suppresses display of sign-on banner + # /TC treat all files named on the command line as C source files + # /TP treat all files named on the command line as C++ source files + # /EP preprocess to stdout without #line directives + # /showIncludes list include files + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /showIncludes) + else() + # return as a flag string + set (_flags "${_sourceFileType${_language}} /EP /showIncludes") + endif() + elseif (_compilerID MATCHES "GNU") + # GCC options used + # -H print the name of each header file used + # -E invoke preprocessor + # -fdirectives-only do not expand macros, requires GCC >= 4.3 + if (_flags) + # append to list + list (APPEND _flags -H -E) + if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") + list (APPEND _flags "-fdirectives-only") + endif() + else() + # return as a flag string + set (_flags "-H -E") + if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") + set (_flags "${_flags} -fdirectives-only") + endif() + endif() + elseif (_compilerID MATCHES "Clang") + # Clang options used + # -H print the name of each header file used + # -E invoke preprocessor + # -fno-color-diagnostics don't prints diagnostics in color + if (_flags) + # append to list + list (APPEND _flags -H -E -fno-color-diagnostics) + else() + # return as a flag string + set (_flags "-H -E -fno-color-diagnostics") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + # Windows Intel options used + # /nologo do not display compiler version information + # /QH display the include file order + # /EP preprocess to stdout, omitting #line directives + # /TC process all source or unrecognized file types as C source files + # /TP process all source or unrecognized file types as C++ source files + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /QH) + else() + # return as a flag string + set (_flags "${_sourceFileType${_language}} /EP /QH") + endif() + else() + # Linux / Mac OS X Intel options used + # -H print the name of each header file used + # -EP preprocess to stdout, omitting #line directives + # -Kc++ process all source or unrecognized file types as C++ source files + if (_flags) + # append to list + if ("${_language}" STREQUAL "CXX") + list (APPEND _flags -Kc++) + endif() + list (APPEND _flags -H -EP) + else() + # return as a flag string + if ("${_language}" STREQUAL "CXX") + set (_flags "-Kc++ ") + endif() + set (_flags "${_flags}-H -EP") + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersion _prefixFile _pchFile _hostFile _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) + # cl.exe options used + # /Yc creates a precompiled header file + # /Fp specifies precompiled header binary file name + # /FI forces inclusion of file + # /TC treat all files named on the command line as C source files + # /TP treat all files named on the command line as C++ source files + # /Zs syntax check only + # /Zm precompiled header memory allocation scaling factor + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" + "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + else() + # return as a flag string + set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + endif() + elseif (_compilerID MATCHES "GNU|Clang") + # GCC / Clang options used + # -x specify the source language + # -c compile but do not link + # -o place output in file + # note that we cannot use -w to suppress all warnings upon pre-compiling, because turning off a warning may + # alter compile flags as a side effect (e.g., -Wwrite-string implies -fconst-strings) + set (_xLanguage_C "c-header") + set (_xLanguage_CXX "c++-header") + if (_flags) + # append to list + list (APPEND _flags "-x" "${_xLanguage_${_language}}" "-c" "${_prefixFile}" -o "${_pchFile}") + else() + # return as a flag string + set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) + # Windows Intel options used + # /nologo do not display compiler version information + # /Yc create a precompiled header (PCH) file + # /Fp specify a path or file name for precompiled header files + # /FI tells the preprocessor to include a specified file name as the header file + # /TC process all source or unrecognized file types as C source files + # /TP process all source or unrecognized file types as C++ source files + # /Zs syntax check only + # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" + "/Yc" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "/Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "/Yc /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} /Wpch-messages") + endif() + endif() + else() + # Linux / Mac OS X Intel options used + # -pch-dir location for precompiled header files + # -pch-create name of the precompiled header (PCH) to create + # -Kc++ process all source or unrecognized file types as C++ source files + # -fsyntax-only check only for correct syntax + # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + get_filename_component(_pchDir "${_pchFile}" DIRECTORY) + get_filename_component(_pchName "${_pchFile}" NAME) + set (_xLanguage_C "c-header") + set (_xLanguage_CXX "c++-header") + if (_flags) + # append to list + if ("${_language}" STREQUAL "CXX") + list (APPEND _flags -Kc++) + endif() + list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-create" "${_pchName}" "-fsyntax-only" "${_hostFile}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "-Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-create \"${_pchName}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} -Wpch-messages") + endif() + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerVersion _prefixFile _pchFile _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + # cl.exe options used + # /Yu uses a precompiled header file during build + # /Fp specifies precompiled header binary file name + # /FI forces inclusion of file + # /Zm precompiled header memory allocation scaling factor + if (_pchFile) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + if (_flags) + # append to list + list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + else() + # return as a flag string + set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "/FI${_prefixFileNative}") + else() + # return as a flag string + set (_flags "/FI\"${_prefixFileNative}\"") + endif() + endif() + elseif (_compilerID MATCHES "GNU") + # GCC options used + # -include process include file as the first line of the primary source file + # -Winvalid-pch warns if precompiled header is found but cannot be used + # note: ccache requires the -include flag to be used in order to process precompiled header correctly + if (_flags) + # append to list + list (APPEND _flags "-Winvalid-pch" "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-Winvalid-pch -include \"${_prefixFile}\"") + endif() + elseif (_compilerID MATCHES "Clang") + # Clang options used + # -include process include file as the first line of the primary source file + # -include-pch include precompiled header file + # -Qunused-arguments don't emit warning for unused driver arguments + # note: ccache requires the -include flag to be used in order to process precompiled header correctly + if (_flags) + # append to list + list (APPEND _flags "-Qunused-arguments" "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-Qunused-arguments -include \"${_prefixFile}\"") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + # Windows Intel options used + # /Yu use a precompiled header (PCH) file + # /Fp specify a path or file name for precompiled header files + # /FI tells the preprocessor to include a specified file name as the header file + # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + if (_pchFile) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + if (_flags) + # append to list + list (APPEND _flags "/Yu" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "/Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "/Yu /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} /Wpch-messages") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "/FI${_prefixFileNative}") + else() + # return as a flag string + set (_flags "/FI\"${_prefixFileNative}\"") + endif() + endif() + else() + # Linux / Mac OS X Intel options used + # -pch-dir location for precompiled header files + # -pch-use name of the precompiled header (PCH) to use + # -include process include file as the first line of the primary source file + # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + if (_pchFile) + get_filename_component(_pchDir "${_pchFile}" DIRECTORY) + get_filename_component(_pchName "${_pchFile}" NAME) + if (_flags) + # append to list + list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-use" "${_pchName}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "-Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-use \"${_pchName}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} -Wpch-messages") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\"") + endif() + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) + set(_options "") + set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION LANGUAGE) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES SYS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_LANGUAGE) + set (_option_LANGUAGE "CXX") + endif() + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") + cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) + cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_pch_compilation_flags( + "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" "${_hostFile}" _cmd) + if (COTIRE_VERBOSE) + message (STATUS "execute_process: ${_cmd}") + endif() + if (_option_COMPILER_ID MATCHES "MSVC") + # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared + unset (ENV{VS_UNICODE_OUTPUT}) + endif() + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result) + if (_result) + message (FATAL_ERROR "cotire: error ${_result} precompiling ${_prefixFile}.") + endif() +endfunction() + +function (cotire_check_precompiled_header_support _language _target _msgVar) + set (_unsupportedCompiler + "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}") + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") + # supported since Visual Studio C++ 6.0 + # and CMake does not support an earlier version + set (${_msgVar} "" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") + # GCC PCH support requires version >= 3.4 + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + else() + set (${_msgVar} "" PARENT_SCOPE) + endif() + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") + # all Clang versions have PCH support + set (${_msgVar} "" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") + # Intel PCH support requires version >= 8.0.0 + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + else() + set (${_msgVar} "" PARENT_SCOPE) + endif() + else() + set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE) + endif() + if (CMAKE_${_language}_COMPILER MATCHES "ccache") + if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros|pch_defines") + set (${_msgVar} + "ccache requires the environment variable CCACHE_SLOPPINESS to be set to \"pch_defines,time_macros\"." + PARENT_SCOPE) + endif() + endif() + if (APPLE) + # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64) + cotire_get_configuration_types(_configs) + foreach (_config ${_configs}) + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags}) + list (LENGTH _architectures _numberOfArchitectures) + if (_numberOfArchitectures GREATER 1) + string (REPLACE ";" ", " _architectureStr "${_architectures}") + set (${_msgVar} + "Precompiled headers not supported on Darwin for multi-architecture builds (${_architectureStr})." + PARENT_SCOPE) + break() + endif() + endforeach() + endif() +endfunction() + +macro (cotire_get_intermediate_dir _cotireDir) + # ${CMAKE_CFG_INTDIR} may reference a build-time variable when using a generator which supports configuration types + get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE) +endmacro() + +macro (cotire_setup_file_extension_variables) + set (_unityFileExt_C ".c") + set (_unityFileExt_CXX ".cxx") + set (_prefixFileExt_C ".h") + set (_prefixFileExt_CXX ".hxx") + set (_prefixSourceFileExt_C ".c") + set (_prefixSourceFileExt_CXX ".cxx") +endmacro() + +function (cotire_make_single_unity_source_file_path _language _target _unityFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_unityFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + set (_unityFileName "${_unityFileBaseName}${_unityFileExt_${_language}}") + cotire_get_intermediate_dir(_baseDir) + set (_unityFile "${_baseDir}/${_unityFileName}") + set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE) +endfunction() + +function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_unityFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + cotire_get_intermediate_dir(_baseDir) + set (_startIndex 0) + set (_index 0) + set (_unityFiles "") + set (_sourceFiles ${ARGN}) + foreach (_sourceFile ${_sourceFiles}) + get_source_file_property(_startNew "${_sourceFile}" COTIRE_START_NEW_UNITY_SOURCE) + math (EXPR _unityFileCount "${_index} - ${_startIndex}") + if (_startNew OR (_maxIncludes GREATER 0 AND NOT _unityFileCount LESS _maxIncludes)) + if (_index GREATER 0) + # start new unity file segment + math (EXPR _endIndex "${_index} - 1") + set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") + list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") + endif() + set (_startIndex ${_index}) + endif() + math (EXPR _index "${_index} + 1") + endforeach() + list (LENGTH _sourceFiles _numberOfSources) + if (_startIndex EQUAL 0) + # there is only a single unity file + cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFiles) + elseif (_startIndex LESS _numberOfSources) + # end with final unity file segment + math (EXPR _endIndex "${_index} - 1") + set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") + list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") + endif() + set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE) + if (COTIRE_DEBUG AND _unityFiles) + message (STATUS "unity files: ${_unityFiles}") + endif() +endfunction() + +function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_prefixFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + string (REPLACE "${_unityFileBaseName}" "${_prefixFileBaseName}" _prefixFile "${_unityFile}") + string (REGEX REPLACE "${_unityFileExt_${_language}}$" "${_prefixFileExt_${_language}}" _prefixFile "${_prefixFile}") + set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE) +endfunction() + +function (cotire_prefix_header_to_source_file_path _language _prefixHeaderFile _prefixSourceFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _prefixSourceFileExt_${_language}) + set (${_prefixSourceFileVar} "" PARENT_SCOPE) + return() + endif() + string (REGEX REPLACE "${_prefixFileExt_${_language}}$" "${_prefixSourceFileExt_${_language}}" _prefixSourceFile "${_prefixHeaderFile}") + set (${_prefixSourceFileVar} "${_prefixSourceFile}" PARENT_SCOPE) +endfunction() + +function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar) + cotire_setup_file_extension_variables() + if (NOT _language) + set (_prefixFileBaseName "${_target}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_C}") + elseif (DEFINED _prefixFileExt_${_language}) + set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_${_language}}") + else() + set (_prefixFileBaseName "") + set (_prefixFileName "") + endif() + set (${_prefixFileBaseNameVar} "${_prefixFileBaseName}" PARENT_SCOPE) + set (${_prefixFileNameVar} "${_prefixFileName}" PARENT_SCOPE) +endfunction() + +function (cotire_make_prefix_file_path _language _target _prefixFileVar) + cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) + set (${_prefixFileVar} "" PARENT_SCOPE) + if (_prefixFileName) + if (NOT _language) + set (_language "C") + endif() + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel|MSVC") + cotire_get_intermediate_dir(_baseDir) + set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE) + endif() + endif() +endfunction() + +function (cotire_make_pch_file_path _language _target _pchFileVar) + cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) + set (${_pchFileVar} "" PARENT_SCOPE) + if (_prefixFileBaseName AND _prefixFileName) + cotire_check_precompiled_header_support("${_language}" "${_target}" _msg) + if (NOT _msg) + if (XCODE) + # For Xcode, we completely hand off the compilation of the prefix header to the IDE + return() + endif() + cotire_get_intermediate_dir(_baseDir) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") + # MSVC uses the extension .pch added to the prefix header base name + set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") + # Clang looks for a precompiled header corresponding to the prefix header with the extension .pch appended + set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.pch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") + # GCC looks for a precompiled header corresponding to the prefix header with the extension .gch appended + set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.gch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") + # Intel uses the extension .pchi added to the prefix header base name + set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pchi" PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + +function (cotire_select_unity_source_files _unityFile _sourcesVar) + set (_sourceFiles ${ARGN}) + if (_sourceFiles AND "${_unityFile}" MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}_([0-9]+)_([0-9]+)") + set (_startIndex ${CMAKE_MATCH_1}) + set (_endIndex ${CMAKE_MATCH_2}) + list (LENGTH _sourceFiles _numberOfSources) + if (NOT _startIndex LESS _numberOfSources) + math (EXPR _startIndex "${_numberOfSources} - 1") + endif() + if (NOT _endIndex LESS _numberOfSources) + math (EXPR _endIndex "${_numberOfSources} - 1") + endif() + set (_files "") + foreach (_index RANGE ${_startIndex} ${_endIndex}) + list (GET _sourceFiles ${_index} _file) + list (APPEND _files "${_file}") + endforeach() + else() + set (_files ${_sourceFiles}) + endif() + set (${_sourcesVar} ${_files} PARENT_SCOPE) +endfunction() + +function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar) + set (_dependencySources "") + # depend on target's generated source files + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) + if (_generatedSources) + # but omit all generated source files that have the COTIRE_EXCLUDED property set to true + cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources}) + if (_excludedGeneratedSources) + list (REMOVE_ITEM _generatedSources ${_excludedGeneratedSources}) + endif() + # and omit all generated source files that have the COTIRE_DEPENDENCY property set to false explicitly + cotire_get_objects_with_property_off(_excludedNonDependencySources COTIRE_DEPENDENCY SOURCE ${_generatedSources}) + if (_excludedNonDependencySources) + list (REMOVE_ITEM _generatedSources ${_excludedNonDependencySources}) + endif() + if (_generatedSources) + list (APPEND _dependencySources ${_generatedSources}) + endif() + endif() + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_language} ${_target} unity source dependencies: ${_dependencySources}") + endif() + set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) +endfunction() + +function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar) + set (_dependencySources "") + # depend on target source files marked with custom COTIRE_DEPENDENCY property + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${_targetSourceFiles}) + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_language} ${_target} prefix header dependencies: ${_dependencySources}") + endif() + set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) +endfunction() + +function (cotire_generate_target_script _language _configurations _target _targetScriptVar _targetConfigScriptVar) + set (_targetSources ${ARGN}) + cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${_targetSources}) + cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${_targetSources}) + # set up variables to be configured + set (COTIRE_TARGET_LANGUAGE "${_language}") + get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH) + cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH) + get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH) + cotire_add_sys_root_paths(COTIRE_TARGET_INCLUDE_PATH) + get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS) + get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS) + get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) + get_target_property(COTIRE_TARGET_INCLUDE_PRIORITY_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${_targetSources}) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${_targetSources}) + set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}") + foreach (_config ${_configurations}) + string (TOUPPER "${_config}" _upperConfig) + cotire_get_target_include_directories( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig} COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}) + cotire_get_target_compile_definitions( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) + cotire_get_target_compiler_flags( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) + cotire_get_source_files_compile_definitions( + "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${_targetSources}) + endforeach() + # set up COTIRE_TARGET_SOURCES + set (COTIRE_TARGET_SOURCES "") + foreach (_sourceFile ${_targetSources}) + get_source_file_property(_generated "${_sourceFile}" GENERATED) + if (_generated) + # use absolute paths for generated files only, retrieving the LOCATION property is an expensive operation + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND COTIRE_TARGET_SOURCES "${_sourceLocation}") + else() + list (APPEND COTIRE_TARGET_SOURCES "${_sourceFile}") + endif() + endforeach() + # copy variable definitions to cotire target script + get_cmake_property(_vars VARIABLES) + string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}") + # omit COTIRE_*_INIT variables + string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+_INIT" _initVars "${_matchVars}") + if (_initVars) + list (REMOVE_ITEM _matchVars ${_initVars}) + endif() + # omit COTIRE_VERBOSE which is passed as a CMake define on command line + list (REMOVE_ITEM _matchVars COTIRE_VERBOSE) + set (_contents "") + set (_contentsHasGeneratorExpressions FALSE) + foreach (_var IN LISTS _matchVars ITEMS + XCODE MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES + CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER_VERSION + CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 + CMAKE_INCLUDE_FLAG_${_language} CMAKE_INCLUDE_FLAG_${_language}_SEP + CMAKE_INCLUDE_SYSTEM_FLAG_${_language} + CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG + CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG + CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) + if (DEFINED ${_var}) + string (REPLACE "\"" "\\\"" _value "${${_var}}") + set (_contents "${_contents}set (${_var} \"${_value}\")\n") + if (NOT _contentsHasGeneratorExpressions) + if ("${_value}" MATCHES "\\$<.*>") + set (_contentsHasGeneratorExpressions TRUE) + endif() + endif() + endif() + endforeach() + # generate target script file + get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) + set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") + cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE) + if (_contentsHasGeneratorExpressions) + # use file(GENERATE ...) to expand generator expressions in the target script at CMake generate-time + set (_configNameOrNoneGeneratorExpression "$<$:None>$<$>:$>") + set (_targetCotireConfigScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_configNameOrNoneGeneratorExpression}_${_moduleName}") + file (GENERATE OUTPUT "${_targetCotireConfigScript}" INPUT "${_targetCotireScript}") + else() + set (_targetCotireConfigScript "${_targetCotireScript}") + endif() + set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE) + set (${_targetConfigScriptVar} "${_targetCotireConfigScript}" PARENT_SCOPE) +endfunction() + +function (cotire_setup_pch_file_compilation _language _target _targetScript _prefixFile _pchFile _hostFile) + set (_sourceFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # for Visual Studio and Intel, we attach the precompiled header compilation to the host file + # the remaining files include the precompiled header, see cotire_setup_pch_file_inclusion + if (_sourceFiles) + set (_flags "") + cotire_add_pch_compilation_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags) + set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}") + # make object file generated from host file depend on prefix header + set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") + # mark host file as cotired to prevent it from being used in another cotired target + set_property (SOURCE ${_hostFile} PROPERTY COTIRE_TARGET "${_target}") + endif() + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generator, we add a custom command to precompile the prefix header + if (_targetScript) + cotire_set_cmd_to_prologue(_cmds) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}") + file (RELATIVE_PATH _pchFileRelPath "${CMAKE_BINARY_DIR}" "${_pchFile}") + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} IMPLICIT_DEPENDS ${_language} ${_prefixFile}") + endif() + set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE) + add_custom_command( + OUTPUT "${_pchFile}" + COMMAND ${_cmds} + DEPENDS "${_prefixFile}" + IMPLICIT_DEPENDS ${_language} "${_prefixFile}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Building ${_language} precompiled header ${_pchFileRelPath}" + VERBATIM) + endif() + endif() +endfunction() + +function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile _hostFile) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # for Visual Studio and Intel, we include the precompiled header in all but the host file + # the host file does the precompiled header compilation, see cotire_setup_pch_file_compilation + set (_sourceFiles ${ARGN}) + list (LENGTH _sourceFiles _numberOfSourceFiles) + if (_numberOfSourceFiles GREATER 0) + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + set (_flags "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # make object files generated from source files depend on precompiled header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") + endif() + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + set (_sourceFiles ${_hostFile} ${ARGN}) + if (NOT _wholeTarget) + # for makefile based generator, we force the inclusion of the prefix header for a subset + # of the source files, if this is a multi-language target or has excluded files + set (_flags "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + endif() + # make object files generated from source files depend on precompiled header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") + endif() +endfunction() + +function (cotire_setup_prefix_file_inclusion _language _target _prefixFile) + set (_sourceFiles ${ARGN}) + # force the inclusion of the prefix header for the given source files + set (_flags "") + set (_pchFile "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + # make object files generated from source files depend on prefix header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") +endfunction() + +function (cotire_get_first_set_property_value _propertyValueVar _type _object) + set (_properties ${ARGN}) + foreach (_property ${_properties}) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (_propertyValue) + set (${_propertyValueVar} ${_propertyValue} PARENT_SCOPE) + return() + endif() + endforeach() + set (${_propertyValueVar} "" PARENT_SCOPE) +endfunction() + +function (cotire_setup_combine_command _language _targetScript _joinedFile _cmdsVar) + set (_files ${ARGN}) + set (_filesPaths "") + foreach (_file ${_files}) + get_filename_component(_filePath "${_file}" ABSOLUTE) + list (APPEND _filesPaths "${_filePath}") + endforeach() + cotire_set_cmd_to_prologue(_prefixCmd) + list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine") + if (_targetScript) + list (APPEND _prefixCmd "${_targetScript}") + endif() + list (APPEND _prefixCmd "${_joinedFile}" ${_filesPaths}) + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_joinedFile} COMMAND ${_prefixCmd} DEPENDS ${_files}") + endif() + set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE) + file (RELATIVE_PATH _joinedFileRelPath "${CMAKE_BINARY_DIR}" "${_joinedFile}") + get_filename_component(_joinedFileBaseName "${_joinedFile}" NAME_WE) + get_filename_component(_joinedFileExt "${_joinedFile}" EXT) + if (_language AND _joinedFileBaseName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") + set (_comment "Generating ${_language} unity source ${_joinedFileRelPath}") + elseif (_language AND _joinedFileBaseName MATCHES "${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}$") + if (_joinedFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_joinedFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_joinedFileRelPath}") + endif() + else() + set (_comment "Generating ${_joinedFileRelPath}") + endif() + add_custom_command( + OUTPUT "${_joinedFile}" + COMMAND ${_prefixCmd} + DEPENDS ${_files} + COMMENT "${_comment}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_target_pch_usage _languages _target _wholeTarget) + if (XCODE) + # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers + set (_prefixFiles "") + foreach (_language ${_languages}) + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_prefixFile) + list (APPEND _prefixFiles "${_prefixFile}") + endif() + endforeach() + set (_cmds ${ARGN}) + list (LENGTH _prefixFiles _numberOfPrefixFiles) + if (_numberOfPrefixFiles GREATER 1) + # we also generate a generic, single prefix header which includes all language specific prefix headers + set (_language "") + set (_targetScript "") + cotire_make_prefix_file_path("${_language}" ${_target} _prefixHeader) + cotire_setup_combine_command("${_language}" "${_targetScript}" "${_prefixHeader}" _cmds ${_prefixFiles}) + else() + set (_prefixHeader "${_prefixFiles}") + endif() + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}") + endif() + # because CMake PRE_BUILD command does not support dependencies, + # we check dependencies explicity in cotire script mode when the pre-build action is run + add_custom_command( + TARGET "${_target}" + PRE_BUILD ${_cmds} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Updating target ${_target} prefix headers" + VERBATIM) + # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++ + set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") + set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}") + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generator, we force inclusion of the prefix header for all target source files + # if this is a single-language target without any excluded files + if (_wholeTarget) + set (_language "${_languages}") + # for Visual Studio and Intel, precompiled header inclusion is always done on the source file level + # see cotire_setup_pch_file_inclusion + if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_prefixFile) + get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) + set (_options COMPILE_OPTIONS) + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _options) + set_property(TARGET ${_target} APPEND PROPERTY ${_options}) + endif() + endif() + endif() + endif() +endfunction() + +function (cotire_setup_unity_generation_commands _language _target _targetScript _targetConfigScript _unityFiles _cmdsVar) + set (_dependencySources "") + cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN}) + foreach (_unityFile ${_unityFiles}) + set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE) + # set up compiled unity source dependencies via OBJECT_DEPENDS + # this ensures that missing source files are generated before the unity file is compiled + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}") + endif() + if (_dependencySources) + # the OBJECT_DEPENDS property requires a list of full paths + set (_objectDependsPaths "") + foreach (_sourceFile ${_dependencySources}) + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND _objectDependsPaths "${_sourceLocation}") + endforeach() + set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_objectDependsPaths}) + endif() + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # unity file compilation results in potentially huge object file, thus use /bigobj by default unter MSVC and Windows Intel + set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj") + endif() + cotire_set_cmd_to_prologue(_unityCmd) + list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetConfigScript}" "${_unityFile}") + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_unityCmdDepends "${_targetScript}") + else() + # CMake 3.1.0 supports generator expressions in arguments to DEPENDS + set (_unityCmdDepends "${_targetConfigScript}") + endif() + file (RELATIVE_PATH _unityFileRelPath "${CMAKE_BINARY_DIR}" "${_unityFile}") + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_unityCmdDepends}") + endif() + add_custom_command( + OUTPUT "${_unityFile}" + COMMAND ${_unityCmd} + DEPENDS ${_unityCmdDepends} + COMMENT "Generating ${_language} unity source ${_unityFileRelPath}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_unityCmd}) + endforeach() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) + set (_sourceFiles ${ARGN}) + set (_dependencySources "") + cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles}) + cotire_set_cmd_to_prologue(_prefixCmd) + list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" ${_unityFiles}) + set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE) + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_unityFile} ${_dependencySources}") + endif() + file (RELATIVE_PATH _prefixFileRelPath "${CMAKE_BINARY_DIR}" "${_prefixFile}") + get_filename_component(_prefixFileExt "${_prefixFile}" EXT) + if (_prefixFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_prefixFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_prefixFileRelPath}") + endif() + # prevent pre-processing errors upon generating the prefix header when a target's generated include file does not yet exist + # we do not add a file-level dependency for the target's generated files though, because we only want to depend on their existence + # thus we make the prefix header generation depend on a custom helper target which triggers the generation of the files + set (_preTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}_pre") + if (TARGET ${_preTargetName}) + # custom helper target has already been generated while processing a different language + list (APPEND _dependencySources ${_preTargetName}) + else() + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) + if (_generatedSources) + add_custom_target("${_preTargetName}" DEPENDS ${_generatedSources}) + cotire_init_target("${_preTargetName}") + list (APPEND _dependencySources ${_preTargetName}) + endif() + endif() + add_custom_command( + OUTPUT "${_prefixFile}" "${_prefixFile}.log" + COMMAND ${_prefixCmd} + DEPENDS ${_unityFiles} ${_dependencySources} + COMMENT "${_comment}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_from_unity_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) + set (_sourceFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() + cotire_setup_prefix_generation_command( + ${_language} ${_target} "${_targetScript}" + "${_prefixSourceFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) + endif() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_from_provided_command _language _target _targetScript _prefixFile _cmdsVar) + set (_prefixHeaderFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixSourceFile}" _cmds ${_prefixHeaderFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) + endif() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_init_cotire_target_properties _target) + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER TRUE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD TRUE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN FALSE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_SOURCE_DIR}") + cotire_check_is_path_relative_to("${CMAKE_BINARY_DIR}" _isRelative "${CMAKE_SOURCE_DIR}") + if (NOT _isRelative) + set_property(TARGET ${_target} APPEND PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_BINARY_DIR}") + endif() + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "COPY_UNITY") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET) + if (NOT _isSet) + if (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}") + else() + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "") + endif() + endif() +endfunction() + +function (cotire_make_target_message _target _languages _disableMsg _targetMsgVar) + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + string (REPLACE ";" " " _languagesStr "${_languages}") + math (EXPR _numberOfExcludedFiles "${ARGC} - 4") + if (_numberOfExcludedFiles EQUAL 0) + set (_excludedStr "") + elseif (COTIRE_VERBOSE OR _numberOfExcludedFiles LESS 4) + string (REPLACE ";" ", " _excludedStr "excluding ${ARGN}") + else() + set (_excludedStr "excluding ${_numberOfExcludedFiles} files") + endif() + set (_targetMsg "") + if (NOT _languages) + set (_targetMsg "Target ${_target} cannot be cotired.") + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetUsePCH AND NOT _targetAddSCU) + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build and precompiled header.") + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetUsePCH) + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header.") + endif() + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetAddSCU) + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build.") + endif() + else() + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired.") + endif() + endif() + set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE) +endfunction() + +function (cotire_choose_target_languages _target _targetLanguagesVar _wholeTargetVar) + set (_languages ${ARGN}) + set (_allSourceFiles "") + set (_allExcludedSourceFiles "") + set (_allCotiredSourceFiles "") + set (_targetLanguages "") + set (_pchEligibleTargetLanguages "") + get_target_property(_targetType ${_target} TYPE) + get_target_property(_targetSourceFiles ${_target} SOURCES) + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + set (_disableMsg "") + foreach (_language ${_languages}) + get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER) + get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE) + if (_prefixHeader OR _unityBuildFile) + message (STATUS "cotire: target ${_target} has already been cotired.") + set (${_targetLanguagesVar} "" PARENT_SCOPE) + return() + endif() + if (_targetUsePCH AND "${_language}" MATCHES "^C|CXX$") + cotire_check_precompiled_header_support("${_language}" "${_target}" _disableMsg) + if (_disableMsg) + set (_targetUsePCH FALSE) + endif() + endif() + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (_sourceFiles OR _excludedSources OR _cotiredSources) + list (APPEND _targetLanguages ${_language}) + endif() + if (_sourceFiles) + list (APPEND _allSourceFiles ${_sourceFiles}) + endif() + list (LENGTH _sourceFiles _numberOfSources) + if (NOT _numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + list (APPEND _pchEligibleTargetLanguages ${_language}) + endif() + if (_excludedSources) + list (APPEND _allExcludedSourceFiles ${_excludedSources}) + endif() + if (_cotiredSources) + list (APPEND _allCotiredSourceFiles ${_cotiredSources}) + endif() + endforeach() + set (_targetMsgLevel STATUS) + if (NOT _targetLanguages) + string (REPLACE ";" " or " _languagesStr "${_languages}") + set (_disableMsg "No ${_languagesStr} source files.") + set (_targetUsePCH FALSE) + set (_targetAddSCU FALSE) + endif() + if (_targetUsePCH) + if (_allCotiredSourceFiles) + cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles}) + list (REMOVE_DUPLICATES _cotireTargets) + string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}") + set (_disableMsg "Target sources already include a precompiled header for target(s) ${_cotireTargets}.") + set (_disableMsg "${_disableMsg} Set target property COTIRE_ENABLE_PRECOMPILED_HEADER to FALSE for targets ${_target},") + set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.") + set (_targetMsgLevel SEND_ERROR) + set (_targetUsePCH FALSE) + elseif (NOT _pchEligibleTargetLanguages) + set (_disableMsg "Too few applicable sources.") + set (_targetUsePCH FALSE) + elseif (XCODE AND _allExcludedSourceFiles) + # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target + set (_disableMsg "Exclusion of source files not supported for generator Xcode.") + set (_targetUsePCH FALSE) + elseif (XCODE AND "${_targetType}" STREQUAL "OBJECT_LIBRARY") + # for Xcode, we cannot apply the required PRE_BUILD action to generate the prefix header to an OBJECT_LIBRARY target + set (_disableMsg "Required PRE_BUILD action not supported for OBJECT_LIBRARY targets for generator Xcode.") + set (_targetUsePCH FALSE) + endif() + endif() + set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER ${_targetUsePCH}) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD ${_targetAddSCU}) + cotire_make_target_message(${_target} "${_targetLanguages}" "${_disableMsg}" _targetMsg ${_allExcludedSourceFiles}) + if (_targetMsg) + if (NOT DEFINED COTIREMSG_${_target}) + set (COTIREMSG_${_target} "") + endif() + if (COTIRE_VERBOSE OR NOT "${_targetMsgLevel}" STREQUAL "STATUS" OR + NOT "${COTIREMSG_${_target}}" STREQUAL "${_targetMsg}") + # cache message to avoid redundant messages on re-configure + set (COTIREMSG_${_target} "${_targetMsg}" CACHE INTERNAL "${_target} cotire message.") + message (${_targetMsgLevel} "${_targetMsg}") + endif() + endif() + list (LENGTH _targetLanguages _numberOfLanguages) + if (_numberOfLanguages GREATER 1 OR _allExcludedSourceFiles) + set (${_wholeTargetVar} FALSE PARENT_SCOPE) + else() + set (${_wholeTargetVar} TRUE PARENT_SCOPE) + endif() + set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE) +endfunction() + +function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar) + set (_sourceFiles ${ARGN}) + get_target_property(_maxIncludes ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) + if (_maxIncludes MATCHES "(-j|--parallel|--jobs) ?([0-9]*)") + set (_numberOfThreads "${CMAKE_MATCH_2}") + if (NOT _numberOfThreads) + # use all available cores + ProcessorCount(_numberOfThreads) + endif() + list (LENGTH _sourceFiles _numberOfSources) + math (EXPR _maxIncludes "(${_numberOfSources} + ${_numberOfThreads} - 1) / ${_numberOfThreads}") + elseif (NOT _maxIncludes MATCHES "[0-9]+") + set (_maxIncludes 0) + endif() + if (COTIRE_DEBUG) + message (STATUS "${_target} unity source max includes: ${_maxIncludes}") + endif() + set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE) +endfunction() + +function (cotire_process_target_language _language _configurations _target _wholeTarget _cmdsVar) + set (${_cmdsVar} "" PARENT_SCOPE) + get_target_property(_targetSourceFiles ${_target} SOURCES) + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (NOT _sourceFiles AND NOT _cotiredSources) + return() + endif() + set (_cmds "") + # check for user provided unity source file list + get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT) + if (NOT _unitySourceFiles) + set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources}) + endif() + cotire_generate_target_script( + ${_language} "${_configurations}" ${_target} _targetScript _targetConfigScript ${_unitySourceFiles}) + # set up unity files for parallel compilation + cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles}) + cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles}) + list (LENGTH _unityFiles _numberOfUnityFiles) + if (_numberOfUnityFiles EQUAL 0) + return() + elseif (_numberOfUnityFiles GREATER 1) + cotire_setup_unity_generation_commands( + ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) + endif() + # set up single unity file for prefix header generation + cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) + cotire_setup_unity_generation_commands( + ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFile}" _cmds ${_unitySourceFiles}) + cotire_make_prefix_file_path(${_language} ${_target} _prefixFile) + # set up prefix header + if (_prefixFile) + # check for user provided prefix header files + get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) + if (_prefixHeaderFiles) + cotire_setup_prefix_generation_from_provided_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) + else() + cotire_setup_prefix_generation_from_unity_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_unityFile}" _cmds ${_unitySourceFiles}) + endif() + # check if selected language has enough sources at all + list (LENGTH _sourceFiles _numberOfSources) + if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + set (_targetUsePCH FALSE) + else() + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + endif() + if (_targetUsePCH) + cotire_make_pch_file_path(${_language} ${_target} _pchFile) + if (_pchFile) + # first file in _sourceFiles is passed as the host file + cotire_setup_pch_file_compilation( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) + cotire_setup_pch_file_inclusion( + ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) + endif() + elseif (_prefixHeaderFiles) + # user provided prefix header must be included unconditionally + cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) + endif() + endif() + # mark target as cotired for language + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE "${_unityFiles}") + if (_prefixFile) + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER "${_prefixFile}") + if (_targetUsePCH AND _pchFile) + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}") + endif() + endif() + set (${_cmdsVar} ${_cmds} PARENT_SCOPE) +endfunction() + +function (cotire_setup_clean_target _target) + set (_cleanTargetName "${_target}${COTIRE_CLEAN_TARGET_SUFFIX}") + if (NOT TARGET "${_cleanTargetName}") + cotire_set_cmd_to_prologue(_cmds) + get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}") + add_custom_target(${_cleanTargetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up target ${_target} cotire generated files" + VERBATIM) + cotire_init_target("${_cleanTargetName}") + endif() +endfunction() + +function (cotire_setup_pch_target _languages _configurations _target) + if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generators, we add a custom target to trigger the generation of the cotire related files + set (_dependsFiles "") + foreach (_language ${_languages}) + set (_props COTIRE_${_language}_PREFIX_HEADER COTIRE_${_language}_UNITY_SOURCE) + if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # Visual Studio and Intel only create precompiled header as a side effect + list (INSERT _props 0 COTIRE_${_language}_PRECOMPILED_HEADER) + endif() + cotire_get_first_set_property_value(_dependsFile TARGET ${_target} ${_props}) + if (_dependsFile) + list (APPEND _dependsFiles "${_dependsFile}") + endif() + endforeach() + if (_dependsFiles) + set (_pchTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}") + add_custom_target("${_pchTargetName}" DEPENDS ${_dependsFiles}) + cotire_init_target("${_pchTargetName}") + cotire_add_to_pch_all_target(${_pchTargetName}) + endif() + else() + # for other generators, we add the "clean all" target to clean up the precompiled header + cotire_setup_clean_all_target() + endif() +endfunction() + +function (cotire_collect_unity_target_sources _target _languages _unityTargetSourcesVar) + get_target_property(_targetSourceFiles ${_target} SOURCES) + set (_unityTargetSources ${_targetSourceFiles}) + foreach (_language ${_languages}) + get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) + if (_unityFiles) + # remove source files that are included in the unity source + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (_sourceFiles OR _cotiredSources) + list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) + endif() + # add unity source files instead + list (APPEND _unityTargetSources ${_unityFiles}) + endif() + endforeach() + set (${_unityTargetSourcesVar} ${_unityTargetSources} PARENT_SCOPE) +endfunction() + +function (cotire_setup_unity_target_pch_usage _languages _target) + foreach (_language ${_languages}) + get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) + if (_unityFiles) + get_property(_userPrefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_userPrefixFile AND _prefixFile) + # user provided prefix header must be included unconditionally by unity sources + cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_unityFiles}) + endif() + endif() + endforeach() +endfunction() + +function (cotire_setup_unity_build_target _languages _configurations _target) + get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) + if (NOT _unityTargetName) + set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}") + endif() + # determine unity target sub type + get_target_property(_targetType ${_target} TYPE) + if ("${_targetType}" STREQUAL "EXECUTABLE") + set (_unityTargetSubType "") + elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") + set (_unityTargetSubType "${CMAKE_MATCH_1}") + else() + message (WARNING "cotire: target ${_target} has unknown target type ${_targetType}.") + return() + endif() + # determine unity target sources + set (_unityTargetSources "") + cotire_collect_unity_target_sources(${_target} "${_languages}" _unityTargetSources) + # handle automatic Qt processing + get_target_property(_targetAutoMoc ${_target} AUTOMOC) + get_target_property(_targetAutoUic ${_target} AUTOUIC) + get_target_property(_targetAutoRcc ${_target} AUTORCC) + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # if the original target sources are subject to CMake's automatic Qt processing, + # also include implicitly generated _automoc.cpp file + list (APPEND _unityTargetSources "${_target}_automoc.cpp") + set_property (SOURCE "${_target}_automoc.cpp" PROPERTY GENERATED TRUE) + endif() + # prevent AUTOMOC, AUTOUIC and AUTORCC properties from being set when the unity target is created + set (CMAKE_AUTOMOC OFF) + set (CMAKE_AUTOUIC OFF) + set (CMAKE_AUTORCC OFF) + if (COTIRE_DEBUG) + message (STATUS "add target ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") + endif() + # generate unity target + if ("${_targetType}" STREQUAL "EXECUTABLE") + add_executable(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) + else() + add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) + endif() + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # depend on the original target's implicity generated _automoc target + add_dependencies(${_unityTargetName} ${_target}_automoc) + endif() + # copy output location properties + set (_outputDirProperties + ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_ + LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_ + RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_) + if (COTIRE_UNITY_OUTPUT_DIRECTORY) + set (_setDefaultOutputDir TRUE) + if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}") + set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}") + else() + # append relative COTIRE_UNITY_OUTPUT_DIRECTORY to target's actual output directory + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) + cotire_resolve_config_properites("${_configurations}" _properties ${_outputDirProperties}) + foreach (_property ${_properties}) + get_property(_outputDir TARGET ${_target} PROPERTY ${_property}) + if (_outputDir) + get_filename_component(_outputDir "${_outputDir}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) + set_property(TARGET ${_unityTargetName} PROPERTY ${_property} "${_outputDir}") + set (_setDefaultOutputDir FALSE) + endif() + endforeach() + if (_setDefaultOutputDir) + get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) + endif() + endif() + if (_setDefaultOutputDir) + set_target_properties(${_unityTargetName} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${_outputDir}" + LIBRARY_OUTPUT_DIRECTORY "${_outputDir}" + RUNTIME_OUTPUT_DIRECTORY "${_outputDir}") + endif() + else() + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ${_outputDirProperties}) + endif() + # copy output name + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ARCHIVE_OUTPUT_NAME ARCHIVE_OUTPUT_NAME_ + LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_ + OUTPUT_NAME OUTPUT_NAME_ + RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_ + PREFIX _POSTFIX SUFFIX + IMPORT_PREFIX IMPORT_SUFFIX) + # copy compile stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + COMPILE_DEFINITIONS COMPILE_DEFINITIONS_ + COMPILE_FLAGS COMPILE_OPTIONS + Fortran_FORMAT Fortran_MODULE_DIRECTORY + INCLUDE_DIRECTORIES + INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_ + POSITION_INDEPENDENT_CODE + C_COMPILER_LAUNCHER CXX_COMPILER_LAUNCHER + C_INCLUDE_WHAT_YOU_USE CXX_INCLUDE_WHAT_YOU_USE + C_VISIBILITY_PRESET CXX_VISIBILITY_PRESET VISIBILITY_INLINES_HIDDEN) + # copy compile features + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + C_EXTENSIONS C_STANDARD C_STANDARD_REQUIRED + CXX_EXTENSIONS CXX_STANDARD CXX_STANDARD_REQUIRED + COMPILE_FEATURES) + # copy interface stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_NUMBER_MAX COMPATIBLE_INTERFACE_NUMBER_MIN + COMPATIBLE_INTERFACE_STRING + INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_FEATURES INTERFACE_COMPILE_OPTIONS + INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SOURCES + INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES + INTERFACE_AUTOUIC_OPTIONS NO_SYSTEM_FROM_IMPORTED) + # copy link stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + BUILD_WITH_INSTALL_RPATH INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH + LINKER_LANGUAGE LINK_DEPENDS LINK_DEPENDS_NO_SHARED + LINK_FLAGS LINK_FLAGS_ + LINK_INTERFACE_LIBRARIES LINK_INTERFACE_LIBRARIES_ + LINK_INTERFACE_MULTIPLICITY LINK_INTERFACE_MULTIPLICITY_ + LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC + STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_ + NO_SONAME SOVERSION VERSION) + # copy cmake stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK) + # copy Apple platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + BUNDLE BUNDLE_EXTENSION FRAMEWORK FRAMEWORK_VERSION INSTALL_NAME_DIR + MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST MACOSX_RPATH + OSX_ARCHITECTURES OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE XCTEST) + # copy Windows platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + GNUtoMS + COMPILE_PDB_NAME COMPILE_PDB_NAME_ + COMPILE_PDB_OUTPUT_DIRECTORY COMPILE_PDB_OUTPUT_DIRECTORY_ + PDB_NAME PDB_NAME_ PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_ + VS_DESKTOP_EXTENSIONS_VERSION VS_DOTNET_REFERENCES VS_DOTNET_TARGET_FRAMEWORK_VERSION + VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE + VS_IOT_EXTENSIONS_VERSION VS_IOT_STARTUP_TASK + VS_KEYWORD VS_MOBILE_EXTENSIONS_VERSION + VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER + VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION + VS_WINRT_COMPONENT VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES + WIN32_EXECUTABLE WINDOWS_EXPORT_ALL_SYMBOLS) + # copy Android platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ANDROID_API ANDROID_API_MIN ANDROID_GUI + ANDROID_ANT_ADDITIONAL_OPTIONS ANDROID_ARCH ANDROID_ASSETS_DIRECTORIES + ANDROID_JAR_DEPENDENCIES ANDROID_JAR_DIRECTORIES ANDROID_JAVA_SOURCE_DIR + ANDROID_NATIVE_LIB_DEPENDENCIES ANDROID_NATIVE_LIB_DIRECTORIES + ANDROID_PROCESS_MAX ANDROID_PROGUARD ANDROID_PROGUARD_CONFIG_PATH + ANDROID_SECURE_PROPS_PATH ANDROID_SKIP_ANT_STEP ANDROID_STL_TYPE) + # use output name from original target + get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME) + if (NOT _targetOutputName) + set_property(TARGET ${_unityTargetName} PROPERTY OUTPUT_NAME "${_target}") + endif() + # use export symbol from original target + cotire_get_target_export_symbol("${_target}" _defineSymbol) + if (_defineSymbol) + set_property(TARGET ${_unityTargetName} PROPERTY DEFINE_SYMBOL "${_defineSymbol}") + if ("${_targetType}" STREQUAL "EXECUTABLE") + set_property(TARGET ${_unityTargetName} PROPERTY ENABLE_EXPORTS TRUE) + endif() + endif() + cotire_init_target(${_unityTargetName}) + cotire_add_to_unity_all_target(${_unityTargetName}) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_TARGET_NAME "${_unityTargetName}") +endfunction(cotire_setup_unity_build_target) + +function (cotire_target _target) + set(_options "") + set(_oneValueArgs "") + set(_multiValueArgs LANGUAGES CONFIGURATIONS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_LANGUAGES) + get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + endif() + if (NOT _option_CONFIGURATIONS) + cotire_get_configuration_types(_option_CONFIGURATIONS) + endif() + # trivial checks + get_target_property(_imported ${_target} IMPORTED) + if (_imported) + message (WARNING "cotire: imported target ${_target} cannot be cotired.") + return() + endif() + # resolve alias + get_target_property(_aliasName ${_target} ALIASED_TARGET) + if (_aliasName) + if (COTIRE_DEBUG) + message (STATUS "${_target} is an alias. Applying cotire to aliased target ${_aliasName} instead.") + endif() + set (_target ${_aliasName}) + endif() + # check if target needs to be cotired for build type + # when using configuration types, the test is performed at build time + cotire_init_cotire_target_properties(${_target}) + if (NOT CMAKE_CONFIGURATION_TYPES) + if (CMAKE_BUILD_TYPE) + list (FIND _option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}" _index) + else() + list (FIND _option_CONFIGURATIONS "None" _index) + endif() + if (_index EQUAL -1) + if (COTIRE_DEBUG) + message (STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} not cotired (${_option_CONFIGURATIONS})") + endif() + return() + endif() + endif() + # when not using configuration types, immediately create cotire intermediate dir + if (NOT CMAKE_CONFIGURATION_TYPES) + cotire_get_intermediate_dir(_baseDir) + file (MAKE_DIRECTORY "${_baseDir}") + endif() + # choose languages that apply to the target + cotire_choose_target_languages("${_target}" _targetLanguages _wholeTarget ${_option_LANGUAGES}) + if (NOT _targetLanguages) + return() + endif() + set (_cmds "") + foreach (_language ${_targetLanguages}) + cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" ${_target} ${_wholeTarget} _cmd) + if (_cmd) + list (APPEND _cmds ${_cmd}) + endif() + endforeach() + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + if (_targetAddSCU) + cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) + endif() + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + if (_targetUsePCH) + cotire_setup_target_pch_usage("${_targetLanguages}" ${_target} ${_wholeTarget} ${_cmds}) + cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) + if (_targetAddSCU) + cotire_setup_unity_target_pch_usage("${_targetLanguages}" ${_target}) + endif() + endif() + get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN) + if (_targetAddCleanTarget) + cotire_setup_clean_target(${_target}) + endif() +endfunction(cotire_target) + +function (cotire_map_libraries _strategy _mappedLibrariesVar) + set (_mappedLibraries "") + foreach (_library ${ARGN}) + if (TARGET "${_library}" AND "${_strategy}" MATCHES "COPY_UNITY") + # use target's corresponding unity target, if available + get_target_property(_libraryUnityTargetName ${_library} COTIRE_UNITY_TARGET_NAME) + if (TARGET "${_libraryUnityTargetName}") + list (APPEND _mappedLibraries "${_libraryUnityTargetName}") + else() + list (APPEND _mappedLibraries "${_library}") + endif() + else() + list (APPEND _mappedLibraries "${_library}") + endif() + endforeach() + list (REMOVE_DUPLICATES _mappedLibraries) + set (${_mappedLibrariesVar} ${_mappedLibraries} PARENT_SCOPE) +endfunction() + +function (cotire_target_link_libraries _target) + get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) + if (TARGET "${_unityTargetName}") + get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) + if (COTIRE_DEBUG) + message (STATUS "unity target ${_unityTargetName} link strategy: ${_linkLibrariesStrategy}") + endif() + if ("${_linkLibrariesStrategy}" MATCHES "^(COPY|COPY_UNITY)$") + set (_unityLinkLibraries "") + get_target_property(_linkLibraries ${_target} LINK_LIBRARIES) + if (_linkLibraries) + list (APPEND _unityLinkLibraries ${_linkLibraries}) + endif() + get_target_property(_interfaceLinkLibraries ${_target} INTERFACE_LINK_LIBRARIES) + if (_interfaceLinkLibraries) + list (APPEND _unityLinkLibraries ${_interfaceLinkLibraries}) + endif() + cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkLibraries ${_unityLinkLibraries}) + if (COTIRE_DEBUG) + message (STATUS "unity target ${_unityTargetName} libraries: ${_unityLinkLibraries}") + endif() + if (_unityLinkLibraries) + target_link_libraries(${_unityTargetName} ${_unityLinkLibraries}) + endif() + endif() + endif() +endfunction(cotire_target_link_libraries) + +function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName) + if (_targetName) + file (GLOB_RECURSE _cotireFiles "${_binaryDir}/${_targetName}*.*") + else() + file (GLOB_RECURSE _cotireFiles "${_binaryDir}/*.*") + endif() + # filter files in intermediate directory + set (_filesToRemove "") + foreach (_file ${_cotireFiles}) + get_filename_component(_dir "${_file}" DIRECTORY) + get_filename_component(_dirName "${_dir}" NAME) + if ("${_dirName}" STREQUAL "${_cotireIntermediateDirName}") + list (APPEND _filesToRemove "${_file}") + endif() + endforeach() + if (_filesToRemove) + if (COTIRE_VERBOSE) + message (STATUS "cleaning up ${_filesToRemove}") + endif() + file (REMOVE ${_filesToRemove}) + endif() +endfunction() + +function (cotire_init_target _targetName) + if (COTIRE_TARGETS_FOLDER) + set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}") + endif() + set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_ALL TRUE) + if (MSVC_IDE) + set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) + endif() +endfunction() + +function (cotire_add_to_pch_all_target _pchTargetName) + set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + cotire_init_target("${_targetName}") + endif() + cotire_setup_clean_all_target() + add_dependencies(${_targetName} ${_pchTargetName}) +endfunction() + +function (cotire_add_to_unity_all_target _unityTargetName) + set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + cotire_init_target("${_targetName}") + endif() + cotire_setup_clean_all_target() + add_dependencies(${_targetName} ${_unityTargetName}) +endfunction() + +function (cotire_setup_clean_all_target) + set (_targetName "${COTIRE_CLEAN_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + cotire_set_cmd_to_prologue(_cmds) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}") + add_custom_target(${_targetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up all cotire generated files" + VERBATIM) + cotire_init_target("${_targetName}") + endif() +endfunction() + +function (cotire) + set(_options "") + set(_oneValueArgs "") + set(_multiValueArgs LANGUAGES CONFIGURATIONS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + set (_targets ${_option_UNPARSED_ARGUMENTS}) + foreach (_target ${_targets}) + if (TARGET ${_target}) + cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}) + else() + message (WARNING "cotire: ${_target} is not a target.") + endif() + endforeach() + foreach (_target ${_targets}) + if (TARGET ${_target}) + cotire_target_link_libraries(${_target}) + endif() + endforeach() +endfunction() + +if (CMAKE_SCRIPT_MODE_FILE) + + # cotire is being run in script mode + # locate -P on command args + set (COTIRE_ARGC -1) + foreach (_index RANGE ${CMAKE_ARGC}) + if (COTIRE_ARGC GREATER -1) + set (COTIRE_ARGV${COTIRE_ARGC} "${CMAKE_ARGV${_index}}") + math (EXPR COTIRE_ARGC "${COTIRE_ARGC} + 1") + elseif ("${CMAKE_ARGV${_index}}" STREQUAL "-P") + set (COTIRE_ARGC 0) + endif() + endforeach() + + # include target script if available + if ("${COTIRE_ARGV2}" MATCHES "\\.cmake$") + # the included target scripts sets up additional variables relating to the target (e.g., COTIRE_TARGET_SOURCES) + include("${COTIRE_ARGV2}") + endif() + + if (COTIRE_DEBUG) + message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}") + endif() + + if (NOT COTIRE_BUILD_TYPE) + set (COTIRE_BUILD_TYPE "None") + endif() + string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig) + set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}}) + set (_systemIncludeDirs ${COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}}) + set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}}) + set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}}) + # check if target has been cotired for actual build type COTIRE_BUILD_TYPE + list (FIND COTIRE_TARGET_CONFIGURATION_TYPES "${COTIRE_BUILD_TYPE}" _index) + if (_index GREATER -1) + set (_sources ${COTIRE_TARGET_SOURCES}) + set (_sourcesDefinitions ${COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig}}) + else() + if (COTIRE_DEBUG) + message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})") + endif() + set (_sources "") + set (_sourcesDefinitions "") + endif() + set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS}) + set (_targetPostUndefs ${COTIRE_TARGET_POST_UNDEFS}) + set (_sourcesPreUndefs ${COTIRE_TARGET_SOURCES_PRE_UNDEFS}) + set (_sourcesPostUndefs ${COTIRE_TARGET_SOURCES_POST_UNDEFS}) + + if ("${COTIRE_ARGV1}" STREQUAL "unity") + + if (XCODE) + # executing pre-build action under Xcode, check dependency on target script + set (_dependsOption DEPENDS "${COTIRE_ARGV2}") + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources}) + + cotire_generate_unity_source( + "${COTIRE_ARGV3}" ${_sources} + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions} + PRE_UNDEFS ${_targetPreUndefs} + POST_UNDEFS ${_targetPostUndefs} + SOURCES_PRE_UNDEFS ${_sourcesPreUndefs} + SOURCES_POST_UNDEFS ${_sourcesPostUndefs} + ${_dependsOption}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "prefix") + + if (XCODE) + # executing pre-build action under Xcode, check dependency on unity file and prefix dependencies + set (_dependsOption DEPENDS "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}) + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + set (_files "") + foreach (_index RANGE 4 ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + cotire_generate_prefix_header( + "${COTIRE_ARGV3}" ${_files} + COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" + COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} + COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}" + INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH} + IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}" + INCLUDE_PRIORITY_PATH ${COTIRE_TARGET_INCLUDE_PRIORITY_PATH} + INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} + COMPILE_DEFINITIONS ${_compileDefinitions} + COMPILE_FLAGS ${_compileFlags} + ${_dependsOption}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "precompile") + + set (_files "") + foreach (_index RANGE 5 ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + cotire_precompile_prefix_header( + "${COTIRE_ARGV3}" "${COTIRE_ARGV4}" "${COTIRE_ARGV5}" + COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" + COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} + COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} + COMPILE_DEFINITIONS ${_compileDefinitions} + COMPILE_FLAGS ${_compileFlags}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "combine") + + if (COTIRE_TARGET_LANGUAGE) + set (_combinedFile "${COTIRE_ARGV3}") + set (_startIndex 4) + else() + set (_combinedFile "${COTIRE_ARGV2}") + set (_startIndex 3) + endif() + set (_files "") + foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + if (XCODE) + # executing pre-build action under Xcode, check dependency on files to be combined + set (_dependsOption DEPENDS ${_files}) + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + if (COTIRE_TARGET_LANGUAGE) + cotire_generate_unity_source( + "${_combinedFile}" ${_files} + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + ${_dependsOption}) + else() + cotire_generate_unity_source("${_combinedFile}" ${_files} ${_dependsOption}) + endif() + + elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup") + + cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}") + + else() + message (FATAL_ERROR "cotire: unknown command \"${COTIRE_ARGV1}\".") + endif() + +else() + + # cotire is being run in include mode + # set up all variable and property definitions + + if (NOT DEFINED COTIRE_DEBUG_INIT) + if (DEFINED COTIRE_DEBUG) + set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG}) + else() + set (COTIRE_DEBUG_INIT FALSE) + endif() + endif() + option (COTIRE_DEBUG "Enable cotire debugging output?" ${COTIRE_DEBUG_INIT}) + + if (NOT DEFINED COTIRE_VERBOSE_INIT) + if (DEFINED COTIRE_VERBOSE) + set (COTIRE_VERBOSE_INIT ${COTIRE_VERBOSE}) + else() + set (COTIRE_VERBOSE_INIT FALSE) + endif() + endif() + option (COTIRE_VERBOSE "Enable cotire verbose output?" ${COTIRE_VERBOSE_INIT}) + + set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS "inc;inl;ipp" CACHE STRING + "Ignore headers with the listed file extensions from the generated prefix header.") + + set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH "" CACHE STRING + "Ignore headers from these directories when generating the prefix header.") + + set (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS "m;mm" CACHE STRING + "Ignore sources with the listed file extensions from the generated unity source.") + + set (COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES "3" CACHE STRING + "Minimum number of sources in target required to enable use of precompiled header.") + + if (NOT DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT) + if (DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT ${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}) + elseif ("${CMAKE_GENERATOR}" MATCHES "JOM|Ninja|Visual Studio") + # enable parallelization for generators that run multiple jobs by default + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "-j") + else() + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "0") + endif() + endif() + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT}" CACHE STRING + "Maximum number of source files to include in a single unity source file.") + + if (NOT COTIRE_PREFIX_HEADER_FILENAME_SUFFIX) + set (COTIRE_PREFIX_HEADER_FILENAME_SUFFIX "_prefix") + endif() + if (NOT COTIRE_UNITY_SOURCE_FILENAME_SUFFIX) + set (COTIRE_UNITY_SOURCE_FILENAME_SUFFIX "_unity") + endif() + if (NOT COTIRE_INTDIR) + set (COTIRE_INTDIR "cotire") + endif() + if (NOT COTIRE_PCH_ALL_TARGET_NAME) + set (COTIRE_PCH_ALL_TARGET_NAME "all_pch") + endif() + if (NOT COTIRE_UNITY_BUILD_ALL_TARGET_NAME) + set (COTIRE_UNITY_BUILD_ALL_TARGET_NAME "all_unity") + endif() + if (NOT COTIRE_CLEAN_ALL_TARGET_NAME) + set (COTIRE_CLEAN_ALL_TARGET_NAME "clean_cotire") + endif() + if (NOT COTIRE_CLEAN_TARGET_SUFFIX) + set (COTIRE_CLEAN_TARGET_SUFFIX "_clean_cotire") + endif() + if (NOT COTIRE_PCH_TARGET_SUFFIX) + set (COTIRE_PCH_TARGET_SUFFIX "_pch") + endif() + if (MSVC) + # MSVC default PCH memory scaling factor of 100 percent (75 MB) is too small for template heavy C++ code + # use a bigger default factor of 170 percent (128 MB) + if (NOT DEFINED COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (COTIRE_PCH_MEMORY_SCALING_FACTOR "170") + endif() + endif() + if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX) + set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity") + endif() + if (NOT DEFINED COTIRE_TARGETS_FOLDER) + set (COTIRE_TARGETS_FOLDER "cotire") + endif() + if (NOT DEFINED COTIRE_UNITY_OUTPUT_DIRECTORY) + if ("${CMAKE_GENERATOR}" MATCHES "Ninja") + # generated Ninja build files do not work if the unity target produces the same output file as the cotired target + set (COTIRE_UNITY_OUTPUT_DIRECTORY "unity") + else() + set (COTIRE_UNITY_OUTPUT_DIRECTORY "") + endif() + endif() + + # define cotire cache variables + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH" + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "The variable can be set to a semicolon separated list of include directories." + "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." + "If not defined, defaults to empty list." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS" + BRIEF_DOCS "Ignore includes with the listed file extensions from the generated prefix header." + FULL_DOCS + "The variable can be set to a semicolon separated list of file extensions." + "If a header file extension matches one in the list, it will be excluded from the generated prefix header." + "Includes with an extension in CMAKE__SOURCE_FILE_EXTENSIONS are always ignored." + "If not defined, defaults to inc;inl;ipp." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS" + BRIEF_DOCS "Exclude sources with the listed file extensions from the generated unity source." + FULL_DOCS + "The variable can be set to a semicolon separated list of file extensions." + "If a source file extension matches one in the list, it will be excluded from the generated unity source file." + "Source files with an extension in CMAKE__IGNORE_EXTENSIONS are always excluded." + "If not defined, defaults to m;mm." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES" + BRIEF_DOCS "Minimum number of sources in target required to enable use of precompiled header." + FULL_DOCS + "The variable can be set to an integer > 0." + "If a target contains less than that number of source files, cotire will not enable the use of the precompiled header for the target." + "If not defined, defaults to 3." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES" + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "This may be set to an integer >= 0." + "If 0, cotire will only create a single unity source file." + "If a target contains more than that number of source files, cotire will create multiple unity source files for it." + "Can be set to \"-j\" to optimize the count of unity source files for the number of available processor cores." + "Can be set to \"-j jobs\" to optimize the number of unity source files for the given number of simultaneous jobs." + "Is used to initialize the target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." + "Defaults to \"-j\" for the generators Visual Studio, JOM or Ninja. Defaults to 0 otherwise." + ) + + # define cotire directory properties + + define_property( + DIRECTORY PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" + BRIEF_DOCS "Modify build command of cotired targets added in this directory to make use of the generated precompiled header." + FULL_DOCS + "See target property COTIRE_ENABLE_PRECOMPILED_HEADER." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_ADD_UNITY_BUILD" + BRIEF_DOCS "Add a new target that performs a unity build for cotired targets added in this directory." + FULL_DOCS + "See target property COTIRE_ADD_UNITY_BUILD." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_ADD_CLEAN" + BRIEF_DOCS "Add a new target that cleans all cotire generated files for cotired targets added in this directory." + FULL_DOCS + "See target property COTIRE_ADD_CLEAN." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_IGNORE_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" + BRIEF_DOCS "Honor headers from these directories when generating the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" + BRIEF_DOCS "Header paths matching one of these directories are put at the top of the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_PRE_UNDEFS." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_POST_UNDEFS." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" + BRIEF_DOCS "Define strategy for setting up the unity target's link libraries." + FULL_DOCS + "See target property COTIRE_UNITY_LINK_LIBRARIES_INIT." + ) + + # define cotire target properties + + define_property( + TARGET PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" INHERITED + BRIEF_DOCS "Modify this target's build command to make use of the generated precompiled header." + FULL_DOCS + "If this property is set to TRUE, cotire will modify the build command to make use of the generated precompiled header." + "Irrespective of the value of this property, cotire will setup custom commands to generate the unity source and prefix header for the target." + "For makefile based generators cotire will also set up a custom target to manually invoke the generation of the precompiled header." + "The target name will be set to this target's name with the suffix _pch appended." + "Inherited from directory." + "Defaults to TRUE." + ) + + define_property( + TARGET PROPERTY "COTIRE_ADD_UNITY_BUILD" INHERITED + BRIEF_DOCS "Add a new target that performs a unity build for this target." + FULL_DOCS + "If this property is set to TRUE, cotire creates a new target of the same type that uses the generated unity source file instead of the target sources." + "Most of the relevant target properties will be copied from this target to the new unity build target." + "Target dependencies and linked libraries have to be manually set up for the new unity build target." + "The unity target name will be set to this target's name with the suffix _unity appended." + "Inherited from directory." + "Defaults to TRUE." + ) + + define_property( + TARGET PROPERTY "COTIRE_ADD_CLEAN" INHERITED + BRIEF_DOCS "Add a new target that cleans all cotire generated files for this target." + FULL_DOCS + "If this property is set to TRUE, cotire creates a new target that clean all files (unity source, prefix header, precompiled header)." + "The clean target name will be set to this target's name with the suffix _clean_cotire appended." + "Inherited from directory." + "Defaults to FALSE." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" INHERITED + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." + "Inherited from directory." + "If not set, this property is initialized to \${CMAKE_SOURCE_DIR};\${CMAKE_BINARY_DIR}." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" INHERITED + BRIEF_DOCS "Honor headers from these directories when generating the prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "If a header file is found in one of these directories or sub-directories, it will be included in the generated prefix header." + "If a header file is both selected by COTIRE_PREFIX_HEADER_IGNORE_PATH and COTIRE_PREFIX_HEADER_INCLUDE_PATH," + "the option which yields the closer relative path match wins." + "Inherited from directory." + "If not set, this property is initialized to the empty list." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" INHERITED + BRIEF_DOCS "Header paths matching one of these directories are put at the top of prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "Header file paths matching one of these directories will be inserted at the beginning of the generated prefix header." + "Header files are sorted according to the order of the directories in the property." + "If not set, this property is initialized to the empty list." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file before each target source file." + "Inherited from directory." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" INHERITED + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each target source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file after each target source file." + "Inherited from directory." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" INHERITED + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "This may be set to an integer > 0." + "If a target contains more than that number of source files, cotire will create multiple unity build files for it." + "If not set, cotire will only create a single unity source file." + "Inherited from directory." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__UNITY_SOURCE_INIT" + BRIEF_DOCS "User provided unity source file to be used instead of the automatically generated one." + FULL_DOCS + "If set, cotire will only add the given file(s) to the generated unity source file." + "If not set, cotire will add all the target source files to the generated unity source file." + "The property can be set to a user provided unity source file." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__PREFIX_HEADER_INIT" + BRIEF_DOCS "User provided prefix header file to be used instead of the automatically generated one." + FULL_DOCS + "If set, cotire will add the given header file(s) to the generated prefix header file." + "If not set, cotire will generate a prefix header by tracking the header files included by the unity source file." + "The property can be set to a user provided prefix header file (e.g., stdafx.h)." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" INHERITED + BRIEF_DOCS "Define strategy for setting up unity target's link libraries." + FULL_DOCS + "If this property is empty or set to NONE, the generated unity target's link libraries have to be set up manually." + "If this property is set to COPY, the unity target's link libraries will be copied from this target." + "If this property is set to COPY_UNITY, the unity target's link libraries will be copied from this target with considering existing unity targets." + "Inherited from directory." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__UNITY_SOURCE" + BRIEF_DOCS "Read-only property. The generated unity source file(s)." + FULL_DOCS + "cotire sets this property to the path of the generated single computation unit source file for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE__PREFIX_HEADER" + BRIEF_DOCS "Read-only property. The generated prefix header file." + FULL_DOCS + "cotire sets this property to the full path of the generated language prefix header for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE__PRECOMPILED_HEADER" + BRIEF_DOCS "Read-only property. The generated precompiled header file." + FULL_DOCS + "cotire sets this property to the full path of the generated language precompiled header binary for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_TARGET_NAME" + BRIEF_DOCS "The name of the generated unity build target corresponding to this target." + FULL_DOCS + "This property can be set to the desired name of the unity target that will be created by cotire." + "If not set, the unity target name will be set to this target's name with the suffix _unity appended." + "After this target has been processed by cotire, the property is set to the actual name of the generated unity target." + "Defaults to empty string." + ) + + # define cotire source properties + + define_property( + SOURCE PROPERTY "COTIRE_EXCLUDED" + BRIEF_DOCS "Do not modify source file's build command." + FULL_DOCS + "If this property is set to TRUE, the source file's build command will not be modified to make use of the precompiled header." + "The source file will also be excluded from the generated unity source file." + "Source files that have their COMPILE_FLAGS property set will be excluded by default." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_DEPENDENCY" + BRIEF_DOCS "Add this source file to dependencies of the automatically generated prefix header file." + FULL_DOCS + "If this property is set to TRUE, the source file is added to dependencies of the generated prefix header file." + "If the file is modified, cotire will re-generate the prefix header source upon build." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of this source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file before this file is included." + "Defaults to empty string." + ) + + define_property( + SOURCE PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of this source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file after this file is included." + "Defaults to empty string." + ) + + define_property( + SOURCE PROPERTY "COTIRE_START_NEW_UNITY_SOURCE" + BRIEF_DOCS "Start a new unity source file which includes this source file as the first one." + FULL_DOCS + "If this property is set to TRUE, cotire will complete the current unity file and start a new one." + "The new unity source file will include this source file as the first one." + "This property essentially works as a separator for unity source files." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_TARGET" + BRIEF_DOCS "Read-only property. Mark this source file as cotired for the given target." + FULL_DOCS + "cotire sets this property to the name of target, that the source file's build command has been altered for." + "Defaults to empty string." + ) + + message (STATUS "cotire ${COTIRE_CMAKE_MODULE_VERSION} loaded.") + +endif() diff --git a/app/SabrehavenServer/config.lua b/app/SabrehavenServer/config.lua new file mode 100644 index 0000000..01caf06 --- /dev/null +++ b/app/SabrehavenServer/config.lua @@ -0,0 +1,120 @@ +-- Custom +clientVersion = 792 +knightCloseAttackDamageIncreasePercent = 15 +paladinRangeAttackDamageIncreasePercent = 10 +-- Min/Max rate spawn is a multiplication of the map spawntime in spawns.xml Regular monster spawn time is 600. The formula would be randomValue = random(600*100, 600*200) which varies between 60s and 120s +minRateSpawn = 100 +maxRateSpawn = 200 +corpseOwnerEnabled = false +uhTrap = true +ropeSpotBlock = false +showMonsterLoot = true +blockHeight = true +dropItems = false + +-- Combat settings +-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" +worldType = "pvp" +hotkeyAimbotEnabled = true +protectionLevel = 1 +pzLocked = 60000 +removeChargesFromRunes = true +stairJumpExhaustion = 0 +experienceByKillingPlayers = true +expFromPlayersLevelRange = 50 +distanceWeaponsDropOnGround = true + +-- Skull System +banLength = 2 * 24 * 60 * 60 +whiteSkullTime = 15 * 60 +redSkullTime = 2 * 24 * 60 * 60 +killsDayRedSkull = 4 +killsWeekRedSkull = 12 +killsMonthRedSkull = 35 +killsDayBanishment = 7 +killsWeekBanishment = 18 +killsMonthBanishment = 60 + +-- Connection Config +-- NOTE: maxPlayers set to 0 means no limit +ip = "127.0.0.1" +bindOnlyGlobalAddress = false +loginProtocolPort = 7171 +gameProtocolPort = 7172 +statusProtocolPort = 7171 +maxPlayers = 0 +motd = "Welcome to Tibianus!" +onePlayerOnlinePerAccount = true +allowClones = false +serverName = "Tibianus" +statusTimeout = 5000 +replaceKickOnLogin = true +maxPacketsPerSecond = -1 +autoStackCumulatives = false +moneyRate = 1 + +-- Deaths +-- NOTE: Leave deathLosePercent as -1 if you want to use the default +-- death penalty formula. For the old formula, set it to 10. For +-- no skill/experience loss, set it to 0. +deathLosePercent = 10 + +-- Houses +houseRentPeriod = "monthly" + +-- Item Usage +timeBetweenActions = 200 +timeBetweenExActions = 1000 + +-- Map +-- NOTE: set mapName WITHOUT .otbm at the end +mapName = "map" +mapAuthor = "CipSoft" + +-- MySQL +mysqlHost = "127.0.0.1" +mysqlUser = "root" +mysqlPass = "" +mysqlDatabase = "sabrehaven" +mysqlPort = 3306 +mysqlSock = "" + +-- Misc. +allowChangeOutfit = true +freePremium = false +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 8 + +-- Character Rooking +-- Level threshold is the level requirement to teleport players back to newbie town +teleportNewbies = true +newbieTownId = 11 +newbieLevelThreshold = 5 + +-- Rates +-- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml +rateExp = 1 +rateSkill = 3 +rateLoot = 2 +rateMagic = 2 +rateSpawn = 0 + +-- Monsters +deSpawnRange = 2 +deSpawnRadius = 50 + +-- Scripts +warnUnsafeScripts = true +convertUnsafeScripts = true + +-- Startup +-- NOTE: defaultPriority only works on Windows and sets process +-- priority, valid values are: "normal", "above-normal", "high" +defaultPriority = "high" +startupDatabaseOptimization = true + +-- Status server information +ownerName = "Erikas" +ownerEmail = "" +url = "https://tibianus.com/" +location = "Poland" diff --git a/app/SabrehavenServer/config.prod.lua b/app/SabrehavenServer/config.prod.lua new file mode 100644 index 0000000..a53910b --- /dev/null +++ b/app/SabrehavenServer/config.prod.lua @@ -0,0 +1,109 @@ +-- Combat settings +-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" +worldType = "pvp" +hotkeyAimbotEnabled = true +protectionLevel = 1 +pzLocked = 60000 +removeChargesFromRunes = true +stairJumpExhaustion = 0 +experienceByKillingPlayers = false +expFromPlayersLevelRange = 75 + +-- Skull System +banLength = 30 * 24 * 60 * 60 +whiteSkullTime = 15 * 60 +redSkullTime = 30 * 24 * 60 * 60 +killsDayRedSkull = 3 +killsWeekRedSkull = 5 +killsMonthRedSkull = 10 +killsDayBanishment = 6 +killsWeekBanishment = 10 +killsMonthBanishment = 20 + +-- Connection Config +-- NOTE: maxPlayers set to 0 means no limit +ip = "31.220.54.20" +bindOnlyGlobalAddress = false +loginProtocolPort = 7171 +gameProtocolPort = 7172 +statusProtocolPort = 7171 +maxPlayers = 1000 +motd = "Welcome to Sabrehaven!" +onePlayerOnlinePerAccount = true +allowClones = false +serverName = "Sabrehaven" +statusTimeout = 5000 +replaceKickOnLogin = true +maxPacketsPerSecond = -1 +autoStackCumulatives = false +moneyRate = 1 + +-- Deaths +-- NOTE: Leave deathLosePercent as -1 if you want to use the default +-- death penalty formula. For the old formula, set it to 10. For +-- no skill/experience loss, set it to 0. +deathLosePercent = 10 + +-- Houses +houseRentPeriod = "monthly" + +-- Item Usage +timeBetweenActions = 200 +timeBetweenExActions = 1000 + +-- Map +-- NOTE: set mapName WITHOUT .otbm at the end +mapName = "map" +mapAuthor = "CipSoft" + +-- MySQL +mysqlHost = "127.0.0.1" +mysqlUser = "forgottenserver" +mysqlPass = "DZpJ+UL+t5OgdC0LhxPaz4ae" +mysqlDatabase = "forgottenserver" +mysqlPort = 3306 +mysqlSock = "" + +-- Misc. +allowChangeOutfit = true +freePremium = true +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 4 +showMonsterLoot = false +blockHeight = false +dropItems = false + + +-- Character Rooking +-- Level threshold is the level requirement to teleport players back to newbie town +teleportNewbies = true +newbieTownId = 11 +newbieLevelThreshold = 5 + +-- Rates +-- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml +rateExp = 1 +rateSkill = 5 +rateLoot = 2 +rateMagic = 2 +rateSpawn = 0 + +-- Monsters +deSpawnRange = 2 +deSpawnRadius = 50 + +-- Scripts +warnUnsafeScripts = true +convertUnsafeScripts = true + +-- Startup +-- NOTE: defaultPriority only works on Windows and sets process +-- priority, valid values are: "normal", "above-normal", "high" +defaultPriority = "high" +startupDatabaseOptimization = true + +-- Status server information +ownerName = "Erikas" +ownerEmail = "e.kontenis@gmail.com" +url = "https://sabrehaven.com" +location = "France" \ No newline at end of file diff --git a/app/SabrehavenServer/data/XML/commands.xml b/app/SabrehavenServer/data/XML/commands.xml new file mode 100644 index 0000000..a000e63 --- /dev/null +++ b/app/SabrehavenServer/data/XML/commands.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/XML/groups.xml b/app/SabrehavenServer/data/XML/groups.xml new file mode 100644 index 0000000..11923c7 --- /dev/null +++ b/app/SabrehavenServer/data/XML/groups.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/SabrehavenServer/data/XML/outfits.xml b/app/SabrehavenServer/data/XML/outfits.xml new file mode 100644 index 0000000..fd35f8f --- /dev/null +++ b/app/SabrehavenServer/data/XML/outfits.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/XML/quests.xml b/app/SabrehavenServer/data/XML/quests.xml new file mode 100644 index 0000000..840be0d --- /dev/null +++ b/app/SabrehavenServer/data/XML/quests.xml @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/XML/stages.xml b/app/SabrehavenServer/data/XML/stages.xml new file mode 100644 index 0000000..7bbf9e2 --- /dev/null +++ b/app/SabrehavenServer/data/XML/stages.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/XML/vocations.xml b/app/SabrehavenServer/data/XML/vocations.xml new file mode 100644 index 0000000..dfdee2d --- /dev/null +++ b/app/SabrehavenServer/data/XML/vocations.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/actions/actions.xml b/app/SabrehavenServer/data/actions/actions.xml new file mode 100644 index 0000000..c31a15c --- /dev/null +++ b/app/SabrehavenServer/data/actions/actions.xml @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/actions/lib/actions.lua b/app/SabrehavenServer/data/actions/lib/actions.lua new file mode 100644 index 0000000..1544460 --- /dev/null +++ b/app/SabrehavenServer/data/actions/lib/actions.lua @@ -0,0 +1,20 @@ +function doDestroyItem(target) + if not target:isItem() then + return false + end + + local itemType = ItemType(target:getId()) + if not itemType:isDestroyable() then + return false + end + + if math.random(1,10) <= 3 then + target:transform(itemType:getDestroyTarget()) + target:decay() + target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) + else + target:getPosition():sendMagicEffect(CONST_ME_POFF) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_hymn.lua b/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_hymn.lua new file mode 100644 index 0000000..0a87948 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_hymn.lua @@ -0,0 +1,21 @@ +local config = { + [6087] = {storage = 1053, text = 'first', effect = CONST_ME_SOUND_GREEN}, + [6088] = {storage = 1054, text = 'second', effect = CONST_ME_SOUND_RED}, + [6089] = {storage = 1055, text = 'third', effect = CONST_ME_SOUND_YELLOW}, + [6090] = {storage = 1056, text = 'fourth', effect = CONST_ME_SOUND_BLUE} +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local playerPosition = player:getPosition() + local useItem = config[item.itemid] + if player:getStorageValue(useItem.storage) ~= 1 then + player:setStorageValue(useItem.storage, 1) + player:say("You have learned the ".. useItem.text .." part of a hymn.", TALKTYPE_MONSTER_SAY, false, 0, playerPosition) + playerPosition:sendMagicEffect(useItem.effect) + item:remove(1) + else + player:say("You already know the ".. useItem.text .." verse of the hymn.", TALKTYPE_MONSTER_SAY, false, 0, playerPosition) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_piano_teleport.lua b/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_piano_teleport.lua new file mode 100644 index 0000000..791a58a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/liberty_bay/cult_piano_teleport.lua @@ -0,0 +1,18 @@ +local storages = {1053, 1054, 1055, 1056} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local playerPosition = player:getPosition() + + for i = 1, #storages do + if player:getStorageValue(storages[i]) ~= 1 then + player:say("You have not learned all the verses of the hymn.", TALKTYPE_MONSTER_SAY, false, 0, toPosition) + playerPosition:sendMagicEffect(CONST_ME_POFF) + return true + end + end + + player:teleportTo(Position(32402, 32794, 9)) + player:say("You have sucessfully played the secret hymn of the cult.", TALKTYPE_MONSTER_SAY) + Game.sendMagicEffect({x = 32402, y = 32794, z = 9}, CONST_ME_SOUND_PURPLE) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/meriana_quest/pirate_map.lua b/app/SabrehavenServer/data/actions/scripts/meriana_quest/pirate_map.lua new file mode 100644 index 0000000..58dfd2c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/meriana_quest/pirate_map.lua @@ -0,0 +1,28 @@ +local maps = { + [17528] = "You have successfully read plan A.", + [17529] = "You have successfully read plan B.", + [17530] = "You have successfully read plan C." +} + +function onUse(player, item, fromPosition, target, toPosition) + local mapActionId = item:getActionId() + local map = maps[mapActionId] + if not map then + return false + end + + local playerPosition = player:getPosition() + if player:getStorageValue(17520) < 8 then + player:say("You shouldn't touch these maps because someone might see you.", TALKTYPE_MONSTER_SAY, false, 0, playerPosition) + return true + end + + if player:getStorageValue(mapActionId) ~= 1 then + player:say(map, TALKTYPE_MONSTER_SAY, false, 0, playerPosition) + player:setStorageValue(mapActionId, 1) + else + player:say("You have already read this map.", TALKTYPE_MONSTER_SAY, false, 0, playerPosition) + end + + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/meriana_quest/tortoise_egg.lua b/app/SabrehavenServer/data/actions/scripts/meriana_quest/tortoise_egg.lua new file mode 100644 index 0000000..b407e8a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/meriana_quest/tortoise_egg.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(17526) < os.time() then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a tortoise egg from Nargor.") + player:setStorageValue(17526, os.time() + 24 * 60 * 60) -- 24 hour + player:addItem(6125,1) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You shoud not take any more today.") + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/baking.lua b/app/SabrehavenServer/data/actions/scripts/misc/baking.lua new file mode 100644 index 0000000..bea5640 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/baking.lua @@ -0,0 +1,51 @@ +local ovens = { + 2535, 2537, 2539, 2541, 3510, 6355, 6357, 6359, 6361 +} + +local milestone = { + 1943, 1944, 1945, 1946 +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if item:getId() == 3603 then + if (target:getId() == 2524 or target:getId() == 2873) and target:getFluidType() == FLUID_WATER then + target:transform(target:getId(), FLUID_NONE) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3604, 1):decay() + else + Game.createItem(3604, 1, item:getPosition()):decay() + end + item:remove(1) + return true + end + elseif item:getId() == 3604 then + if table.contains(ovens, target:getId()) then + Game.createItem(3600, 1, target:getPosition()) + item:remove(1) + return true + end + elseif item:getId() == 6276 then + if table.contains(ovens, target:getId()) then + Game.createItem(6277, 1, target:getPosition()) + item:remove(1) + return true + end + elseif item:getId() == 3605 then + if table.contains(milestone, target:getId()) then + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3603, 1):decay() + else + Game.createItem(3603, 1, item:getPosition()):decay() + end + item:remove(1) + return true + end + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/birdcage.lua b/app/SabrehavenServer/data/actions/scripts/misc/birdcage.lua new file mode 100644 index 0000000..276a7ce --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/birdcage.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 1 and math.random(1, 100) <= 10 then + item:transform(2975, 0) + item:decay() + else + item:getPosition():sendMagicEffect(22) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/blueberry_bush.lua b/app/SabrehavenServer/data/actions/scripts/misc/blueberry_bush.lua new file mode 100644 index 0000000..4a40a50 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/blueberry_bush.lua @@ -0,0 +1,6 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:transform(3700, 1) + item:decay() + Game.createItem(3588, 3, fromPosition) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/botanist_container.lua b/app/SabrehavenServer/data/actions/scripts/misc/botanist_container.lua new file mode 100644 index 0000000..6699252 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/botanist_container.lua @@ -0,0 +1,30 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3874 and player:getStorageValue(305) == 1 then + item:transform(4868, 1) + target:getPosition():sendMagicEffect(10) + return true + elseif target:getId() == 3885 and player:getStorageValue(305) == 3 then + item:transform(4870, 1) + target:getPosition():sendMagicEffect(10) + return true + elseif target:getId() == 3878 and player:getStorageValue(305) == 5 then + item:transform(4869, 1) + target:getPosition():sendMagicEffect(10) + return true + elseif target:getId() == 5658 and player:getStorageValue(17535) == 1 then + if player:getStorageValue(17536) < os.time() then + item:transform(5937, 1) + target:getPosition():sendMagicEffect(10) + player:setStorageValue(17536, os.time() + 20 * 60 * 60) -- 20 hour + return true + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You just collected a fragile griffinclaw. At least wait for the rest of the plant to recover a bit before gathering more.") + return true + end + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/butterfly_conservation_kit.lua b/app/SabrehavenServer/data/actions/scripts/misc/butterfly_conservation_kit.lua new file mode 100644 index 0000000..83e96bc --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/butterfly_conservation_kit.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4992 and player:getStorageValue(304) == 1 then + target:getPosition():sendMagicEffect(17) + item:transform(4865, 1) + item:decay() + target:remove() + elseif target:getId() == 4993 and player:getStorageValue(304) == 3 then + target:getPosition():sendMagicEffect(17) + item:transform(4866, 1) + item:decay() + target:remove() + elseif target:getId() == 4991 and player:getStorageValue(304) == 5 then + target:getPosition():sendMagicEffect(17) + item:transform(4864, 1) + item:decay() + target:remove() + elseif target:getId() == 5013 and player:getStorageValue(304) == 5 then + target:getPosition():sendMagicEffect(17) + item:transform(5089, 1) + item:decay() + target:remove() + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/catch_fish.lua b/app/SabrehavenServer/data/actions/scripts/misc/catch_fish.lua new file mode 100644 index 0000000..fce7433 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/catch_fish.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid ~= 5553 then + return false + end + + if math.random(2) ~= 1 then + player:say("The golden fish escaped.", TALKTYPE_MONSTER_SAY) + return true + end + + player:say("You catch a golden fish in the bowl.", TALKTYPE_MONSTER_SAY) + item:transform(5929) + target:remove() + toPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/ceirons_waterskin.lua b/app/SabrehavenServer/data/actions/scripts/misc/ceirons_waterskin.lua new file mode 100644 index 0000000..e23aaf2 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/ceirons_waterskin.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 5662 and Game.isItemThere({x = 33024, y = 32672, z = 6}, 5662) then + item:transform(5939, 1) + target:getPosition():sendMagicEffect(2) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/ceremonial_ankh.lua b/app/SabrehavenServer/data/actions/scripts/misc/ceremonial_ankh.lua new file mode 100644 index 0000000..9d3ef92 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/ceremonial_ankh.lua @@ -0,0 +1,18 @@ +local blessings = { + {key = 101, value = 1, name = 'Wisdom of Solitude'}, + {key = 102, value = 1, name = 'Spark of the Phoenix'}, + {key = 103, value = 3, name = 'Fire of the Suns'}, + {key = 104, value = 1, name = 'Spiritual Shielding'}, + {key = 105, value = 1, name = 'Embrace of Tibia'} +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local result, bless = 'Received blessings:' + for i = 1, #blessings do + bless = blessings[i] + result = (player:getStorageValue(bless.key) == bless.value) and result .. '\n' .. bless.name or result + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 20 > result:len() and 'No blessings received.' or result) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/changegold.lua b/app/SabrehavenServer/data/actions/scripts/misc/changegold.lua new file mode 100644 index 0000000..cbfc299 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/changegold.lua @@ -0,0 +1,35 @@ +local config = { + [3031] = {changeTo = 3035}, + [3035] = {changeBack = 3031, changeTo = 3043}, + [3043] = {changeBack = 3035} +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local ring = player:getSlotItem(CONST_SLOT_RING) + if ring == nil or ring:getId() ~= 3007 then + return false + end + + local ringCharges = ring:getAttribute(ITEM_ATTRIBUTE_CHARGES) + local coin = config[item:getId()] + if coin.changeTo and item.type == 100 then + item:remove() + player:addItem(coin.changeTo, 1) + if ringCharges > 1 then + ring:setAttribute(ITEM_ATTRIBUTE_CHARGES,(ringCharges-1)) + else + ring:remove(1) + end + elseif coin.changeBack then + item:remove(1) + player:addItem(coin.changeBack, 100) + if ringCharges > 1 then + ring:setAttribute(ITEM_ATTRIBUTE_CHARGES,(ringCharges-1)) + else + ring:remove(1) + end + else + return false + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/chests.lua b/app/SabrehavenServer/data/actions/scripts/misc/chests.lua new file mode 100644 index 0000000..b119b1b --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/chests.lua @@ -0,0 +1,115 @@ +function onUse(player, item, fromPosition, target, toPosition) + local chestQuestNumber = item:getAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) + + if chestQuestNumber == 0 then + return false + end + + if player:getStorageValue(chestQuestNumber) > 0 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The " .. item:getName() .. " is empty.") + return true + end + + local playerCapacity = player:getFreeCapacity() + + if item:getSize() <= 0 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The chest is empty. This is a bug, report it to a gamemaster.") + return true + end + + local reward = item:getItem(0) + local stackable = reward:getType():isStackable() + local rewardName = reward:getName() + local rewardWeight = reward:getWeight() + + if stackable then + if reward:getCount() > 1 then + rewardName = reward:getCount() .. " " .. reward:getPluralName() + else + rewardName = reward:getName() + end + end + + if reward:getArticle():len() > 0 and reward:getCount() <= 1 then + rewardName = reward:getArticle() .. " " .. rewardName + end + + if rewardWeight > playerCapacity and not getPlayerFlagValue(player, layerFlag_HasInfiniteCapacity) then + local term = "it is" + if stackable and reward:getCount() > 1 then + term = "they are" + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You have found %s. Weighing %d.%02d oz %s too heavy.", rewardName, rewardWeight / 100, rewardWeight % 100, term)) + return true + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have found " .. rewardName .. ".") + + local attackAttribute = math.random(-2, 5) + local rewardClone = reward:clone() + if rewardClone:getType():getAttack() > 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_ATTACK, rewardClone:getType():getAttack() + attackAttribute) + local description = rewardClone:hasAttribute(ITEM_ATTRIBUTE_DESCRIPTION) and rewardClone:getAttribute(ITEM_ATTRIBUTE_DESCRIPTION) or rewardClone:getType():getDescription() + if description ~= nil and description ~= '' then + description = description .. ". " + end + + if attackAttribute == 5 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with perfect attack.") + else + if attackAttribute ~= 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with " .. attackAttribute .. " attack.") + end + end + end + + local defenseAttribute = math.random(-2, 5) + if rewardClone:getType():getDefense() > 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DEFENSE, rewardClone:getType():getDefense() + defenseAttribute) + local description = rewardClone:hasAttribute(ITEM_ATTRIBUTE_DESCRIPTION) and rewardClone:getAttribute(ITEM_ATTRIBUTE_DESCRIPTION) or rewardClone:getType():getDescription() + + if rewardClone:getType():getAttack() > 0 and attackAttribute ~= 0 then + if defenseAttribute == 5 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. " Enchanted with perfect defense.") + else + if defenseAttribute ~= 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. " Enchanted with " .. defenseAttribute .. " defense.") + end + end + else + if description ~= nil and description ~= '' then + description = description .. ". " + end + + if defenseAttribute == 5 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with perfect defense.") + else + if defenseAttribute ~= 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with " .. defenseAttribute .. " defense.") + end + end + end + end + + local armorAttribute = math.random(-1, 2) + if rewardClone:getType():getArmor() > 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_ARMOR, rewardClone:getType():getArmor() + armorAttribute) + local description = rewardClone:hasAttribute(ITEM_ATTRIBUTE_DESCRIPTION) and rewardClone:getAttribute(ITEM_ATTRIBUTE_DESCRIPTION) or rewardClone:getType():getDescription() + if description ~= nil and description ~= '' then + description = description .. ". " + end + + if armorAttribute == 2 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with perfect armor protection.") + else + if armorAttribute ~= 0 then + rewardClone:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, description .. "Enchanted with " .. armorAttribute .. " armor protection.") + end + end + end + + player:addItemEx(rewardClone, true) + player:setStorageValue(chestQuestNumber, 1) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/christmas_bundle.lua b/app/SabrehavenServer/data/actions/scripts/misc/christmas_bundle.lua new file mode 100644 index 0000000..fa1368d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/christmas_bundle.lua @@ -0,0 +1,50 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + + local bundleTypes = { + [6506] = { -- red christmas bundle + 6503 + }, + [6507] = { -- blue christmas bundle + 6504 + }, + [6508] = { -- green christmas bundle + 6502 + } + } + + local common = { + {6569, 15}, {3598, 20}, {3599, 10}, {3586, 10}, {3585, 5}, 6500, 6501, 6489, 6387 + } + + local targetItem = bundleTypes[item.itemid] + if not targetItem then + return true + end + + targetItem = common + + -- In case there's going to be more than one unique item per bundle + for i = 1, #bundleTypes[item.itemid] do + table.insert(targetItem, bundleTypes[item.itemid][i]) + end + + local rewards = {} + repeat + local count = 1 + local rand = math.random(#targetItem) + local gift = targetItem[rand] + if type(gift) == "table" then + count = gift[2] + gift = gift[1] + end + rewards[#rewards + 1] = {gift, count} + table.remove(targetItem, rand) + until #rewards == 7 + + for i = 1, #rewards do + player:addItem(rewards[i][1], rewards[i][2]) + end + item:remove(1) + fromPosition:sendMagicEffect(CONST_ME_GIFT_WRAPS) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/christmas_card.lua b/app/SabrehavenServer/data/actions/scripts/misc/christmas_card.lua new file mode 100644 index 0000000..dfaac29 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/christmas_card.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) + item:getPosition():sendMonsterSay("Merry Christmas, " .. player:getName() .. "!") + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/closed_trap.lua b/app/SabrehavenServer/data/actions/scripts/misc/closed_trap.lua new file mode 100644 index 0000000..5f50e44 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/closed_trap.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if Tile(item:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then + item:getPosition():sendMagicEffect(3) + else + item:transform(3482, 1) + item:decay() + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/concentrated_demonic_blood.lua b/app/SabrehavenServer/data/actions/scripts/misc/concentrated_demonic_blood.lua new file mode 100644 index 0000000..7319f95 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/concentrated_demonic_blood.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + item:getPosition():sendMagicEffect(CONST_ME_DRAWBLOOD) + item:transform(2874, math.random(10, 11)) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/cornucopia.lua b/app/SabrehavenServer/data/actions/scripts/misc/cornucopia.lua new file mode 100644 index 0000000..572952d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/cornucopia.lua @@ -0,0 +1,61 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 95 then + item:getPosition():sendMagicEffect(19) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + else + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + end + else + item:getPosition():sendMagicEffect(19) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + else + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + end + item:transform(3592, 1) + item:decay() + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/crowbar.lua b/app/SabrehavenServer/data/actions/scripts/misc/crowbar.lua new file mode 100644 index 0000000..7064de6 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/crowbar.lua @@ -0,0 +1,36 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4848 and player:getStorageValue(297) == 0 then + player:setStorageValue(297, 1) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 1) + target:decay() + return true + elseif target:getId() == 4848 and player:getStorageValue(297) == 1 then + player:setStorageValue(297, 2) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 1) + target:decay() + return true + elseif target:getId() == 4848 and player:getStorageValue(297) == 2 then + player:setStorageValue(297, 3) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 3) + target:decay() + return true + elseif target:getId() == 1628 and toPosition.x == 32680 and toPosition.y == 32083 and toPosition.z == 09 then + Game.transformItemOnMap({x = 32680, y = 32083, z = 09}, 1628, 1630) + return true + elseif target:getId() == 3501 and toPosition.x == 32013 and toPosition.y == 31562 and toPosition.z == 04 and player:getStorageValue(228) == 1 then + Game.sendMagicEffect({x = 32013, y = 31562, z = 04}, 15) + player:setStorageValue(228, 2) + return true + elseif target:getId() == 3501 and toPosition.x == 32013 and toPosition.y == 31562 and toPosition.z == 04 then + Game.sendMagicEffect({x = 32013, y = 31562, z = 04}, 3) + return true + end + return doDestroyItem(target) +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/dice.lua b/app/SabrehavenServer/data/actions/scripts/misc/dice.lua new file mode 100644 index 0000000..0dcc278 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/dice.lua @@ -0,0 +1,31 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local playerPosition = player:getPosition() + local afrompos = {x=playerPosition.x - 1, y=playerPosition.y - 1, z=playerPosition.z} + local atopos = {x=playerPosition.x + 1, y=playerPosition.y + 1, z=playerPosition.z} + for xa = afrompos.x,atopos.x do + for ya = afrompos.y,atopos.y do + for za = afrompos.z,atopos.z do + local npos = {x = xa,y = ya,z = za} + local depotItem = Tile(npos):getItemByType(ITEM_TYPE_DEPOT) + if depotItem ~= nil then + player:sendCancelMessage("You cannot use this object.") + return true + end + end + end + end + + local dicePosition = item:getPosition() + local value = math.random(6) + local isInGhostMode = player:isInGhostMode() + + dicePosition:sendMagicEffect(CONST_ME_CRAPS, isInGhostMode and player) + + local spectators = Game.getSpectators(dicePosition, false, true, 3, 3) + for i = 1, #spectators do + player:say(player:getName() .. " rolled a " .. value .. ".", TALKTYPE_MONSTER_SAY, isInGhostMode, spectators[i], dicePosition) + end + + item:transform(5791 + value) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/distilling_rum.lua b/app/SabrehavenServer/data/actions/scripts/misc/distilling_rum.lua new file mode 100644 index 0000000..55f6437 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/distilling_rum.lua @@ -0,0 +1,20 @@ +local distillingMachines = { + [5468] = 5512, + [5469] = 5513 +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + local machine = distillingMachines[target:getId()] + if machine then + target:transform(machine, 1) + target:decay() + item:remove(1) + return true + end + + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/djinn_quest_pass.lua b/app/SabrehavenServer/data/actions/scripts/misc/djinn_quest_pass.lua new file mode 100644 index 0000000..9d0e134 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/djinn_quest_pass.lua @@ -0,0 +1,26 @@ +local greenDjinnItem = 6549 +local blueDjinnItem = 6551 + +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(278) > 1 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have already finished one of the djinn quest or your quest is in-progress.") + return true + end + + if item:getId() == greenDjinnItem then + player:setStorageValue(278, 3) + player:setStorageValue(286, 3) + player:setStorageValue(287, 3) + player:setStorageValue(288, 3) + elseif item:getId() == blueDjinnItem then + player:setStorageValue(278, 2) + player:setStorageValue(280, 2) + player:setStorageValue(281, 2) + player:setStorageValue(282, 2) + player:setStorageValue(283, 3) + end + + item:getPosition():sendMagicEffect(3) + item:remove() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/dolls.lua b/app/SabrehavenServer/data/actions/scripts/misc/dolls.lua new file mode 100644 index 0000000..a5487d3 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/dolls.lua @@ -0,0 +1,74 @@ +local dolls = { + [5080] = {"Hug me."}, + [5668] = { + "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!" + }, + [5791] = { + "Fchhhhhh!", + "Zchhhhhh!", + "Grooaaaaar*cough*", + "Aaa... CHOO!", + "You... will.... burn!!" + }, + [6511] = { + "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." + } +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local sounds = dolls[item.itemid] + if not sounds then + return false + end + + if fromPosition.x == CONTAINER_POSITION then + fromPosition = player:getPosition() + end + + local random = math.random(#sounds) + local sound = sounds[random] + if item.itemid == 5791 then + if random == 3 then + fromPosition:sendMagicEffect(CONST_ME_POFF) + elseif random == 4 then + fromPosition:sendMagicEffect(CONST_ME_FIREAREA) + elseif random == 5 then + doTargetCombatHealth(0, player, COMBAT_PHYSICALDAMAGE, -1, -1, CONST_ME_EXPLOSIONHIT) + end + + if configManager.getNumber(configKeys.CLIENT_VERSION) >= 790 then + item:transform(6566) + item:decay() + end + elseif item.itemid == 5668 then + fromPosition:sendMagicEffect(CONST_ME_MAGIC_RED) + item:transform(item.itemid + 1) + item:decay() + elseif item.itemid == 5080 then + if configManager.getNumber(configKeys.CLIENT_VERSION) >= 790 then + item:transform(6568) + item:decay() + end + elseif item.itemid == 6511 then + item:transform(6567) + item:decay() + end + + sound = sound:gsub('|PLAYERNAME|', player:getName()) + player:say(sound, TALKTYPE_MONSTER_SAY, false, 0, fromPosition) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/doors.lua b/app/SabrehavenServer/data/actions/scripts/misc/doors.lua new file mode 100644 index 0000000..abd1654 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/doors.lua @@ -0,0 +1,275 @@ +local lockedDoors = { + 1628, 1631, 1650, 1653, 1668, 1671, 1682, 1691, 5006, 5007, 5097, 5106, 5115, 5124, 5133, 5136, 5139, 5142, 5277, 5280, 5732, 5735, 6191, 6194, 6248, 6251 +} + +local closedNormalDoors = { + [1629] = 1630, + [1632] = 1633, + [1638] = 1639, + [1640] = 1641, + [1651] = 1652, + [1654] = 1655, + [1656] = 1657, + [1658] = 1659, + [1669] = 1670, + [1672] = 1673, + [1683] = 1684, + [1685] = 1686, + [1692] = 1693, + [1694] = 1695, + [4912] = 4911, + [4913] = 4914, + [5082] = 5083, + [5084] = 5085, + [2177] = 2178, + [2179] = 2180, + [5098] = 5099, + [5100] = 5101, + [5107] = 5108, + [5109] = 5110, + [5116] = 5117, + [5118] = 5119, + [5125] = 5126, + [5127] = 5128, + [5134] = 5135, + [5137] = 5138, + [5140] = 5141, + [5143] = 5144, + [5278] = 5279, + [5281] = 5282, + [5283] = 5284, + [5285] = 5286, + [5514] = 5515, + [5516] = 5517, + [5733] = 5734, + [5736] = 5737, + [6192] = 6193, + [6195] = 6196, + [6197] = 6198, + [6199] = 6200, + [6249] = 6250, + [6252] = 6253, + [6254] = 6255, + [6256] = 6257, +} + +local openVerticalDoors = { + [1630] = 1629, + [1639] = 1638, + [1643] = 1642, + [1647] = 1646, + [1652] = 1651, + [1657] = 1656, + [1661] = 1660, + [1665] = 1664, + [1670] = 1669, + [1675] = 1674, + [1679] = 1678, + [1693] = 1692, + [1695] = 1694, + [1697] = 1696, + [1699] = 1698, + [4914] = 4913, + [5083] = 5082, + [2178] = 2177, + [5108] = 5107, + [5110] = 5109, + [5112] = 5111, + [5114] = 5113, + [5126] = 5125, + [5128] = 5127, + [5130] = 5129, + [5132] = 5131, + [5141] = 5140, + [5144] = 5143, + [5282] = 5281, + [5284] = 5283, + [5288] = 5287, + [5292] = 5291, + [5515] = 5514, + [5737] = 5736, + [5748] = 5749, + [6193] = 6192, + [6198] = 6197, + [6202] = 6201, + [6206] = 6205, + [6250] = 6249, + [6255] = 6254, + [6259] = 6258, + [6263] = 6262, +} + +local openHorizontalDoors = { + [1633] = 1632, + [1641] = 1640, + [1645] = 1644, + [1649] = 1648, + [1655] = 1654, + [1659] = 1658, + [1663] = 1662, + [1667] = 1666, + [1673] = 1672, + [1677] = 1676, + [1681] = 1680, + [1684] = 1683, + [1686] = 1685, + [1688] = 1687, + [1690] = 1689, + [4911] = 4912, + [5085] = 5084, + [2180] = 2179, + [5099] = 5098, + [5101] = 5100, + [5103] = 5102, + [5105] = 5104, + [5117] = 5116, + [5119] = 5118, + [5121] = 5120, + [5123] = 5122, + [5135] = 5134, + [5138] = 5137, + [5279] = 5278, + [5286] = 5285, + [5290] = 5289, + [5294] = 5293, + [5517] = 5516, + [5734] = 5733, + [5746] = 5745, + [6196] = 6195, + [6200] = 6199, + [6204] = 6203, + [6208] = 6207, + [6253] = 6252, + [6257] = 6256, + [6261] = 6260, + [6265] = 6264, +} + +local levelDoors = { + [1646] = 1647, + [1648] = 1649, + [1664] = 1665, + [1666] = 1667, + [1678] = 1679, + [1680] = 1681, + [1687] = 1688, + [1696] = 1697, + [5102] = 5103, + [5111] = 5112, + [5120] = 5121, + [5129] = 5130, + [5291] = 5292, + [5293] = 5294, + [6205] = 6206, + [6207] = 6208, + [6262] = 6263, + [6264] = 6265, +} + +local questDoors = { + [1642] = 1643, + [1644] = 1645, + [1660] = 1661, + [1662] = 1663, + [1674] = 1675, + [1676] = 1677, + [1689] = 1690, + [1698] = 1699, + [5104] = 5105, + [5113] = 5114, + [5122] = 5123, + [5131] = 5132, + [5287] = 5288, + [5289] = 5290, + [5745] = 5746, + [5749] = 5748, + [6201] = 6202, + [6203] = 6204, + [6258] = 6259, + [6260] = 6261, +} + +local passthrough = { + [2334] = 2335, + [2335] = 2334, + [2336] = 2337, + [2337] = 2336, + [2338] = 2339, + [2339] = 2338, + [2340] = 2341, + [2341] = 2340, +} + +function onUse(player, item, fromPosition, target, toPosition) + if table.contains(lockedDoors, item:getId()) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") + return true + end + + local door = closedNormalDoors[item:getId()] + if door then + item:transform(door, 1) + item:decay() + return true + end + + door = openVerticalDoors[item:getId()] + if door then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(1, 0, 0), true) + end + item:transform(door, 1) + item:decay() + return true + end + + door = openHorizontalDoors[item:getId()] + if door then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(0, 1, 0), true) + end + item:transform(door, 1) + item:decay() + return true + end + + door = levelDoors[item:getId()] + if door then + if player:getLevel() < item:getAttribute(ITEM_ATTRIBUTE_DOORLEVEL) then + player:sendTextMessage(MESSAGE_INFO_DESCR, item:getType():getDescription() .. ".") + return true + end + + player:teleportTo(item:getPosition(), true) + item:transform(door, 1) + item:decay() + return true + end + + door = questDoors[item:getId()] + if door then + local questNumber = item:getAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER) + local questValue = item:getAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE) + if questNumber > 0 then + if player:getStorageValue(questNumber) ~= questValue then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") + return true + end + end + + player:teleportTo(item:getPosition(), true) + item:transform(door, 1) + item:decay() + return true + end + + door = passthrough[item:getId()] + if door then + item:transform(door, 1) + item:decay() + return true + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/ectoplasm_container.lua b/app/SabrehavenServer/data/actions/scripts/misc/ectoplasm_container.lua new file mode 100644 index 0000000..19838bb --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/ectoplasm_container.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4094 then + item:transform(4853, 1) + item:decay() + target:getPosition():sendMagicEffect(12) + item:getPosition():sendMagicEffect(13) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/fire_bug.lua b/app/SabrehavenServer/data/actions/scripts/misc/fire_bug.lua new file mode 100644 index 0000000..dd57a02 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/fire_bug.lua @@ -0,0 +1,32 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getStorageValue(17520) == 5 and target.itemid == 5601 then + player:setStorageValue(17520, 6) + toPosition:sendMagicEffect(CONST_ME_FIREAREA) + return true + end + + local random = math.random(10) + if random >= 4 then --success 6% chance + if target.itemid == 5465 then --Burn Sugar Cane + toPosition:sendMagicEffect(CONST_ME_FIREAREA) + target:transform(5464) + target:decay() + elseif target.itemid == 3514 then --Light Up empty coal basins + toPosition:sendMagicEffect(CONST_ME_HITBYFIRE) + target:transform(3513) + elseif target.itemid == 2114 then --Light Up empty coal basins + toPosition:sendMagicEffect(CONST_ME_HITBYFIRE) + target:transform(2113) + end + elseif random == 2 then --it remove the fire bug 2% chance + item:remove(1) + toPosition:sendMagicEffect(CONST_ME_POFF) + elseif random == 1 then --it explode on the user 1% chance + doTargetCombatHealth(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 dont get removed 3% chance + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/fireworks_rocket.lua b/app/SabrehavenServer/data/actions/scripts/misc/fireworks_rocket.lua new file mode 100644 index 0000000..8f59c8e --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/fireworks_rocket.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if fromPosition.x ~= CONTAINER_POSITION then + fromPosition:sendMagicEffect(math.random(CONST_ME_FIREWORK_YELLOW, CONST_ME_FIREWORK_BLUE)) + else + local pos = player:getPosition() + pos:sendMagicEffect(CONST_ME_HITBYFIRE) + pos:sendMagicEffect(CONST_ME_EXPLOSIONAREA) + player:say("Ouch! Rather place it on the ground next time.", TALKTYPE_MONSTER_SAY) + if (player:getHealth() > 10) then + player:addHealth(-10) + end + end + + item:remove() + + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/fishing_rod.lua b/app/SabrehavenServer/data/actions/scripts/misc/fishing_rod.lua new file mode 100644 index 0000000..b061f17 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/fishing_rod.lua @@ -0,0 +1,36 @@ +local water = { + 4597, 4598, 4599, 4600, 4601, 4602, + 4609, 4610, 4611, 4612, 4613, 4614, + 4615, 4616, 4617, 4618, 4619, 4620, + 622 +} + +local fishableWater = { + 4597, 4598, 4599, 4600, 4601, 4602 +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if not table.contains(water, target:getId()) then + return false + end + + if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then + player:addSkillTries(SKILL_FISHING, 1) + if math.random(1, 100) <= math.min(math.max(10 + (player:getEffectiveSkillLevel(SKILL_FISHING) - 10) * 0.597, 10), 50) then + player:addItem(3578, 1) + + if target:getId() ~= 622 then + target:transform(4609, 1) + end + + target:decay() + end + end + + target:getPosition():sendMagicEffect(2) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/fluids.lua b/app/SabrehavenServer/data/actions/scripts/misc/fluids.lua new file mode 100644 index 0000000..76a02ce --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/fluids.lua @@ -0,0 +1,120 @@ +local drunk = Condition(CONDITION_DRUNK) +drunk:setParameter(CONDITION_PARAM_TICKS, 60000) + +local poison = Condition(CONDITION_POISON) +poison:setTiming(100) + +local messages = { + [FLUID_WATER] = "Gulp.", + [FLUID_WINE] = "Aah...", + [FLUID_BEER] = "Aah...", + [FLUID_MUD] = "Gulp.", + [FLUID_BLOOD] = "Gulp.", + [FLUID_SLIME] = "Urgh!", + [FLUID_OIL] = "Gulp.", + [FLUID_URINE] = "Urgh!", + [FLUID_MILK] = "Mmmh.", + [FLUID_MANAFLUID] = "Aaaah...", + [FLUID_LIFEFLUID] = "Aaaah...", + [FLUID_LEMONADE] = "Mmmh.", + [FLUID_RUM] = "Aah...", + [FLUID_COCONUTMILK] = "Mmmh.", + [FLUID_FRUITJUICE] = "Mmmh." +} + +function onUse(player, item, fromPosition, target, toPosition) + local targetItemType = ItemType(target:getId()) + if targetItemType and targetItemType:isFluidContainer() then + if target:getFluidType() == 0 and item:getFluidType() ~= 0 then + target:transform(target:getId(), item:getFluidType()) + item:transform(item:getId(), 0) + return true + elseif target:getFluidType() ~= 0 and item:getFluidType() == 0 then + player:sendCancelMessage("You cannot use this object.") + return true + end + end + + if (configManager.getBoolean(configKeys.UH_TRAP)) then + local tile = Tile(toPosition) + local creature = tile:getBottomCreature() + if creature and creature:isPlayer() then + target = creature + end + else + -- monsters do not use mana also I do not know if you can use life fluid on monsters + -- if you can just want to use life fluids on monster then change isPlayer to isCreature + target = target:isPlayer() and target + end + + if target:isCreature() and target:getPlayer() ~= nil then + if item:getFluidType() == FLUID_NONE then + player:sendCancelMessage("It is empty.") + else + local self = target == player + if self and item:getFluidType() == FLUID_BEER or item:getFluidType() == FLUID_WINE or item:getFluidType() == FLUID_RUM then + player:addCondition(drunk) + elseif self and item:getFluidType() == FLUID_SLIME then + player:addCondition(poison) + elseif item:getFluidType() == FLUID_MANAFLUID then + target:addMana(math.random(50, 100)) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + elseif item:getFluidType() == FLUID_LIFEFLUID then + target:addHealth(math.random(25, 50)) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + end + + if not self then + if item:getFluidType() ~= FLUID_MANAFLUID and item:getFluidType() ~= FLUID_LIFEFLUID then + if toPosition.x == CONTAINER_POSITION then + toPosition = player:getPosition() + end + Game.createItem(2886, item:getFluidType(), toPosition):decay() + return true + end + end + + local message = messages[item:getFluidType()] + if message then + target:say(message, TALKTYPE_MONSTER_SAY) + else + target:say("Gulp.", TALKTYPE_MONSTER_SAY) + end + + if player:getStorageValue(17742) ~= 1 then + item:transform(item:getId(), FLUID_NONE) + else + item:remove() + end + end + else + if toPosition.x == CONTAINER_POSITION then + toPosition = player:getPosition() + end + + local tile = Tile(toPosition) + if not tile then + return false + end + + if item:getFluidType() ~= FLUID_NONE and tile:hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) then + return false + end + + local fluidSource = targetItemType and targetItemType:getFluidSource() or FLUID_NONE + if fluidSource ~= FLUID_NONE then + item:transform(item:getId(), fluidSource) + elseif item:getFluidType() == FLUID_NONE then + player:sendTextMessage(MESSAGE_STATUS_SMALL, "It is empty.") + else + if item:getFluidType() == FLUID_BLOOD and target:getActionId() == 17639 then + doRelocate({x = 32791, y = 32334, z = 09}, {x = 32791, y = 32332, z = 10}) + Position({x = 32791, y = 32332, z = 10}):sendMonsterSay("Muahahahaha...") + end + + Game.createItem(2886, item.type, toPosition):decay() + item:transform(item:getId(), 0) + end + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/food.lua b/app/SabrehavenServer/data/actions/scripts/misc/food.lua new file mode 100644 index 0000000..e176edd --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/food.lua @@ -0,0 +1,73 @@ +local foods = { + [3250] = "Crunch.", -- the carrot of doom + [3577] = "Munch.", -- meat + [3578] = "Munch.", -- fish + [3579] = "Mmmm.", -- salmon + [3580] = "Munch.", -- fish + [3581] = "Gulp.", -- shrimp + [3582] = "Chomp.", -- ham + [3583] = "Chomp.", -- dragon ham + [3584] = "Yum.", -- pear + [3585] = "Yum.", -- red apple + [3586] = "Yum.", -- orange + [3587] = "Yum.", -- banana + [3588] = "Yum.", -- blueberry + [3589] = "Slurp.", -- coconut + [3590] = "Yum.", -- cherry + [3591] = "Yum.", -- strawberry + [3592] = "Yum.", -- grapes + [3593] = "Yum.", -- melon + [3594] = "Munch.", -- pumpkin + [3595] = "Crunch.", -- carrot + [3596] = "Munch.", -- tomato + [3597] = "Crunch.", -- corncob + [3598] = "Crunch.", -- cookie + [3599] = "Munch.", -- candy cane + [3600] = "Crunch.", -- bread + [3601] = "Crunch.", -- roll + [3602] = "Crunch.", -- brown bread + [3606] = "Gulp.", -- egg + [3607] = "Smack.", -- cheese + [3723] = "Munch.", -- white mushroom + [3724] = "Munch.", -- red mushroom + [3725] = "Munch.", -- brown mushroom + [3726] = "Munch.", -- orange mushroom + [3727] = "Munch.", -- wood mushroom + [3728] = "Munch.", -- dark mushroom + [3729] = "Munch.", -- some mushrooms + [3730] = "Munch.", -- some mushrooms + [3731] = "Munch.", -- fire mushroom + [3732] = "Munch.", -- green mushroom + [5096] = "Yum.", -- mango + [5678] = "Gulp.", -- tortoise egg + [6125] = "Gulp.", -- tortoise egg from Nargor + [6277] = "Mmmm.", -- cake + [6278] = "Mmmm.", -- cake + [6392] = "Mmmm.", -- valentine's cake + [6393] = "Mmmm.", -- cream cake + [6500] = "Mmmm.", -- gingerbreadman + [6541] = "Gulp.", -- coloured egg + [6542] = "Gulp.", -- coloured egg + [6543] = "Gulp.", -- coloured egg + [6544] = "Gulp.", -- coloured egg + [6545] = "Gulp.", -- coloured egg + [6569] = "Mmmm.", -- candy + [6574] = "Mmmm.", -- bar of chocolate +} + +function onUse(player, item, fromPosition, target, toPosition) + local itemType = ItemType(item:getId()) + local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + if condition and math.floor(condition:getTicks() / 1000 + (itemType:getNutrition() * 12)) >= 1200 then + player:sendTextMessage(MESSAGE_STATUS_SMALL, "You are full.") + else + player:feed(itemType:getNutrition() * 12) + item:remove(1) + + if configManager.getNumber(configKeys.CLIENT_VERSION) >= 790 then + player:say(foods[item:getId()] or "Munch.", TALKTYPE_MONSTER_SAY) + end + end + + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/furniture_parcels.lua b/app/SabrehavenServer/data/actions/scripts/misc/furniture_parcels.lua new file mode 100644 index 0000000..0f240ad --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/furniture_parcels.lua @@ -0,0 +1,65 @@ +local parcels = { + [2775] = 2374, + [2776] = 2378, + [2777] = 2358, + [2778] = 2382, + [2779] = 2366, + [2780] = 2418, + [2781] = 2422, + [2782] = 2319, + [2783] = 2316, + [2784] = 2315, + [2785] = 2314, + [2786] = 2346, + [2787] = 2349, + [2788] = 2351, + [2789] = 2433, + [2790] = 2441, + [2791] = 2449, + [2792] = 2524, + [2793] = 2523, + [2794] = 2483, + [2795] = 2465, + [2796] = 2976, + [2797] = 2979, + [2798] = 2934, + [2799] = 3485, + [2800] = 2998, + [2801] = 2445, + [2802] = 2025, + [2803] = 2029, + [2804] = 2030, + [2805] = 2904, + [2806] = 3510, + [2807] = 2959, + [2808] = 2963, + [2809] = 2426, + [2810] = 2352, + [2811] = 2982, + [2812] = 2986, + [5086] = 5046, + [5087] = 5055, + [5088] = 5056, + [6114] = 6111, + [6115] = 6109, + [6371] = 6355, + [6372] = 6367, +} + +function onUse(player, item, fromPosition, target, toPosition) + local parcel = parcels[item:getId()] + if not parcel then + return false + end + + if not item:getParent():isTile() then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + elseif not Tile(fromPosition):getHouse() then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + else + item:transform(parcel) + item:getPosition():sendMagicEffect(CONST_ME_POFF) + end + + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/helmet_of_the_ancients.lua b/app/SabrehavenServer/data/actions/scripts/misc/helmet_of_the_ancients.lua new file mode 100644 index 0000000..563119f --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/helmet_of_the_ancients.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3030 then + item:getPosition():sendMagicEffect(14) + item:transform(3230, 1) + item:decay() + target:remove(1) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/ice_pick.lua b/app/SabrehavenServer/data/actions/scripts/misc/ice_pick.lua new file mode 100644 index 0000000..81a0e3c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/ice_pick.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4994 and player:getStorageValue(306) == 1 and player:getStorageValue(307) == 0 then + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(4837, 1) + else + Game.createItem(4837, 1, item:getPosition()) + end + target:getPosition():sendMagicEffect(2) + player:setStorageValue(307, 1) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/instruments.lua b/app/SabrehavenServer/data/actions/scripts/misc/instruments.lua new file mode 100644 index 0000000..8c71a80 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/instruments.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() >= 2948 and item:getId() <= 2950 or item:getId() >= 2952 and item:getId() <= 2958 or + item:getId() >= 2963 and item:getId() <= 2964 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + elseif (item:getId() >= 2959 and item:getId() <= 2962 or item:getId() == 2965) and math.random(1, 100) <= 50 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + elseif item:getId() >= 2959 and item:getId() <= 2962 or item:getId() == 2965 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_PURPLE) + elseif item:getId() == 3219 then + item:getPosition():sendMagicEffect(19) + elseif item:getId() == 5786 then + Game.createMonster("wolf", player:getPosition()) + local random = math.random(1,10) + if random > 3 then -- destroy 70% chance + item:getPosition():sendMagicEffect(CONST_ME_SOUND_RED) + item:remove(1) + else + item:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) + end + elseif item:getId() == 6572 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + item:getPosition():sendMonsterSay("TOOOOOOT") + item:transform(6573) + item:decay() + end + + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/juice_squeezer.lua b/app/SabrehavenServer/data/actions/scripts/misc/juice_squeezer.lua new file mode 100644 index 0000000..9827692 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/juice_squeezer.lua @@ -0,0 +1,9 @@ +local fruits = {3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3595, 3596, 5096} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if isInArray(fruits, target.itemid) and player:removeItem(2874, 1, 0) then + target:remove(1) + player:addItem(2874, target.itemid == 3589 and 14 or 15) + return true + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/key.lua b/app/SabrehavenServer/data/actions/scripts/misc/key.lua new file mode 100644 index 0000000..a411be5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/key.lua @@ -0,0 +1,104 @@ +local closedDoors = { + [1628] = 1630, + [1629] = 1628, + [1631] = 1633, + [1632] = 1631, + [1650] = 1652, + [1651] = 1650, + [1653] = 1655, + [1654] = 1653, + [1668] = 1670, + [1669] = 1668, + [1671] = 1673, + [1672] = 1671, + [1682] = 1684, + [1683] = 1682, + [1691] = 1693, + [1692] = 1691, + [5097] = 5099, + [5098] = 5097, + [5106] = 5108, + [5107] = 5106, + [5115] = 5117, + [5116] = 5115, + [5124] = 5126, + [5125] = 5124, + [5133] = 5135, + [5134] = 5133, + [5136] = 5138, + [5137] = 5136, + [5139] = 5141, + [5140] = 5139, + [5142] = 5144, + [5143] = 5142, + [5277] = 5279, + [5278] = 5277, + [5280] = 5282, + [5281] = 5280, + [5732] = 5734, + [5733] = 5732, + [5735] = 5737, + [5736] = 5735, + [6191] = 6193, + [6192] = 6191, + [6194] = 6196, + [6195] = 6194, + [6248] = 6250, + [6249] = 6248, + [6251] = 6253, + [6252] = 6251, +} + +local openDoors = { + [1630] = 1628, + [1633] = 1631, + [1652] = 1650, + [1655] = 1653, + [1670] = 1668, + [1673] = 1671, + [1684] = 1682, + [1693] = 1691, + [5099] = 5097, + [5108] = 5106, + [5117] = 5115, + [5126] = 5124, + [5135] = 5133, + [5138] = 5136, + [5141] = 5139, + [5144] = 5142, + [5279] = 5277, + [5282] = 5280, + [5734] = 5732, + [5737] = 5735, + [6193] = 6191, + [6196] = 6194, + [6250] = 6248, + [6253] = 6251, +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + local door = closedDoors[target:getId()] + if not door then + door = openDoors[target:getId()] + end + + if not door then + return false + end + + local keyNumber = item:getAttribute(ITEM_ATTRIBUTE_KEYNUMBER) + local keyHoleNumber = target:getAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER) + + if keyHoleNumber == 0 or keyNumber ~= keyHoleNumber then + player:sendCancelMessage("The key does not match.") + return true + end + + target:transform(door) + target:decay() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/knife.lua b/app/SabrehavenServer/data/actions/scripts/misc/knife.lua new file mode 100644 index 0000000..ab9ba8d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/knife.lua @@ -0,0 +1,19 @@ +local fruits = {3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3595, 3596, 5096} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3594 then + target:transform(2977, 1) + target:decay() + return true + elseif isInArray(fruits, target:getId()) and player:removeItem(6277, 1) then + target:remove(1) + player:addItem(6278, 1) + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/letter_bag.lua b/app/SabrehavenServer/data/actions/scripts/misc/letter_bag.lua new file mode 100644 index 0000000..ecfa1a7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/letter_bag.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 3221 and toPosition.x == 31948 and toPosition.y == 31711 and toPosition.z == 06 then + item:transform(2859, 1) + item:decay() + player:setStorageValue(244, 2) + Game.sendMagicEffect({x = 31948, y = 31711, z = 06}, 19) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/lottery_ticket.lua b/app/SabrehavenServer/data/actions/scripts/misc/lottery_ticket.lua new file mode 100644 index 0000000..25fa5a5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/lottery_ticket.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if math.random(100) == 1 then + player:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW) + player:say("Congratulations! You won a prize!", TALKTYPE_MONSTER_SAY) + item:transform(5958) + else + player:getPosition():sendMagicEffect(CONST_ME_POFF) + player:say("Sorry, but you drew a blank.", TALKTYPE_MONSTER_SAY) + item:remove(1) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/machete.lua b/app/SabrehavenServer/data/actions/scripts/misc/machete.lua new file mode 100644 index 0000000..7e77d39 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/machete.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3696 then + target:transform(3695, 1) + target:decay() + return true + elseif target:getId() == 3702 then + target:transform(3701, 1) + target:decay() + return true + elseif target:getId() == 2130 then + target:remove() + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/open_trap.lua b/app/SabrehavenServer/data/actions/scripts/misc/open_trap.lua new file mode 100644 index 0000000..e7457b7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/open_trap.lua @@ -0,0 +1,6 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:transform(3481, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/party_cake.lua b/app/SabrehavenServer/data/actions/scripts/misc/party_cake.lua new file mode 100644 index 0000000..edc712e --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/party_cake.lua @@ -0,0 +1,6 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(CONST_ME_POFF) + item:transform(6278, 1) + item:getPosition():sendMonsterSay(player:getName() .. " blew out the candle.") + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/party_hat.lua b/app/SabrehavenServer/data/actions/scripts/misc/party_hat.lua new file mode 100644 index 0000000..bf5a485 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/party_hat.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local slot = player:getSlotItem(CONST_SLOT_HEAD) + if slot and item.uid == slot.uid then + player:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) + return true + end + + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/pick.lua b/app/SabrehavenServer/data/actions/scripts/misc/pick.lua new file mode 100644 index 0000000..89d7662 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/pick.lua @@ -0,0 +1,89 @@ +local pitsOfInfernoLava = { + Position(32808, 32336, 11), + Position(32809, 32336, 11), + Position(32810, 32336, 11), + Position(32808, 32334, 11), + Position(32807, 32334, 11), + Position(32807, 32335, 11), + Position(32807, 32336, 11), + Position(32807, 32337, 11), + Position(32806, 32337, 11), + Position(32805, 32337, 11), + Position(32805, 32338, 11), + Position(32805, 32339, 11), + Position(32806, 32339, 11), + Position(32806, 32338, 11), + Position(32807, 32338, 11), + Position(32808, 32338, 11), + Position(32808, 32337, 11), + Position(32809, 32337, 11), + Position(32810, 32337, 11), + Position(32811, 32337, 11), + Position(32811, 32338, 11), + Position(32806, 32338, 11), + Position(32810, 32338, 11), + Position(32810, 32339, 11), + Position(32809, 32339, 11), + Position(32809, 32338, 11), + Position(32811, 32336, 11), + Position(32811, 32335, 11), + Position(32810, 32335, 11), + Position(32809, 32335, 11), + Position(32808, 32335, 11), + Position(32809, 32334, 11), + Position(32809, 32333, 11), + Position(32810, 32333, 11), + Position(32811, 32333, 11), + Position(32806, 32338, 11), + Position(32810, 32334, 11), + Position(32811, 32334, 11), + Position(32812, 32334, 11), + Position(32813, 32334, 11), + Position(32812, 32333, 11), + Position(32810, 32334, 11), + Position(32812, 32335, 11), + Position(32813, 32335, 11), + Position(32813, 32333, 11) +} + +function onUse(player, item, fromPosition, target, toPosition) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + if ground:getId() == 372 then + ground:transform(394, 1) + ground:decay() + return true + elseif target:getId() == 1772 and toPosition.x == 32648 and toPosition.y == 32134 and toPosition.z == 10 and math.random(1, 100) <= 40 then + Game.sendMagicEffect({x = 32648, y = 32134, z = 10}, 3) + Game.removeItemOnMap({x = 32648, y = 32134, z = 10}, 1772) + return true + elseif target:getId() == 1772 and toPosition.x == 32648 and toPosition.y == 32134 and toPosition.z == 10 then + Game.sendMagicEffect({x = 32648, y = 32134, z = 10}, 3) + doTargetCombatHealth(0, player, COMBAT_PHYSICALDAMAGE, -40, -40) + return true + elseif target:getId() == 1791 and toPosition.x == 32356 and toPosition.y == 32074 and toPosition.z == 10 and math.random(1, 100) <= 40 then + Game.sendMagicEffect({x = 32356, y = 32074, z = 10}, 3) + Game.removeItemOnMap({x = 32356, y = 32074, z = 10}, 1791) + return true + elseif target:getId() == 1791 and toPosition.x == 32356 and toPosition.y == 32074 and toPosition.z == 10 then + Game.sendMagicEffect({x = 32356, y = 32074, z = 10}, 3) + doTargetCombatHealth(0, player, COMBAT_PHYSICALDAMAGE, -50, -50) + return true + elseif target:getActionId() == 17643 then + for i = 1, #pitsOfInfernoLava do + Game.createItem(5815, 1, pitsOfInfernoLava[i]) + end + target:transform(3141) + toPosition:sendMagicEffect(CONST_ME_POFF) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/postman_quest_pass.lua b/app/SabrehavenServer/data/actions/scripts/misc/postman_quest_pass.lua new file mode 100644 index 0000000..4aca817 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/postman_quest_pass.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(250) == 5 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have already finished The Postman Missions.") + return true + end + + player:setStorageValue(227, 6) + player:setStorageValue(228, 3) + player:setStorageValue(229, 4) + player:setStorageValue(230, 21) + player:setStorageValue(231, 3) + player:setStorageValue(233, 11) + player:setStorageValue(234, 8) + player:setStorageValue(242, 2) + player:setStorageValue(244, 3) + player:setStorageValue(245, 3) + player:setStorageValue(250, 5) + + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + item:remove() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/present.lua b/app/SabrehavenServer/data/actions/scripts/misc/present.lua new file mode 100644 index 0000000..46f07d8 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/present.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(3) + item:remove() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/pumpkin_head.lua b/app/SabrehavenServer/data/actions/scripts/misc/pumpkin_head.lua new file mode 100644 index 0000000..047f37e --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/pumpkin_head.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 2917 then + item:transform(2978, 1) + item:decay() + target:remove() + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/rake.lua b/app/SabrehavenServer/data/actions/scripts/misc/rake.lua new file mode 100644 index 0000000..2655080 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/rake.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + -- The Shattered Isles Parrot ring + if target:getId() == 6094 then + if player:getStorageValue(17502) == 1 and player:getStorageValue(17503) ~= 1 then + toPosition:sendMagicEffect(CONST_ME_POFF) + Game.createItem(6093, 1, Position(32422, 32770, 1)) + player:say("You have found a ring.", TALKTYPE_MONSTER_SAY) + player:setStorageValue(17503, 1) + end + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/rookgard_skip.lua b/app/SabrehavenServer/data/actions/scripts/misc/rookgard_skip.lua new file mode 100644 index 0000000..89779d8 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/rookgard_skip.lua @@ -0,0 +1,21 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getStorageValue(17572) ~= 1 and player:getLevel() <= 7 then + player:setStorageValue(17572, 1) + player:addHealth(-math.random(player:getHealth()-20, player:getHealth()-1)) + item:getPosition():sendMagicEffect(CONST_ME_HITBYFIRE) + player:say('OUCH!', TALKTYPE_MONSTER_SAY) + player:addExperience(4200 - player:getExperience()) + + player:addItem(3355,1) + player:addItem(3361,1) + player:addItem(3559,1) + player:addItem(3552,1) + player:addItem(3412,1) + player:addItem(3273,1) + player:addItem(3031,25) + player:addItem(3582,3) + player:addItem(3003,1) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/rope.lua b/app/SabrehavenServer/data/actions/scripts/misc/rope.lua new file mode 100644 index 0000000..9fee085 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/rope.lua @@ -0,0 +1,76 @@ +local ropeSpots = { + 386, 421 +} + +local holeSpots = { + 293, 294, 369, 370, 385, 394, 411, 412, + 421, 432, 433, 435, 482, 5081, 483, 594, + 595, 607, 609, 610, 615, 1066, 1067, 1080 +} + +local pools = {2886, 2887, 2888, 2889, 2890, 2891, 2895, 2896, 2897, 2898, 2899, 2900} + +function onUse(player, item, fromPosition, target, toPosition) + if (configManager.getBoolean(configKeys.ROPE_SPOT_BLOCK)) then + -- Rope for 7.4 protocol. + local newPos = {x = toPosition.x, y = toPosition.y, z = toPosition.z, stackpos = 0} + local groundItem = getThingfromPos(newPos) + local blockingItem = getThingfromPos({x = toPosition.x, y = toPosition.y, z = toPosition.z, stackpos = 255}) + if table.contains(ropeSpots, groundItem.itemid) then + newPos.y = newPos.y + 1 + newPos.z = newPos.z - 1 + if((blockingItem.itemid > 0 and not isInArray(pools, blockingItem.itemid)) or isCreature(blockingItem.uid)) then + doPlayerSendCancel(player, "You cannot use this object.") + else + doTeleportThing(player, newPos) + end + elseif table.contains(holeSpots, groundItem.itemid) then + newPos.y = newPos.y + 1 + local downPos = {x = toPosition.x, y = toPosition.y, z = toPosition.z + 1, stackpos = 255} + local downItem = getThingfromPos(downPos) + if(downItem.itemid > 0) then + doTeleportThing(downItem.uid, newPos) + else + doPlayerSendCancel(player, "You cannot use this object.") + end + end + return true + else + local tile = Tile(toPosition) + if not tile then + return false + end + + if not tile:getGround() then + return false + end + + if table.contains(ropeSpots, tile:getGround():getId()) then + player:teleportTo(target:getPosition():moveRel(0, 1, -1)) + return true + elseif table.contains(holeSpots, tile:getGround():getId()) or target:getId() == 435 then + local tile = Tile(target:getPosition():moveRel(0, 0, 1)) + if not tile then + return false + end + + local thing = tile:getTopCreature() + if not thing then + thing = tile:getTopVisibleThing() + end + + if thing:isCreature() then + thing:teleportTo(target:getPosition():moveRel(0, 1, 0), false) + return true + end + if thing:isItem() and thing:getType():isMovable() then + thing:moveTo(target:getPosition():moveRel(0, 1, 0)) + return true + end + return true + end + return false + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/sabrehaven_talon.lua b/app/SabrehavenServer/data/actions/scripts/misc/sabrehaven_talon.lua new file mode 100644 index 0000000..bcc9f29 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/sabrehaven_talon.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if player:getStorageValue(17582) < os.time() then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your experience earnings have been enchanted by the Tibianus Gods.") + player:setStorageValue(17582, os.time() + 24 * 60 * 60) -- 24 hour + item:remove(1) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You shoud not take any more today.") + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/scythe.lua b/app/SabrehavenServer/data/actions/scripts/misc/scythe.lua new file mode 100644 index 0000000..27e07a7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/scythe.lua @@ -0,0 +1,21 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3652 then + player:sendCancelMessage(target:getType():getDescription() .. ".") + return true + elseif target:getId() == 3653 then + target:transform(3651, 1) + target:decay() + Game.createItem(3605, 1, target:getPosition()) + return true + elseif target:getId() == 5463 then + target:transform(5462, 1) + target:decay() + Game.createItem(5466, 1, target:getPosition()) + return true + end + return doDestroyItem(target) +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/sheet_of_tracing_paper.lua b/app/SabrehavenServer/data/actions/scripts/misc/sheet_of_tracing_paper.lua new file mode 100644 index 0000000..5e1ed0a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/sheet_of_tracing_paper.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 2199 and toPosition.x == 32754 and toPosition.y == 32559 and toPosition.z == 09 and player:getStorageValue(315) == 1 then + item:transform(4843, 1) + item:decay() + player:setStorageValue(316, 1) + target:getPosition():sendMagicEffect(4) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/shop_points_scroll.lua b/app/SabrehavenServer/data/actions/scripts/misc/shop_points_scroll.lua new file mode 100644 index 0000000..71ad2f4 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/shop_points_scroll.lua @@ -0,0 +1,15 @@ +local hundredPoints = 6554 +local fiftyPoints = 6555 + +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == hundredPoints then + db.query("UPDATE znote_accounts SET points = points + 100 WHERE account_id = ".. player:getAccountId() .."") + elseif item:getId() == fiftyPoints then + db.query("UPDATE znote_accounts SET points = points + 50 WHERE account_id = ".. player:getAccountId() .."") + end + + item:getPosition():sendMagicEffect(3) + item:remove() + player:save() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/shovel.lua b/app/SabrehavenServer/data/actions/scripts/misc/shovel.lua new file mode 100644 index 0000000..66a1f04 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/shovel.lua @@ -0,0 +1,57 @@ +function onUse(player, item, fromPosition, target, toPosition) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + local toTarget = target; + + local itemType = ItemType(target:getId()) + if itemType:isSplash() then + toTarget = ground + end + + if toTarget:getId() == 231 then + toTarget:getPosition():sendMagicEffect(3) + return true + elseif toTarget:getId() == 593 then + toTarget:transform(594, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + return true + elseif toTarget:getId() == 606 then + toTarget:transform(607, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + return true + elseif toTarget:getId() == 608 then + toTarget:transform(609, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + elseif toTarget:getId() == 614 and math.random(1, 100) <= 50 then + toTarget:transform(615, 1) + toTarget:decay() + toTarget:getPosition():sendMagicEffect(3) + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + elseif toTarget:getId() == 614 then + toTarget:getPosition():sendMagicEffect(3) + elseif toTarget:getId() == 616 and math.random(1, 100) <= 95 then + toTarget:transform(617, 1) + toTarget:decay() + toTarget:getPosition():sendMagicEffect(3) + Game.createMonster("scarab", toTarget:getPosition()) + elseif toTarget:getId() == 616 then + toTarget:getPosition():sendMagicEffect(3) + Game.createItem(3042, 1, toTarget:getPosition()) + toTarget:transform(617, 1) + toTarget:decay() + elseif toTarget:getId() == 617 then + toTarget:getPosition():sendMagicEffect(3) + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/skill_trainer.lua b/app/SabrehavenServer/data/actions/scripts/misc/skill_trainer.lua new file mode 100644 index 0000000..792ee77 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/skill_trainer.lua @@ -0,0 +1,30 @@ +local statues = { + [17725] = SKILL_SWORD, + [17724] = SKILL_AXE, + [17726] = SKILL_CLUB, + [17727] = SKILL_DISTANCE, + [17728] = SKILL_MAGLEVEL +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + -- local skill = statues[item:getActionId()] + -- if not player:isPremium() then + -- player:sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) + -- return true + -- end + + -- if player:isPzLocked() then + -- return false + -- end + + -- local entreePrice = 1000 + -- if player:getBankBalance() < entreePrice then + -- player:sendCancelMessage("You do not have 1000 gold coins in your bank account balance to participate in offline training.") + -- return true + -- end + + -- player:setOfflineTrainingSkill(skill) + -- player:setBankBalance(player:getBankBalance() - entreePrice) + -- player:remove() + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/skinning_dusting.lua b/app/SabrehavenServer/data/actions/scripts/misc/skinning_dusting.lua new file mode 100644 index 0000000..e21dd38 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/skinning_dusting.lua @@ -0,0 +1,80 @@ +local config = { + [5908] = { + -- Minotaurs + [4011] = {value = 25000, newItem = 5878}, + [4047] = {value = 25000, newItem = 5878}, + [4057] = {value = 25000, newItem = 5878}, + [4052] = {value = 25000, newItem = 5878}, + + -- Low Class Lizards + [4321] = {value = 25000, newItem = 5876}, + [4327] = {value = 25000, newItem = 5876}, + [4324] = {value = 25000, newItem = 5876}, + + -- Dragons + [4025] = {value = 25000, newItem = 5877}, + + -- Dragon Lords + [4062] = {value = 25000, newItem = 5948}, + + -- Behemoths + [4112] = {value = 35000, newItem = 5893}, + + -- Bone Beasts + [4212] = {value = 25000, newItem = 5925}, + }, + [5942] = { + -- Demon + [4097] = {value = 25000, newItem = 5906}, + + -- Vampires + [4137] = {value = 25000, newItem = 5905} + } +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local skin = config[item.itemid][target.itemid] + if not skin then + return false + end + + local random, effect, transform = math.random(1, 100000), CONST_ME_MAGIC_GREEN, true + if type(skin[1]) == 'table' then + local _skin + for i = 1, #skin do + _skin = skin[i] + if random <= _skin.value then + if isInArray({7441, 7442, 7444, 7445}, target.itemid) then + player:addItem(_skin.newItem, _skin.amount or 1) + effect = CONST_ME_HITAREA + else + player:addItem(_skin.newItem, _skin.amount or 1) + end + break + end + end + + elseif random <= skin.value then + if isInArray({7441, 7442, 7444, 7445}, target.itemid) then + player:addItem(skin.newItem, skin.amount or 1) + effect = CONST_ME_HITAREA + else + player:addItem(skin.newItem, skin.amount or 1) + end + else + if isInArray({7441, 7442, 7444, 7445}, target.itemid) then + player:say('The attempt of sculpting failed miserably.', TALKTYPE_MONSTER_SAY) + effect = CONST_ME_HITAREA + else + effect = CONST_ME_POFF + end + end + + toPosition:sendMagicEffect(effect) + if transform then + target:transform(skin.after or target.itemid + 1) + target:decay() + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/skull_candle.lua b/app/SabrehavenServer/data/actions/scripts/misc/skull_candle.lua new file mode 100644 index 0000000..ef0f284 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/skull_candle.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 2917 then + player:addItem(5813, 1) + item:remove(1) + target:remove(1) + return true + end + + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/snake_destroyer.lua b/app/SabrehavenServer/data/actions/scripts/misc/snake_destroyer.lua new file mode 100644 index 0000000..e95e797 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/snake_destroyer.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4850 and player:getStorageValue(293) == 17 then + target:transform(4851, 1) + target:decay() + player:setStorageValue(299, 1) + item:remove() + target:getPosition():sendMagicEffect(7) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/snowheap.lua b/app/SabrehavenServer/data/actions/scripts/misc/snowheap.lua new file mode 100644 index 0000000..50a07f3 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/snowheap.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + Game.createItem(2992, 1, fromPosition) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/special_rights.lua b/app/SabrehavenServer/data/actions/scripts/misc/special_rights.lua new file mode 100644 index 0000000..66551be --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/special_rights.lua @@ -0,0 +1,37 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:hasFlag(PlayerFlag_SpecialMoveUse) then + if item:getId() == 372 then + item:transform(394, 1) + item:decay() + elseif item:getId() == 386 or item:getId() == 421 then + local relPos = item:getPosition():moveRel(0, 1, -1) + player:teleportTo(relPos) + elseif item:getId() == 593 then + item:transform(594, 1) + item:decay() + doRelocate(item:getPosition(),item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 606 or item:getId() == 608 then + item:transform(607, 1) + item:decay() + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 614 then + item:transform(615, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 3653 then + item:transform(3651, 1) + item:decay() + Game.createItem(3605, 1, item:getPosition()) + elseif item:getId() == 3696 then + item:transform(3695, 1) + item:decay() + elseif item:getId() == 3702 then + item:transform(3701, 1) + item:decay() + end + else + return false + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/spectral_stone.lua b/app/SabrehavenServer/data/actions/scripts/misc/spectral_stone.lua new file mode 100644 index 0000000..a765752 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/spectral_stone.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 599 and toPosition.x == 32665 and toPosition.y == 32736 and toPosition.z == 06 and player:getStorageValue(320) == 5 then + player:setStorageValue(321,1) + target:getPosition():sendMagicEffect(13) + return true + elseif target:getId() == 599 and toPosition.x == 32497 and toPosition.y == 31622 and toPosition.z == 06 and player:getStorageValue(320) == 5 then + player:setStorageValue(322,1) + target:getPosition():sendMagicEffect(13) + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/spellbook.lua b/app/SabrehavenServer/data/actions/scripts/misc/spellbook.lua new file mode 100644 index 0000000..2445cba --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/spellbook.lua @@ -0,0 +1,32 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local count = getPlayerInstantSpellCount(player) + local text = "" + local spells = {} + for i = 0, count - 1 do + local spell = getPlayerInstantSpellInfo(player, i) + if spell.level ~= 0 then + if spell.manapercent > 0 then + spell.mana = spell.manapercent .. "%" + end + spells[#spells + 1] = spell + end + end + + table.sort(spells, function(a, b) return a.level < b.level end) + + local prevLevel = -1 + for i, spell in ipairs(spells) do + local line = "" + if prevLevel ~= spell.level then + if i ~= 1 then + line = "\n" + end + line = line .. "Spells for Level " .. spell.level .. "\n" + prevLevel = spell.level + end + text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" + end + + player:showTextDialog(item:getId(), text) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/misc/strange_lever.lua b/app/SabrehavenServer/data/actions/scripts/misc/strange_lever.lua new file mode 100644 index 0000000..da457f5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/strange_lever.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2566 then + item:transform(2567, 1) + item:decay() + elseif item:getId() == 2567 then + player:sendCancelMessage("It doesn't move.") + elseif item:getId() == 2569 then + item:transform(2570, 1) + item:decay() + elseif item:getId() == 2570 then + item:transform(2569, 1) + item:decay() + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/teleporters.lua b/app/SabrehavenServer/data/actions/scripts/misc/teleporters.lua new file mode 100644 index 0000000..c48fc69 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/teleporters.lua @@ -0,0 +1,16 @@ +local downstairs = { + 435 +} + +local upstairs = { + 1948, 1968, 5542 +} + +function onUse(player, item, fromPosition, target, toPosition) + if table.contains(downstairs, item:getId()) then + player:teleportTo(item:getPosition():moveRel(0, 0, 1)) + elseif table.contains(upstairs, item:getId()) then + player:teleportTo(item:getPosition():moveRel(0, 1, -1)) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/time.lua b/app/SabrehavenServer/data/actions/scripts/misc/time.lua new file mode 100644 index 0000000..98c7fcb --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/time.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:sendTextMessage(MESSAGE_INFO_DESCR, "The time is " .. getFormattedWorldTime() .. ".") + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/used_lamp.lua b/app/SabrehavenServer/data/actions/scripts/misc/used_lamp.lua new file mode 100644 index 0000000..ca56d87 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/used_lamp.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 2874 and target:getFluidType() == FLUID_OIL then + target:transform(target:getId(), FLUID_NONE) + item:transform(2914, 1) + item:decay() + return true + end + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/water_pipe.lua b/app/SabrehavenServer/data/actions/scripts/misc/water_pipe.lua new file mode 100644 index 0000000..2dc6444 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/water_pipe.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 90 then + item:getPosition():sendMagicEffect(3) + return true + else + player:getPosition():sendMagicEffect(3) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/misc/weapons.lua b/app/SabrehavenServer/data/actions/scripts/misc/weapons.lua new file mode 100644 index 0000000..749279a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/misc/weapons.lua @@ -0,0 +1,3 @@ +function onUse(player, item, fromPosition, target, toPosition) + return doDestroyItem(target) +end diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/1.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/1.lua new file mode 100644 index 0000000..f8f19bc --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/1.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32627, y = 31699, z = 10}, 1771) then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32627, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + doRelocate({x = 32628, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + doRelocate({x = 32629, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + Game.transformItemOnMap({x = 32627, y = 31699, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32627, y = 31699, z = 10}) + Game.transformItemOnMap({x = 32628, y = 31699, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32629, y = 31699, z = 10}, 1771, 622) + Game.createItem(4786, 1, {x = 32629, y = 31699, z = 10}) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32628, y = 31699, z = 10}, 622) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32627, y = 31699, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32628, y = 31699, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32629, y = 31699, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32627, y = 31699, z = 10}, 4788) + Game.removeItemOnMap({x = 32629, y = 31699, z = 10}, 4786) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/10.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/10.lua new file mode 100644 index 0000000..3f542d2 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/10.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32792, y = 31581, z = 07},1282) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32792, y = 31581, z = 07}, 1282) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32792, y = 31581, z = 07}, 1282) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32792, y = 31581, z = 07},{x = 32792, y = 31582, z = 07}) + Game.createItem(1282, 1, {x = 32792, y = 31581, z = 07}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/11.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/11.lua new file mode 100644 index 0000000..4608503 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/11.lua @@ -0,0 +1,32 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32685, y = 32084, z = 09}, 1771) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32687, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32686, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32685, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32684, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + Game.transformItemOnMap({x = 32687, y = 32084, z = 09}, 1771, 727) + Game.createItem(4798, 1, {x = 32687, y = 32084, z = 09}) + Game.transformItemOnMap({x = 32686, y = 32084, z = 09}, 1771, 727) + Game.transformItemOnMap({x = 32685, y = 32084, z = 09}, 1771, 727) + Game.transformItemOnMap({x = 32684, y = 32084, z = 09}, 1771, 727) + Game.createItem(4800, 1, {x = 32684, y = 32084, z = 09}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2772 and Game.isItemThere({x = 32685, y = 32084, z = 09},727) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32684, y = 32084, z = 09}, 4800) + Game.transformItemOnMap({x = 32684, y = 32084, z = 09}, 727, 1771) + Game.transformItemOnMap({x = 32685, y = 32084, z = 09}, 727, 1771) + Game.removeItemOnMap({x = 32687, y = 32084, z = 09}, 4798) + Game.transformItemOnMap({x = 32687, y = 32084, z = 09}, 727, 1771) + Game.transformItemOnMap({x = 32686, y = 32084, z = 09}, 727, 1771) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/12.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/12.lua new file mode 100644 index 0000000..5505cb4 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/12.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32673, y = 32085, z = 08},430) and Game.isItemThere ({x = 32669, y = 32089, z = 08},430) and Game.isItemThere ({x = 32673, y = 32093, z = 08},430) and Game.isItemThere ({x = 32677, y = 32089, z = 08},430) and Game.isItemThere ({x = 32673, y = 32083, z = 08},3349) and Game.isItemThere ({x = 32667, y = 32089, z = 08},3585) and Game.isItemThere ({x = 32673, y = 32094, z = 08},3264) and Game.isItemThere ({x = 32679, y = 32089, z = 08},3059) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32673, y = 32083, z = 08}, 3349) + Game.removeItemOnMap({x = 32667, y = 32089, z = 08}, 3585) + Game.removeItemOnMap({x = 32673, y = 32094, z = 08}, 3264) + Game.removeItemOnMap({x = 32679, y = 32089, z = 08}, 3059) + Game.sendMagicEffect({x = 32673, y = 32083, z = 08}, 11) + Game.sendMagicEffect({x = 32667, y = 32089, z = 08}, 11) + Game.sendMagicEffect({x = 32673, y = 32094, z = 08}, 11) + Game.sendMagicEffect({x = 32679, y = 32089, z = 08}, 11) + doRelocate({x = 32673, y = 32093, z = 08},{x = 32671, y = 32069, z = 08}) + doRelocate({x = 32669, y = 32089, z = 08},{x = 32672, y = 32069, z = 08}) + doRelocate({x = 32673, y = 32085, z = 08},{x = 32671, y = 32070, z = 08}) + doRelocate({x = 32677, y = 32089, z = 08},{x = 32672, y = 32070, z = 08}) + Game.sendMagicEffect({x = 32671, y = 32069, z = 08}, 11) + Game.sendMagicEffect({x = 32672, y = 32069, z = 08}, 11) + Game.sendMagicEffect({x = 32671, y = 32070, z = 08}, 11) + Game.sendMagicEffect({x = 32672, y = 32070, z = 08}, 11) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/13.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/13.lua new file mode 100644 index 0000000..e314472 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/13.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32568, y = 32078, z = 12},2185) and Game.isItemThere ({x = 32569, y = 32078, z = 12},2185) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32568, y = 32078, z = 12}, 2185) + Game.removeItemOnMap({x = 32569, y = 32078, z = 12}, 2185) + elseif item:getId() == 2773 and Game.isItemThere({x = 32568, y = 32078, z = 12},2185) and Game.isItemThere ({x = 32569, y = 32078, z = 12}, 2185) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.createItem(2185, 1, {x = 32568, y = 32078, z = 12}) + Game.createItem(2185, 1, {x = 32569, y = 32078, z = 12}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/14.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/14.lua new file mode 100644 index 0000000..74d5fa7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/14.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33314, y = 31592, z = 15}, 1842) + doRelocate({x = 33316, y = 31591, z = 15},{x = 33317, y = 31591, z = 15}) + Game.createItem(1949, 1, {x = 33316, y = 31591, z = 15}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33314, y = 31592, z = 15},{x = 33315, y = 31592, z = 15}) + Game.createItem(1842, 1, {x = 33314, y = 31592, z = 15}) + Game.removeItemOnMap({x = 33316, y = 31591, z = 15}, 1949) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/15.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/15.lua new file mode 100644 index 0000000..8ec29c7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/15.lua @@ -0,0 +1,31 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33295, y = 31677, z = 15},1791) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33295, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33296, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33297, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33298, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33299, y = 31677, z = 15}, 1791) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 33295, y = 31677, z = 15}, 1791) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33295, y = 31677, z = 15},{x = 33295, y = 31678, z = 15}) + doRelocate({x = 33296, y = 31677, z = 15},{x = 33296, y = 31678, z = 15}) + doRelocate({x = 33297, y = 31677, z = 15},{x = 33297, y = 31678, z = 15}) + doRelocate({x = 33298, y = 31677, z = 15},{x = 33298, y = 31678, z = 15}) + doRelocate({x = 33299, y = 31677, z = 15},{x = 33299, y = 31678, z = 15}) + Game.createItem(1791, 1, {x = 33295, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33296, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33297, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33298, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33299, y = 31677, z = 15}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/16.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/16.lua new file mode 100644 index 0000000..163597a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/16.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33171, y = 31897, z = 08}, 1772) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33171, y = 31897, z = 08},{x = 33171, y = 31898, z = 08}) + Game.createItem(1772, 1, {x = 33171, y = 31897, z = 08}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/17.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/17.lua new file mode 100644 index 0000000..4b534a3 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/17.lua @@ -0,0 +1,31 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 33222, y = 31671, z = 13},430) and Game.isItemThere ({x = 33223, y = 31671, z = 13},430) and Game.isItemThere ({x = 33224, y = 31671, z = 13},430) and Game.isItemThere ({x = 33225, y = 31671, z = 13},430) and Game.isItemThere ({x = 33220, y = 31659, z = 13},1772) then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 33220, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33221, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33222, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33223, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33224, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33219, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33219, y = 31657, z = 13}, 1772) + Game.removeItemOnMap({x = 33221, y = 31657, z = 13}, 1772) + Game.removeItemOnMap({x = 33220, y = 31661, z = 13}, 1772) + Game.removeItemOnMap({x = 33222, y = 31661, z = 13}, 1772) + Game.createMonster("Demon", {x = 33224, y = 31659, z = 13}) + Game.createMonster("Demon", {x = 33223, y = 31659, z = 13}) + Game.createMonster("Demon", {x = 33219, y = 31657, z = 13}) + Game.createMonster("Demon", {x = 33221, y = 31657, z = 13}) + Game.createMonster("Demon", {x = 33220, y = 31661, z = 13}) + Game.createMonster("Demon", {x = 33222, y = 31661, z = 13}) + doRelocate({x = 33222, y = 31671, z = 13},{x = 33219, y = 31659, z = 13}) + doRelocate({x = 33223, y = 31671, z = 13},{x = 33220, y = 31659, z = 13}) + doRelocate({x = 33224, y = 31671, z = 13},{x = 33221, y = 31659, z = 13}) + doRelocate({x = 33225, y = 31671, z = 13},{x = 33222, y = 31659, z = 13}) + Game.sendMagicEffect({x = 33219, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33220, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33221, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33222, y = 31659, z = 13}, 11) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/18.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/18.lua new file mode 100644 index 0000000..6a66414 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/18.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32483, y = 31633, z = 09}, 385) then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32483, y = 31633, z = 09}, 355, 385) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/19.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/19.lua new file mode 100644 index 0000000..c339c4f --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/19.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:remove() + Game.createItem(2126, 1, {x = 32487, y = 31628, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31627, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31627, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31628, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31626, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31626, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31626, z = 13}) + Game.sendMagicEffect({x = 32488, y = 31628, z = 13}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/2.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/2.lua new file mode 100644 index 0000000..d58a377 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/2.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if Game.isItemThere({x = 33211, y = 32698, z = 13}, 1306) then + Game.removeItemOnMap({x = 33211, y = 32698, z = 13}, 1306) + else + doRelocate({x = 33211, y = 32698, z = 13}, {x = 33211, y = 32697, z = 13}) + Game.createItem(1306, 1, {x = 33211, y = 32698, z = 13}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/20.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/20.lua new file mode 100644 index 0000000..bc36d18 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/20.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32259, y = 31891, z = 10},2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32259, y = 31891, z = 10}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32259, y = 31891, z = 10}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32259, y = 31891, z = 10},{x = 32259, y = 31892, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31891, z = 10}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/21.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/21.lua new file mode 100644 index 0000000..f1fbfbd --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/21.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32313, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32313, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/22.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/22.lua new file mode 100644 index 0000000..9d20100 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/22.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32313, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32313, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/23.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/23.lua new file mode 100644 index 0000000..a224a39 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/23.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32311, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32311, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/24.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/24.lua new file mode 100644 index 0000000..2d6ca6d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/24.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32311, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32311, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/25.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/25.lua new file mode 100644 index 0000000..5c9e50c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/25.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32309, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32309, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/26.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/26.lua new file mode 100644 index 0000000..e60621c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/26.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32309, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32309, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/27.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/27.lua new file mode 100644 index 0000000..d9bad72 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/27.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32266, y = 31860, z = 11},2129) then + Game.removeItemOnMap({x = 32266, y = 31860, z = 11}, 2129) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 410, 411) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32266, y = 31860, z = 11}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/28.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/28.lua new file mode 100644 index 0000000..1c16eab --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/28.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32259, y = 31890, z = 10},2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32259, y = 31890, z = 10}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32259, y = 31890, z = 10}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32259, y = 31890, z = 10},{x = 32259, y = 31889, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31890, z = 10}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/29.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/29.lua new file mode 100644 index 0000000..98054d3 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/29.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31845, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/3.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/3.lua new file mode 100644 index 0000000..4ed7b1f --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/3.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33148, y = 32867, z = 09}, 2129) and Game.isItemThere ({x = 33149, y = 32867, z = 09}, 2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33148, y = 32867, z = 09}, 2129) + Game.removeItemOnMap({x = 33149, y = 32867, z = 09}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 33148, y = 32867, z = 09}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33148, y = 32867, z = 09}, {x = 33148, y = 32869, z = 09}) + doRelocate({x = 33149, y = 32867, z = 09}, {x = 33149, y = 32869, z = 09}) + Game.createItem(2129, 1, {x = 33148, y = 32867, z = 09}) + Game.createItem(2129, 1, {x = 33149, y = 32867, z = 09}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/30.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/30.lua new file mode 100644 index 0000000..5667f38 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/30.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31843, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31843, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/31.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/31.lua new file mode 100644 index 0000000..0a71004 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/31.lua @@ -0,0 +1,21 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31842, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31846, z = 14}, 12) + Game.transformItemOnMap({x = 32214, y = 31850, z = 15}, 2114, 2113) + Game.transformItemOnMap({x = 32215, y = 31850, z = 15}, 2114, 2113) + Game.transformItemOnMap({x = 32216, y = 31850, z = 15}, 2114, 2113) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/32.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/32.lua new file mode 100644 index 0000000..4cd245a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/32.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31844, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/33.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/33.lua new file mode 100644 index 0000000..6053a06 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/33.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31841, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31845, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/34.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/34.lua new file mode 100644 index 0000000..07c023c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/34.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + elseif item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + elseif item:getId() == 2773 then + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + item:transform(2772, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/35.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/35.lua new file mode 100644 index 0000000..7b97813 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/35.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32604, y = 31905, z = 03}, 1789) + Game.removeItemOnMap({x = 32605, y = 31905, z = 03}, 1790) + Game.removeItemOnMap({x = 32604, y = 31904, z = 03}, 1787) + Game.removeItemOnMap({x = 32605, y = 31904, z = 03}, 1788) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32604, y = 31904, z = 03},{x = 32604, y = 31906, z = 03}) + doRelocate({x = 32604, y = 31905, z = 03},{x = 32604, y = 31906, z = 03}) + doRelocate({x = 32605, y = 31904, z = 03},{x = 32605, y = 31906, z = 03}) + doRelocate({x = 32605, y = 31905, z = 03},{x = 32605, y = 31906, z = 03}) + Game.createItem(1787, 1, {x = 32604, y = 31904, z = 03}) + Game.createItem(1789, 1, {x = 32604, y = 31905, z = 03}) + Game.createItem(1788, 1, {x = 32605, y = 31904, z = 03}) + Game.createItem(1790, 1, {x = 32605, y = 31905, z = 03}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/36.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/36.lua new file mode 100644 index 0000000..44259c4 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/36.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.transformItemOnMap({x = 32605, y = 31902, z = 04}, 436, 432) + item:transform(2773, 1) + item:decay() + doRelocate({x = 32605, y = 31902, z = 04},{x = 32605, y = 31902, z = 05}) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32605, y = 31902, z = 04}, 432, 436) + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/37.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/37.lua new file mode 100644 index 0000000..0507b76 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/37.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/38.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/38.lua new file mode 100644 index 0000000..0568787 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/38.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32566, y = 32119, z = 07}, 1270) + Game.transformItemOnMap({x = 32566, y = 32118, z = 07}, 1270, 1274) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32566, y = 32119, z = 07},{x = 32567, y = 32119, z = 07}) + Game.createItem(1270, 1, {x = 32566, y = 32119, z = 07}) + Game.transformItemOnMap({x = 32566, y = 32118, z = 07}, 1274, 1270) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/39.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/39.lua new file mode 100644 index 0000000..dbe8941 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/39.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32426, y = 32202, z = 14},{x = 32426, y = 32200, z = 14}) + doRelocate({x = 32426, y = 32201, z = 14},{x = 32426, y = 32200, z = 14}) + doRelocate({x = 32427, y = 32202, z = 14},{x = 32427, y = 32200, z = 14}) + doRelocate({x = 32427, y = 32201, z = 14},{x = 32427, y = 32200, z = 14}) + Game.removeItemOnMap({x = 32426, y = 32202, z = 14}, 1771) + Game.removeItemOnMap({x = 32426, y = 32201, z = 14}, 1771) + Game.removeItemOnMap({x = 32427, y = 32202, z = 14}, 1771) + Game.removeItemOnMap({x = 32427, y = 32201, z = 14}, 1771) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.createTile({x = 32426, y = 32201, z = 14}, true) + Game.createTile({x = 32427, y = 32201, z = 14}, true) + Game.createTile({x = 32426, y = 32202, z = 14}, true) + Game.createTile({x = 32427, y = 32202, z = 14}, true) + Game.createItem(1771, 1, {x = 32426, y = 32201, z = 14}) + Game.createItem(1771, 1, {x = 32427, y = 32201, z = 14}) + Game.createItem(1771, 1, {x = 32426, y = 32202, z = 14}) + Game.createItem(1771, 1, {x = 32427, y = 32202, z = 14}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/4.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/4.lua new file mode 100644 index 0000000..2d20475 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/4.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33147, y = 32862, z = 9}, 3246) and Game.isItemThere({x = 33144, y = 32868, z = 8}, 1653) and getGlobalStorageValue(21411) == -1 then + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 33147, y = 32862, z = 9}, CONST_ME_SOUND_WHITE) + Game.sendMagicEffect({x = 33145, y = 32870, z = 8}, CONST_ME_SOUND_YELLOW) + doRelocate({x = 33147, y = 32862, z = 9}, {x = 33145, y = 32870, z = 8}) + Game.removeItemOnMap({x = 33144, y = 32868, z = 8}, 1653) + Game.createItem(1654, 1, {x = 33144, y = 32868, z = 8}) + setGlobalStorageValue(21411, 1) + player:setStorageValue(17605, 1) + broadcastMessage("The player " .. player:getName() .. " have solved the Serpentine Tower!", MESSAGE_STATUS_WARNING) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/40.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/40.lua new file mode 100644 index 0000000..47ffcdb --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/40.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.transformItemOnMap({x = 32313, y = 31928, z = 08}, 2772, 2773) + Game.sendMagicEffect({x = 32314, y = 31928, z = 08}, 12) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32313, y = 31928, z = 08}, 2773, 2772) + Game.sendMagicEffect({x = 32314, y = 31928, z = 08}, 12) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/41.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/41.lua new file mode 100644 index 0000000..e58ecc6 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/41.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:remove() + Game.removeItemOnMap({x = 32186, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32187, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32188, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32189, y = 31626, z = 08}, 2129) + Game.sendMagicEffect({x = 32180, y = 31633, z = 08}, 3) + Game.sendMagicEffect({x = 32186, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32187, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32188, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32189, y = 31626, z = 08}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/42.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/42.lua new file mode 100644 index 0000000..820f05b --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/42.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32915, y = 32076, z = 06},388) then + Game.removeItemOnMap({x = 32915, y = 32076, z = 06}, 388) + Game.removeItemOnMap({x = 32915, y = 32080, z = 06}, 388) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and not Game.isItemThere({x = 32915, y = 32076, z = 06}, 388) then + doRelocate({x = 32915, y = 32076, z = 06},{x = 32916, y = 32076, z = 06}) + doRelocate({x = 32915, y = 32080, z = 06},{x = 32916, y = 32080, z = 06}) + Game.createItem(388, 1, {x = 32915, y = 32076, z = 06}) + Game.createItem(388, 1, {x = 32915, y = 32080, z = 06}) + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/43.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/43.lua new file mode 100644 index 0000000..8d26791 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/43.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32780, y = 32231, z = 08}, 389) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32780, y = 32231, z = 08},{x = 32780, y = 32232, z = 08}) + Game.createItem(389, 1, {x = 32780, y = 32231, z = 08}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/44.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/44.lua new file mode 100644 index 0000000..f945df3 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/44.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.removeItemOnMap({x = 32649, y = 32923, z = 08}, 1822) + Game.transformItemOnMap({x = 32649, y = 32923, z = 08}, 351, 385) + Game.transformItemOnMap({x = 32652, y = 32922, z = 08}, 2772, 2773) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32649, y = 32923, z = 08}, 385, 351) + Game.createItem(1822, 1, {x = 32649, y = 32923, z = 08}) + Game.transformItemOnMap({x = 32652, y = 32922, z = 08}, 2773, 2772) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/45.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/45.lua new file mode 100644 index 0000000..2a6c75a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/45.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2774 and Game.isItemThere({x = 33151, y = 32866, z = 08},1345) then + Game.removeItemOnMap({x = 33151, y = 32866, z = 08}, 1345) + Game.sendMagicEffect({x = 33151, y = 32862, z = 07}, 14) + elseif item:getId() == 2774 then + Game.sendMagicEffect({x = 33151, y = 32862, z = 07}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/46.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/46.lua new file mode 100644 index 0000000..b67df79 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/46.lua @@ -0,0 +1,42 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32614, y = 32209, z = 10},2523) then + Game.removeItemOnMap({x = 32614, y = 32209, z = 10}, 2523) + Game.removeItemOnMap({x = 32614, y = 32208, z = 10}, 1270) + doRelocate({x = 32614, y = 32209, z = 10},{x = 32614, y = 32208, z = 10}) + Game.createItem(2523, 1, {x = 32614, y = 32208, z = 10}) + Game.createItem(1270, 1, {x = 32614, y = 32209, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32206, z = 10}, 1791) + Game.removeItemOnMap({x = 32614, y = 32205, z = 10}, 1270) + Game.createItem(1810, 1, {x = 32614, y = 32204, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32221, z = 10}, 1270) + Game.removeItemOnMap({x = 32615, y = 32223, z = 10}, 1946) + Game.createItem(1796, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32615, y = 32221, z = 10}) + Game.createItem(2124, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32613, y = 32220, z = 10}) + Game.sendMagicEffect({x = 32613, y = 32220, z = 10}, 9) + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32613, y = 32220, z = 10}, 3050) + Game.createItem(1823, 1, {x = 32614, y = 32205, z = 10}) + Game.createItem(1270, 1, {x = 32614, y = 32206, z = 10}) + Game.sendMagicEffect({x = 32615, y = 32224, z = 10}, 16) + Game.sendMagicEffect({x = 32614, y = 32224, z = 10}, 16) + elseif item:getId() == 2772 then + doRelocate({x = 32614, y = 32209, z = 10},{x = 32613, y = 32209, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32221, z = 10}, 1270) + Game.removeItemOnMap({x = 32615, y = 32223, z = 10}, 1946) + Game.createItem(1796, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32615, y = 32221, z = 10}) + Game.createItem(2124, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32613, y = 32220, z = 10}) + Game.sendMagicEffect({x = 32613, y = 32220, z = 10}, 9) + Game.createItem(1270, 1, {x = 32614, y = 32209, z = 10}) + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32613, y = 32220, z = 10}, 3050) + elseif item:getId() == 2773 then + Game.sendMagicEffect({x = 32616, y = 32222, z = 10}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/47.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/47.lua new file mode 100644 index 0000000..8230d26 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/47.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32623, y = 32189, z = 09},{x = 32623, y = 32190, z = 09}, true) + doRelocate({x = 32623, y = 32188, z = 09},{x = 32623, y = 32189, z = 09}, true) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32623, y = 32188, z = 09},{x = 32622, y = 32189, z = 09}, true) + doRelocate({x = 32623, y = 32189, z = 09},{x = 32623, y = 32188, z = 09}, true) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/48.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/48.lua new file mode 100644 index 0000000..adc1df2 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/48.lua @@ -0,0 +1,40 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32594, y = 32214, z = 09},3050) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32603, y = 32216, z = 09}, 1626) + Game.removeItemOnMap({x = 32604, y = 32216, z = 09}, 1627) + doRelocate({x = 32603, y = 32216, z = 09},{x = 32603, y = 32217, z = 09}) + doRelocate({x = 32604, y = 32216, z = 09},{x = 32604, y = 32217, z = 09}) + doRelocate({x = 32593, y = 32216, z = 09},{x = 32592, y = 32216, z = 09}) + doRelocate({x = 32594, y = 32216, z = 09},{x = 32592, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32606, y = 32216, z = 09}, 1271) + Game.removeItemOnMap({x = 32607, y = 32216, z = 09}, 1271) + Game.transformItemOnMap({x = 32601, y = 32216, z = 09}, 1271, 1626) + Game.transformItemOnMap({x = 32602, y = 32216, z = 09}, 1271, 1627) + Game.createItem(1271, 1, {x = 32594, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32593, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32603, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32604, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32594, y = 32214, z = 09}, 3050) + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 9) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 3) + elseif item:getId() == 2773 and Game.isItemThere({x = 32594, y = 32214, z = 09},3050) then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32593, y = 32216, z = 09}, 1271) + Game.removeItemOnMap({x = 32594, y = 32216, z = 09}, 1271) + Game.transformItemOnMap({x = 32601, y = 32216, z = 09}, 1626, 1271) + Game.transformItemOnMap({x = 32602, y = 32216, z = 09}, 1627, 1271) + Game.transformItemOnMap({x = 32603, y = 32216, z = 09}, 1271, 1626) + Game.transformItemOnMap({x = 32604, y = 32216, z = 09}, 1271, 1627) + Game.createItem(1271, 1, {x = 32606, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32607, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32594, y = 32214, z = 09}, 3050) + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 9) + elseif item:getId() == 2773 then + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/49.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/49.lua new file mode 100644 index 0000000..926a547 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/49.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.createItem(2471, 1, {x = 32479, y = 31901, z = 05}) + Game.createItem(2122, 1, {x = 32479, y = 31892, z = 03}) + elseif item:getId() == 2773 and not Game.isItemThere({x = 32479, y = 31892, z = 03}, 2122) then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/5.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/5.lua new file mode 100644 index 0000000..ba9db1c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/5.lua @@ -0,0 +1,25 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and math.random(1, 100) <= 70 then + item:transform(2773, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3242, 1, {x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -200, -200) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3595, 1, {x = 33117, y = item:getPosition().y, z = 14}) + player:setStorageValue(258, 1) + Game.sendMagicEffect({x = 33122, y = 32765, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32761, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32762, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32763, z = 14}, 15) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3573, 1, {x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/50.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/50.lua new file mode 100644 index 0000000..c1f4725 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/50.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/51.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/51.lua new file mode 100644 index 0000000..406573f --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/51.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32476, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32477, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32478, y = 31900, z = 06},2772) and Game.isItemThere ({x = 32479, y = 31900, z = 06},2772) and Game.isItemThere ({x = 32480, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32481, y = 31900, z = 06}, 2772) then + item:transform(2773, 1) + item:decay() + Game.createItem(1948, 1, {x = 32476, y = 31904, z = 06}) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32479, y = 31905, z = 06}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32476, y = 31904, z = 06}, 1948) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32476, y = 31904, z = 06}, 1948) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/52.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/52.lua new file mode 100644 index 0000000..5722a6c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/52.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32476, y = 31900, z = 04},3593) and Game.isItemThere ({x = 32477, y = 31900, z = 04},3587) and Game.isItemThere ({x = 32478, y = 31900, z = 04},3590) and Game.isItemThere ({x = 32479, y = 31900, z = 04},3585) and Game.isItemThere ({x = 32480, y = 31900, z = 04},3592) and Game.isItemThere ({x = 32481, y = 31900, z = 04},3589) then + Game.createItem(1948, 1, {x = 32476, y = 31904, z = 04}) + Game.removeItemOnMap({x = 32476, y = 31900, z = 04}, 3593) + Game.removeItemOnMap({x = 32477, y = 31900, z = 04}, 3587) + Game.removeItemOnMap({x = 32478, y = 31900, z = 04}, 3590) + Game.removeItemOnMap({x = 32479, y = 31900, z = 04}, 3585) + Game.removeItemOnMap({x = 32480, y = 31900, z = 04}, 3592) + Game.removeItemOnMap({x = 32481, y = 31900, z = 04}, 3589) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32479, y = 31905, z = 04}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/53.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/53.lua new file mode 100644 index 0000000..4688ca8 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/53.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32478, y = 31903, z = 03},3537) and Game.isItemThere ({x = 32479, y = 31903, z = 03},3543) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32478, y = 31903, z = 03}, 3537) + Game.removeItemOnMap({x = 32479, y = 31903, z = 03}, 3543) + Game.createItem(1948, 1, {x = 32479, y = 31904, z = 03}) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32478, y = 31904, z = 03}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32479, y = 31904, z = 03}, 1948) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/54.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/54.lua new file mode 100644 index 0000000..049778a --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/54.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32177, y = 32148, z = 11}, 1630) then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32177, y = 32148, z = 11},{x = 32178, y = 32148, z = 11}) + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1630, 1628) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1629, 1628) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1628, 1630) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/55.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/55.lua new file mode 100644 index 0000000..1a751b8 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/55.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.removeItemOnMap({x = 32145, y = 32101, z = 11}, 1791) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32145, y = 32101, z = 11},{x = 32145, y = 32102, z = 11}) + Game.createItem(1791, 1, {x = 32145, y = 32101, z = 11}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/56.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/56.lua new file mode 100644 index 0000000..1646edf --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/56.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32098, y = 32204, z = 08}, 2772, 2773) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 622, 1771) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 622, 1771) + Game.removeItemOnMap({x = 32100, y = 32205, z = 08}, 4788) + Game.removeItemOnMap({x = 32101, y = 32205, z = 08}, 4786) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32098, y = 32204, z = 08}, 2773, 2772) + doRelocate({x = 32100, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + doRelocate({x = 32101, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 1771, 622) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 1771, 622) + Game.createItem(4788, 1, {x = 32100, y = 32205, z = 08}) + Game.createItem(4786, 1, {x = 32101, y = 32205, z = 08}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/57.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/57.lua new file mode 100644 index 0000000..cb8ef6d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/57.lua @@ -0,0 +1,23 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32104, y = 32204, z = 08}, 2772, 2773) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 622, 1771) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 622, 1771) + Game.removeItemOnMap({x = 32100, y = 32205, z = 08}, 4788) + Game.removeItemOnMap({x = 32101, y = 32205, z = 08}, 4786) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32104, y = 32204, z = 08}, 2773, 2772) + doRelocate({x = 32100, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + doRelocate({x = 32101, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 1771, 622) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 1771, 622) + Game.createItem(4788, 1, {x = 32100, y = 32205, z = 08}) + Game.createItem(4786, 1, {x = 32101, y = 32205, z = 08}) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/58.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/58.lua new file mode 100644 index 0000000..0f55f98 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/58.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32095, y = 32173, z = 08}, 1271) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32095, y = 32173, z = 08},{x = 32095, y = 32174, z = 08}) + Game.createItem(1271, 1, {x = 32095, y = 32173, z = 08}) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/59.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/59.lua new file mode 100644 index 0000000..a07ce61 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/59.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32088, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32088, y = 32149, z = 10},{x = 32088, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32088, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/6.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/6.lua new file mode 100644 index 0000000..cd35aab --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/6.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32891, y = 32590, z = 11},2567) and Game.isItemThere ({x = 32843, y = 32649, z = 11},2567) and Game.isItemThere ({x = 32808, y = 32613, z = 11},2567) and Game.isItemThere ({x = 32775, y = 32583, z = 11},2567) and Game.isItemThere ({x = 32756, y = 32494, z = 11},2567) and Game.isItemThere ({x = 32799, y = 32556, z = 11},2567) and Game.isItemThere ({x = 32864, y = 32556, z = 11},1563) then + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.removeItemOnMap({x = 32864, y = 32556, z = 11}, 1563) + elseif item:getId() == 2772 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + player:sendCancelMessage("The lever won't budge.") + elseif item:getId() == 2773 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32864, y = 32556, z = 11},{x = 32864, y = 32557, z = 11}) + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.createItem(1563, 1, {x = 32864, y = 32556, z = 11}) + elseif item:getId() == 2773 and Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/60.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/60.lua new file mode 100644 index 0000000..03185a7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/60.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32090, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32090, y = 32149, z = 10},{x = 32090, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32090, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/61.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/61.lua new file mode 100644 index 0000000..2ad1717 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/61.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32092, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32092, y = 32149, z = 10},{x = 32092, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32092, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/62.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/62.lua new file mode 100644 index 0000000..6257d4b --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/62.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32094, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32094, y = 32149, z = 10},{x = 32094, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32094, y = 32149, z = 10}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/63.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/63.lua new file mode 100644 index 0000000..71da6b0 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/63.lua @@ -0,0 +1,29 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32413, y = 32230, z = 10}, 2772, 2773) + doRelocate({x = 32411, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32410, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32411, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + doRelocate({x = 32410, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32410, y = 32231, z = 10}) + Game.createItem(4788, 1, {x = 32410, y = 32232, z = 10}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32413, y = 32230, z = 10}, 2773, 2772) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32410, y = 32231, z = 10}, 4788) + Game.removeItemOnMap({x = 32410, y = 32232, z = 10}, 4788) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/64.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/64.lua new file mode 100644 index 0000000..38f9a51 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/64.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32414, y = 32252, z = 10}, 2772, 2773) + doRelocate({x = 32411, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32410, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32411, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + doRelocate({x = 32410, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32410, y = 32231, z = 10}) + Game.createItem(4788, 1, {x = 32410, y = 32232, z = 10}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32414, y = 32252, z = 10}, 2773, 2772) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32410, y = 32231, z = 10}, 4788) + Game.removeItemOnMap({x = 32410, y = 32232, z = 10}, 4788) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/65.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/65.lua new file mode 100644 index 0000000..74d5ad6 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/65.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32400, y = 32241, z = 06}, 432) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32241, z = 06}, 432, 408) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32400, y = 32241, z = 06}, 408) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32241, z = 06}, 408, 432) + doRelocate({x = 32400, y = 32241, z = 06},{x = 32400, y = 32241, z = 07}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/66.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/66.lua new file mode 100644 index 0000000..716079d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/66.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32400, y = 32239, z = 06}, 432) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32239, z = 06}, 432, 408) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32400, y = 32239, z = 06}, 408) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32239, z = 06}, 408, 432) + doRelocate({x = 32400, y = 32239, z = 06},{x = 32400, y = 32239, z = 07}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/67.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/67.lua new file mode 100644 index 0000000..5753d80 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/67.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32225, y = 32276, z = 08},{x = 32225, y = 32276, z = 09}) + Game.transformItemOnMap({x = 32225, y = 32276, z = 08}, 351, 369) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32276, z = 08}, 369, 351) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/68.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/68.lua new file mode 100644 index 0000000..4658b20 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/68.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32225, y = 32276, z = 10},{x = 32225, y = 32275, z = 10}) + Game.createItem(1949, 1, {x = 32225, y = 32276, z = 10}) + doRelocate({x = 32233, y = 32276, z = 09},{x = 32232, y = 32276, z = 09}) + Game.createItem(1949, 1, {x = 32233, y = 32276, z = 09}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32233, y = 32276, z = 09}, 1949) + Game.removeItemOnMap({x = 32225, y = 32276, z = 10}, 1949) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/69.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/69.lua new file mode 100644 index 0000000..bc67cb5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/69.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32592, y = 32104, z = 14},{x = 32591, y = 32104, z = 14}) + doRelocate({x = 32592, y = 32105, z = 14},{x = 32591, y = 32105, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32104, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32105, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32106, z = 14}) + Game.removeItemOnMap({x = 32593, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32594, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32595, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32596, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32597, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32598, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32599, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32600, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32601, y = 32103, z = 14}, 1271) + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/7.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/7.lua new file mode 100644 index 0000000..0ed5ef2 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/7.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.removeItemOnMap({x = 32864, y = 32556, z = 11}, 1563) + elseif item:getId() == 2772 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + player:sendCancelMessage("The lever won't budge.") + elseif item:getId() == 2773 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32864, y = 32556, z = 11},{x = 32864, y = 32557, z = 11}) + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.createItem(1563, 1, {x = 32864, y = 32556, z = 11}) + elseif item:getId() == 2773 then + player:sendCancelMessage("The lever won't budge.") + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/70.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/70.lua new file mode 100644 index 0000000..36e2051 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/70.lua @@ -0,0 +1,8 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(287) == 1 then + player:setStorageValue(287, 2) + Game.createItem(3233, 1, player:getPosition()) + Game.sendMagicEffect(item:getPosition(), 2) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/71.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/71.lua new file mode 100644 index 0000000..5998dc2 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/71.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getItemCount(3231) >= 1 and player:getStorageValue(288) == 1 then + Game.sendMagicEffect({x = 33094, y = 32524, z = 01}, 14) + player:removeItem(3231, 1) + Game.createItem(3243, 1, {x = 33095, y = 32524, z = 01}) + player:setStorageValue(288, 2) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/72.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/72.lua new file mode 100644 index 0000000..21cba51 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/72.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getItemCount(3231) >= 1 and player:getStorageValue(283) == 1 then + Game.sendMagicEffect({x = 33048, y = 32630, z = 01}, 14) + player:removeItem(3231, 1) + Game.createItem(3243, 1, {x = 33048, y = 32631, z = 01}) + player:setStorageValue(283, 2) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/73.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/73.lua new file mode 100644 index 0000000..f08af11 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/73.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 0 then + item:getPosition():sendMagicEffect(23) + player:setStorageValue(259, 1) + else + item:getPosition():sendMagicEffect(23) + player:setStorageValue(259, 0) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/74.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/74.lua new file mode 100644 index 0000000..62b23d5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/74.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259, 0) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/75.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/75.lua new file mode 100644 index 0000000..e3a7a91 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/75.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 4 then + item:getPosition():sendMagicEffect(24) + player:setStorageValue(259,5) + else + item:getPosition():sendMagicEffect(24) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/76.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/76.lua new file mode 100644 index 0000000..76f7273 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/76.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 2 then + item:getPosition():sendMagicEffect(25) + player:setStorageValue(259,3) + else + item:getPosition():sendMagicEffect(25) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/77.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/77.lua new file mode 100644 index 0000000..53dd344 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/77.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259,0) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/78.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/78.lua new file mode 100644 index 0000000..61b58c9 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/78.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 3 then + item:getPosition():sendMagicEffect(22) + player:setStorageValue(259,4) + else + item:getPosition():sendMagicEffect(22) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/79.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/79.lua new file mode 100644 index 0000000..1fd0e8d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/79.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 1 then + item:getPosition():sendMagicEffect(20) + player:setStorageValue(259,2) + else + item:getPosition():sendMagicEffect(20) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/8.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/8.lua new file mode 100644 index 0000000..32be86b --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/8.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/80.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/80.lua new file mode 100644 index 0000000..53dd344 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/80.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259,0) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/81.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/81.lua new file mode 100644 index 0000000..c33b962 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/81.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32354, y = 32131, z = 9}) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/82.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/82.lua new file mode 100644 index 0000000..58bd8a5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/82.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32172, y = 32439, z = 8}) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/83.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/83.lua new file mode 100644 index 0000000..80f1034 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/83.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32508, y = 32176, z = 14}) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/9.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/9.lua new file mode 100644 index 0000000..b46224e --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/9.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32790, y = 31594, z = 07},1772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32790, y = 31594, z = 07}, 1772) + elseif item:getId() == 2773 and Game.isItemThere({x = 32790, y = 31594, z = 07}, 1772) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32790, y = 31594, z = 07},{x = 32790, y = 31595, z = 07}) + Game.createItem(1772, 1, {x = 32790, y = 31594, z = 07}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/nostalrius/_.lua b/app/SabrehavenServer/data/actions/scripts/nostalrius/_.lua new file mode 100644 index 0000000..9c7ebc5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/nostalrius/_.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMazeLever.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMazeLever.lua new file mode 100644 index 0000000..32e931b --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMazeLever.lua @@ -0,0 +1,11 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local portal = Tile(Position(32816, 32345, 13)):getItemById(1949) + if not portal then + local item = Game.createItem(1949, 1, Position(32816, 32345, 13)) + item:setMovementId(17686) + else + portal:remove() + end + item:transform(item.itemid == 2772 and 2773 or 2772) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMirror.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMirror.lua new file mode 100644 index 0000000..1e569ce --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirMirror.lua @@ -0,0 +1,30 @@ +local config = { + [39511] = { + fromPosition = Position(32739, 32392, 14), + toPosition = Position(32739, 32391, 14) + }, + [39512] = { + teleportPlayer = true, + fromPosition = Position(32739, 32391, 14), + toPosition = Position(32739, 32392, 14) + } +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local useItem = config[item:getActionId()] + if not useItem then + return true + end + + if useItem.teleportPlayer then + player:teleportTo(Position(32712, 32392, 13)) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:say('Beauty has to be rewarded! Muahahaha!', TALKTYPE_MONSTER_SAY) + end + + local tapestry = Tile(useItem.fromPosition):getItemById(6433) + if tapestry then + tapestry:moveTo(useItem.toPosition) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirWrongLevers.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirWrongLevers.lua new file mode 100644 index 0000000..f2db5a7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/bazirWrongLevers.lua @@ -0,0 +1,8 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid == 2772 then + player:teleportTo(Position(32806, 32328, 15)) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + item:transform(2773) + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/fireThroneLever.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/fireThroneLever.lua new file mode 100644 index 0000000..fc0822c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/fireThroneLever.lua @@ -0,0 +1,20 @@ +local lava = { + Position(32912, 32209, 15), + Position(32913, 32209, 15), + Position(32912, 32210, 15), + Position(32913, 32210, 15) +} + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local lavaTile + for i = 1, #lava do + lavaTile = Tile(lava[i]):getGround() + if lavaTile and isInArray({410, 727}, lavaTile.itemid) then + lavaTile:transform(lavaTile.itemid == 727 and 410 or 727) + lava[i]:sendMagicEffect(CONST_ME_POFF) + end + end + + item:transform(item.itemid == 2772 and 2773 or 2772) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/hall_of_the_four_ways.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/hall_of_the_four_ways.lua new file mode 100644 index 0000000..c65d1c5 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/hall_of_the_four_ways.lua @@ -0,0 +1,37 @@ +local config = { + [17653] = { doorPosition = {x = 32833, y = 32333, z = 11}, vocationIds = {4, 8} }, + [17656] = { doorPosition = {x = 32835, y = 32333, z = 11}, vocationIds = {1, 5} }, + [17655] = { doorPosition = {x = 32831, y = 32333, z = 11}, vocationIds = {3, 7} }, + [17654] = { doorPosition = {x = 32837, y = 32333, z = 11}, vocationIds = {2, 6} } +} + +local function doTransformDoors(position) + local tile = Tile(Position(position)) + if tile then + local lockedDoor = tile:getItemById(1628) + local closedDoor = tile:getItemById(1629) + local openDoor = tile:getItemById(1630) + if lockedDoor then + lockedDoor:transform(1629, 1) + lockedDoor:decay() + elseif closedDoor then + closedDoor:transform(1628, 1) + closedDoor:decay() + elseif openDoor then + openDoor:transform(1628, 1) + openDoor:decay() + end + end +end + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local configValue = config[item:getActionId()]; + if isInArray(configValue.vocationIds, player:getVocation():getId()) then + doTransformDoors(configValue.doorPosition) + Position(configValue.doorPosition):sendMagicEffect(CONST_ME_FIREAREA) + item:transform(item.itemid == 2772 and 2773 or 2772) + return true + end + + return false +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/ladderLevers.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/ladderLevers.lua new file mode 100644 index 0000000..8963aaf --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/ladderLevers.lua @@ -0,0 +1,30 @@ +local pos = { Position(32861, 32305, 11), Position(32860, 32313, 11) } + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item:getId() ~= 2772 then + return false + end + + item:transform(2773) + + if item:getActionId() == 3301 then + local lava = Tile(pos[1]):getItemById(727) + if lava then + lava:transform(1771) + end + + local dirtId, dirtItem = { 4797, 4799 } + for i = 1, #dirtId do + dirtItem = Tile(pos[1]):getItemById(dirtId[i]) + if dirtItem then + dirtItem:remove() + end + end + elseif item:getActionId() == 3302 then + local item = Tile(pos[2]):getItemById(389) + if item then + item:remove() + end + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/levers.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/levers.lua new file mode 100644 index 0000000..53c72a7 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/levers.lua @@ -0,0 +1,62 @@ +local text = { + [1] = 'first', [2] = 'second', [3] = 'third', [4] = 'fourth', [5] = 'fifth', + [6] = 'sixth', [7] = 'seventh', [8] = 'eighth', [9] = 'ninth', [10] = 'tenth', + [11] = 'eleventh', [12] = 'twelfth', [13] = 'thirteenth', [14] = 'fourteenth', [15] = 'fifteenth' +} + +local stonePositions = { + Position(32851, 32333, 12), + Position(32852, 32333, 12) +} + +local function createStones() + for i = 1, #stonePositions do + Game.createItem(1791, 1, stonePositions[i]) + end + + setGlobalStorageValue(17657, 0) +end + +local function revertLever(position) + local leverItem = Tile(position):getItemById(2773) + if leverItem then + leverItem:transform(2772) + end +end + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item:getId() ~= 2772 then + return false + end + + local leverCount = math.max(0, getGlobalStorageValue(17657)) + if item:getActionId() > 2049 and item:getActionId() < 2065 then + local number = item:getActionId() - 2049 + if leverCount + 1 ~= number then + return false + end + + setGlobalStorageValue(17657, number) + player:say('You flipped the ' .. text[number] .. ' lever. Hurry up and find the next one!', TALKTYPE_MONSTER_SAY, false, player, toPosition) + elseif item:getActionId() == 2065 then + if leverCount ~= 15 then + player:say('The final lever won\'t budge... yet.', TALKTYPE_MONSTER_SAY) + return true + end + + local stone + for i = 1, #stonePositions do + stone = Tile(stonePositions[i]):getItemById(1791) + if stone then + stone:remove() + stonePositions[i]:sendMagicEffect(CONST_ME_EXPLOSIONAREA) + end + end + + addEvent(createStones, 15 * 60 * 1000) + end + + item:transform(2773) + addEvent(revertLever, 15 * 60 * 1000, toPosition) + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/mazeStone.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/mazeStone.lua new file mode 100644 index 0000000..a6f8a1d --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/mazeStone.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid == 2773 then + return false + end + + toPosition.x = toPosition.x - 1 + toPosition.y = toPosition.y + 1 + + local stone = Tile(toPosition):getItemById(1791) + if stone then + stone:remove() + end + + item:transform(2773) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/oil.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/oil.lua new file mode 100644 index 0000000..d217d76 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/oil.lua @@ -0,0 +1,33 @@ +local bridgePosition = Position(32801, 32336, 11) + +local function revertBridge() + Tile(bridgePosition):getItemById(409):transform(622) +end + +local function revertLever(position) + local leverItem = Tile(position):getItemById(2773) + if leverItem then + leverItem:transform(2772) + end +end + +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid ~= 2772 then + return false + end + + if not Tile(Position(32795, 32337, 11)):getItemById(2886, FLUID_OIL) then + player:say('The lever is creaking and rusty.', TALKTYPE_MONSTER_SAY) + return true + end + + local water = Tile(bridgePosition):getItemById(622) + if water then + water:transform(409) + addEvent(revertBridge, 10 * 60 * 1000) + end + + item:transform(2773) + addEvent(revertLever, 10 * 60 * 1000, toPosition) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/stoneLever.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/stoneLever.lua new file mode 100644 index 0000000..0928ba9 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/stoneLever.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item.itemid == 2772 then + local stonePosition = Position(32849, 32282, 10) + local stoneItem = Tile(stonePosition):getItemById(1791) + if stoneItem then + stoneItem:remove() + stonePosition:sendMagicEffect(CONST_ME_EXPLOSIONAREA) + item:transform(2773) + end + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/trapLever.lua b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/trapLever.lua new file mode 100644 index 0000000..9abb784 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/pits_of_inferno/trapLever.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + + item:transform(item.itemid == 2772 and 2773 or 2772) + + if item.itemid ~= 2772 then + return true + end + + local stoneItem = Tile(Position(32826, 32274, 11)):getItemById(1772) + if stoneItem then + stoneItem:remove() + end + return true +end diff --git a/app/SabrehavenServer/data/actions/scripts/serpentine_tower/behemoth_lever.lua b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/behemoth_lever.lua new file mode 100644 index 0000000..876acf9 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/behemoth_lever.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere ({x = 33148, y = 32868, z = 09}, 2129) and Game.isItemThere ({x = 33149, y = 32868, z = 09}, 2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33148, y = 32868, z = 09}, 2129) + Game.removeItemOnMap({x = 33149, y = 32868, z = 09}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 33148, y = 32868, z = 9}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33148, y = 32868, z = 09}, {x = 33148, y = 32869, z = 09}) + doRelocate({x = 33149, y = 32868, z = 09},{x = 33149, y = 32869, z = 09}) + Game.createItem(2129, 1, {x = 33148, y = 32868, z = 09}) + Game.createItem(2129, 1, {x = 33149, y = 32868, z = 09}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_behemoth.lua b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_behemoth.lua new file mode 100644 index 0000000..ec74aa6 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_behemoth.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2931 and Game.isItemThere({x = 33151, y = 32864, z = 8},1345) then + Game.removeItemOnMap({x = 33151, y = 32864, z = 8}, 1345) + Game.sendMagicEffect({x = 33146, y = 32871, z = 8}, 14) + elseif item:getId() == 2931 then + Game.sendMagicEffect({x = 33146, y = 32871, z = 8}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_fire_elemental.lua b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_fire_elemental.lua new file mode 100644 index 0000000..b517f53 --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_fire_elemental.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2929 and Game.isItemThere({x = 33151, y = 32866, z = 08},1345) then + Game.removeItemOnMap({x = 33151, y = 32866, z = 08}, 1345) + Game.sendMagicEffect({x = 33148, y = 32861, z = 8}, 14) + elseif item:getId() == 2929 then + Game.sendMagicEffect({x = 33148, y = 32861, z = 8}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_vampire.lua b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_vampire.lua new file mode 100644 index 0000000..e73ea5c --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/release_vampire.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2931 and Game.isItemThere({x = 33151, y = 32868, z = 8}, 1345) then + Game.removeItemOnMap({x = 33151, y = 32868, z = 8}, 1345) + Game.sendMagicEffect({x = 33145, y = 32865, z = 8}, 14) + elseif item:getId() == 2931 then + Game.sendMagicEffect({x = 33145, y = 32865, z = 8}, 3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/actions/scripts/serpentine_tower/vampire_lever.lua b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/vampire_lever.lua new file mode 100644 index 0000000..4d6948e --- /dev/null +++ b/app/SabrehavenServer/data/actions/scripts/serpentine_tower/vampire_lever.lua @@ -0,0 +1,7 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33145, y = 32863, z = 8}, 1650) then + Game.removeItemOnMap({x = 33145, y = 32863, z = 8}, 1650) + Game.createItem(1651, 1, {x = 33145, y = 32863, z = 8}) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/chatchannels/chatchannels.xml b/app/SabrehavenServer/data/chatchannels/chatchannels.xml new file mode 100644 index 0000000..67ae8f5 --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/chatchannels.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/SabrehavenServer/data/chatchannels/scripts/englishchat.lua b/app/SabrehavenServer/data/chatchannels/scripts/englishchat.lua new file mode 100644 index 0000000..04cf10c --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/englishchat.lua @@ -0,0 +1,22 @@ +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/gamemaster.lua b/app/SabrehavenServer/data/chatchannels/scripts/gamemaster.lua new file mode 100644 index 0000000..b3050cc --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/gamemaster.lua @@ -0,0 +1,21 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER +end + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType == ACCOUNT_TYPE_GOD then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType ~= ACCOUNT_TYPE_GOD then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType ~= ACCOUNT_TYPE_GOD and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/help.lua b/app/SabrehavenServer/data/chatchannels/scripts/help.lua new file mode 100644 index 0000000..54e7de6 --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/help.lua @@ -0,0 +1,77 @@ +local CHANNEL_HELP = 7 + +local muted = Condition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT) +muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_HELP) +muted:setParameter(CONDITION_PARAM_TICKS, 3600000) + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType == ACCOUNT_TYPE_NORMAL then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + player:sendCancelMessage("You are muted from the Help channel for using it inappropriately.") + return false + end + + if playerAccountType >= ACCOUNT_TYPE_TUTOR then + if string.sub(message, 1, 6) == "!mute " then + local targetName = string.sub(message, 7) + local target = Player(targetName) + if target ~= nil then + if playerAccountType > target:getAccountType() then + if not target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + target:addCondition(muted) + sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been muted by " .. player:getName() .. " for using Help Channel inappropriately.") + else + player:sendCancelMessage("That player is already muted.") + end + else + player:sendCancelMessage("You are not authorized to mute that player.") + end + else + player:sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + end + return false + elseif string.sub(message, 1, 8) == "!unmute " then + local targetName = string.sub(message, 9) + local target = Player(targetName) + if target ~= nil then + if playerAccountType > target:getAccountType() then + if target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + target:removeCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) + sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been unmuted by " .. player:getName() .. ".") + else + player:sendCancelMessage("That player is not muted.") + end + else + player:sendCancelMessage("You are not authorized to unmute that player.") + end + else + player:sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + end + return false + end + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_TUTOR or getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_TUTOR and not getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + if playerAccountType >= ACCOUNT_TYPE_TUTOR or getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_O + else + type = TALKTYPE_CHANNEL_Y + end + end + end + return type +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/ruleviolations.lua b/app/SabrehavenServer/data/chatchannels/scripts/ruleviolations.lua new file mode 100644 index 0000000..ea52307 --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/ruleviolations.lua @@ -0,0 +1,3 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/trade.lua b/app/SabrehavenServer/data/chatchannels/scripts/trade.lua new file mode 100644 index 0000000..73caeb7 --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/trade.lua @@ -0,0 +1,40 @@ +function canJoin(player) + return player:getVocation():getId() ~= VOCATION_NONE or player:getAccountType() >= ACCOUNT_TYPE_SENIORTUTOR +end + +local CHANNEL_TRADE = 6 + +local muted = Condition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT) +muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_TRADE) +muted:setParameter(CONDITION_PARAM_TICKS, 120000) + +function onSpeak(player, type, message) + if player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER then + if type == TALKTYPE_CHANNEL_Y then + return TALKTYPE_CHANNEL_O + end + return true + end + + if player:getLevel() == 1 then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_TRADE) then + player:sendCancelMessage("You may only place one offer in two minutes.") + return false + end + player:addCondition(muted) + + if type == TALKTYPE_CHANNEL_O then + if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/tutor.lua b/app/SabrehavenServer/data/chatchannels/scripts/tutor.lua new file mode 100644 index 0000000..8db26a8 --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/tutor.lua @@ -0,0 +1,21 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_TUTOR +end + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_SENIORTUTOR then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_SENIORTUTOR then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/app/SabrehavenServer/data/chatchannels/scripts/worldchat.lua b/app/SabrehavenServer/data/chatchannels/scripts/worldchat.lua new file mode 100644 index 0000000..04cf10c --- /dev/null +++ b/app/SabrehavenServer/data/chatchannels/scripts/worldchat.lua @@ -0,0 +1,22 @@ +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/app/SabrehavenServer/data/creaturescripts/creaturescripts.xml b/app/SabrehavenServer/data/creaturescripts/creaturescripts.xml new file mode 100644 index 0000000..fa369a3 --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/creaturescripts.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/creaturescripts/lib/creaturescripts.lua b/app/SabrehavenServer/data/creaturescripts/lib/creaturescripts.lua new file mode 100644 index 0000000..6116bcc --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/lib/creaturescripts.lua @@ -0,0 +1 @@ +-- empty file -- diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/firstitems.lua b/app/SabrehavenServer/data/creaturescripts/scripts/firstitems.lua new file mode 100644 index 0000000..e3c2197 --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/firstitems.lua @@ -0,0 +1,42 @@ +function onLogin(player) + if player:getStorageValue(17571) ~= 1 and not player:isFakePlayer() then + player:setStorageValue(17571, 1) + + -- Items + player:addItem(3355, 1, true, -1, CONST_SLOT_HEAD) + player:addItem(3361, 1, true, -1, CONST_SLOT_ARMOR) + player:addItem(3559, 1, true, -1, CONST_SLOT_LEGS) + player:addItem(3552, 1, true, -1, CONST_SLOT_FEET) + player:addItem(3412, 1, true, -1, CONST_SLOT_LEFT) + player:addItem(3007, 1, true, 100, CONST_SLOT_RING) + player:addItem(2920, 1, true, -1, CONST_SLOT_AMMO) + + if player:getVocation():getId() == 1 then + player:addItem(3074, 1, true, -1, CONST_SLOT_RIGHT) + elseif player:getVocation():getId() == 2 then + player:addItem(3066, 1, true, -1, CONST_SLOT_RIGHT) + elseif player:getVocation():getId() == 3 then + player:addItem(3277, 1, true, 5, CONST_SLOT_RIGHT) + elseif player:getVocation():getId() == 4 then + local weapons = { 3300, 3286, 3276 } + player:addItem(weapons[math.random(#weapons)], 1, true, -1, CONST_SLOT_RIGHT) + end + + local container = Game.createItem(2854, 1) + container:addItem(3585, 1) + container:addItem(3031, 25) + container:addItem(3725, 5) + container:addItem(3003, 1) + container:addItem(3457, 1) + + player:addItemEx(container, true, CONST_SLOT_BACKPACK) + + -- Default Outfit + if player:getSex() == PLAYERSEX_FEMALE then + player:setOutfit({lookType = 136, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + else + player:setOutfit({lookType = 128, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + end + end + return true +end diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/killing_in_the_name_of.lua b/app/SabrehavenServer/data/creaturescripts/scripts/killing_in_the_name_of.lua new file mode 100644 index 0000000..fd7102c --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/killing_in_the_name_of.lua @@ -0,0 +1,158 @@ +local tasks = { + -- Grizzly Adams + ['crocodile'] = {taskerStorage = 17608, progressStorage = 17609, killsRequired = 100}, + ['tarantula'] = {taskerStorage = 17608, progressStorage = 17610, killsRequired = 100}, + ['carniphila'] = {taskerStorage = 17608, progressStorage = 17611, killsRequired = 50}, + ['merlkin'] = {taskerStorage = 17608, progressStorage = 17612, killsRequired = 100}, + ['kongra'] = {taskerStorage = 17608, progressStorage = 17612, killsRequired = 100}, + ['sibang'] = {taskerStorage = 17608, progressStorage = 17612, killsRequired = 100}, + ['thornback tortoise'] = {taskerStorage = 17608, progressStorage = 17613, killsRequired = 100}, + ['gargoyle'] = {taskerStorage = 17608, progressStorage = 17614, killsRequired = 65}, + ['quara constrictor scout'] = {taskerStorage = 17608, progressStorage = 17616, killsRequired = 200}, + ['quara hydromancer scout'] = {taskerStorage = 17608, progressStorage = 17616, killsRequired = 200}, + ['quara mantassin scout'] = {taskerStorage = 17608, progressStorage = 17616, killsRequired = 200}, + ['quara pincher scout'] = {taskerStorage = 17608, progressStorage = 17616, killsRequired = 200}, + ['quara predator scout'] = {taskerStorage = 17608, progressStorage = 17616, killsRequired = 200}, + ['ancient scarab'] = {taskerStorage = 17608, progressStorage = 17617, killsRequired = 125}, + ['wyvern'] = {taskerStorage = 17608, progressStorage = 17618, killsRequired = 100}, + ['bonebeast'] = {taskerStorage = 17608, progressStorage = 17619, killsRequired = 100}, + ['dragon'] = {taskerStorage = 17608, progressStorage = 17620, killsRequired = 200}, + ['quara constrictor'] = {taskerStorage = 17608, progressStorage = 17621, killsRequired = 600}, + ['quara hydromancer'] = {taskerStorage = 17608, progressStorage = 17621, killsRequired = 600}, + ['quara mantassin'] = {taskerStorage = 17608, progressStorage = 17621, killsRequired = 600}, + ['quara pincher'] = {taskerStorage = 17608, progressStorage = 17621, killsRequired = 600}, + ['quara predator'] = {taskerStorage = 17608, progressStorage = 17621, killsRequired = 600}, + ['giant spider'] = {taskerStorage = 17608, progressStorage = 17622, killsRequired = 500}, + ['banshee'] = {taskerStorage = 17608, progressStorage = 17623, killsRequired = 300}, + ['lich'] = {taskerStorage = 17608, progressStorage = 17624, killsRequired = 500}, + ['acolyte of the cult'] = {taskerStorage = 17608, progressStorage = 17625, killsRequired = 500}, + ['adept of the cult'] = {taskerStorage = 17608, progressStorage = 17625, killsRequired = 500}, + ['enlightened of the cult'] = {taskerStorage = 17608, progressStorage = 17625, killsRequired = 500}, + ['novice of the cult'] = {taskerStorage = 17608, progressStorage = 17625, killsRequired = 500}, + ['hydra'] = {taskerStorage = 17608, progressStorage = 17626, killsRequired = 650}, + ['serpent spawn'] = {taskerStorage = 17608, progressStorage = 17627, killsRequired = 800}, + ['behemoth'] = {taskerStorage = 17608, progressStorage = 17628, killsRequired = 700}, + ['dragon lord'] = {taskerStorage = 17608, progressStorage = 17629, killsRequired = 600}, + ['hand of cursed fate'] = {taskerStorage = 17608, progressStorage = 17630, killsRequired = 200}, + ['juggernaut'] = {taskerStorage = 17608, progressStorage = 17631, killsRequired = 200}, + ['frost troll'] = {taskerStorage = 17608, progressStorage = 17697, killsRequired = 100}, + ['swamp troll'] = {taskerStorage = 17608, progressStorage = 17698, killsRequired = 100}, + ['rat'] = {taskerStorage = 17608, progressStorage = 17699, killsRequired = 25}, + ['cave rat'] = {taskerStorage = 17608, progressStorage = 17699, killsRequired = 25}, + ['wolf'] = {taskerStorage = 17608, progressStorage = 17700, killsRequired = 100}, + ['winter wolf'] = {taskerStorage = 17608, progressStorage = 17700, killsRequired = 100}, + ['wasp'] = {taskerStorage = 17608, progressStorage = 17701, killsRequired = 100}, + ['larva'] = {taskerStorage = 17608, progressStorage = 17702, killsRequired = 100}, + ['dwarf'] = {taskerStorage = 17608, progressStorage = 17703, killsRequired = 100}, + ['skeleton'] = {taskerStorage = 17608, progressStorage = 17704, killsRequired = 100}, + ['ghoul'] = {taskerStorage = 17608, progressStorage = 17704, killsRequired = 100}, + ['elf'] = {taskerStorage = 17608, progressStorage = 17729, killsRequired = 200}, + ['elf scout'] = {taskerStorage = 17608, progressStorage = 17729, killsRequired = 200}, + ['elf arcanist'] = {taskerStorage = 17608, progressStorage = 17729, killsRequired = 200}, + ['bug'] = {taskerStorage = 17608, progressStorage = 17730, killsRequired = 40}, + ['smuggler'] = {taskerStorage = 17608, progressStorage = 17731, killsRequired = 250}, + ['wild warrior'] = {taskerStorage = 17608, progressStorage = 17731, killsRequired = 250}, + ['bandit'] = {taskerStorage = 17608, progressStorage = 17731, killsRequired = 250}, + ['hyaena'] = {taskerStorage = 17608, progressStorage = 17732, killsRequired = 30}, + ['lion'] = {taskerStorage = 17608, progressStorage = 17733, killsRequired = 20}, + ['bear'] = {taskerStorage = 17608, progressStorage = 17734, killsRequired = 35}, + ['slime'] = {taskerStorage = 17608, progressStorage = 17735, killsRequired = 100}, + ['beholder'] = {taskerStorage = 17608, progressStorage = 17736, killsRequired = 250}, + ['elder beholder'] = {taskerStorage = 17608, progressStorage = 17736, killsRequired = 250}, + ['green djinn'] = {taskerStorage = 17608, progressStorage = 17737, killsRequired = 500}, + ['blue djinn'] = {taskerStorage = 17608, progressStorage = 17737, killsRequired = 500}, + ['marid'] = {taskerStorage = 17608, progressStorage = 17737, killsRequired = 500}, + ['efreet'] = {taskerStorage = 17608, progressStorage = 17737, killsRequired = 500}, + ['pirate skeleton'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['pirate marauder'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['pirate cutthroat'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['pirate ghost'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['pirate buccaneer'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['pirate corsair'] = {taskerStorage = 17608, progressStorage = 17738, killsRequired = 600}, + ['orc spearman'] = {taskerStorage = 17608, progressStorage = 17712, killsRequired = 300}, + ['orc shaman'] = {taskerStorage = 17608, progressStorage = 17712, killsRequired = 300}, + ['orc rider'] = {taskerStorage = 17608, progressStorage = 17712, killsRequired = 300}, + ['orc warrior'] = {taskerStorage = 17608, progressStorage = 17712, killsRequired = 300}, + ['orc berserker'] = {taskerStorage = 17608, progressStorage = 17712, killsRequired = 300}, + ['minotaur archer'] = {taskerStorage = 17608, progressStorage = 17713, killsRequired = 300}, + ['minotaur guard'] = {taskerStorage = 17608, progressStorage = 17713, killsRequired = 300}, + ['minotaur mage'] = {taskerStorage = 17608, progressStorage = 17713, killsRequired = 300}, + ['lizard templar'] = {taskerStorage = 17608, progressStorage = 17714, killsRequired = 300}, + ['lizard sentinel'] = {taskerStorage = 17608, progressStorage = 17714, killsRequired = 300}, + ['lizard snakecharmer'] = {taskerStorage = 17608, progressStorage = 17714, killsRequired = 300}, + ['dwarf soldier'] = {taskerStorage = 17608, progressStorage = 17715, killsRequired = 300}, + ['dwarf guard'] = {taskerStorage = 17608, progressStorage = 17715, killsRequired = 300}, + ['dwarf geomancer'] = {taskerStorage = 17608, progressStorage = 17715, killsRequired = 300}, + ['ghost'] = {taskerStorage = 17608, progressStorage = 17716, killsRequired = 200}, + ['demon skeleton'] = {taskerStorage = 17608, progressStorage = 17716, killsRequired = 200}, + ['vampire'] = {taskerStorage = 17608, progressStorage = 17716, killsRequired = 200}, + ['orc leader'] = {taskerStorage = 17608, progressStorage = 17717, killsRequired = 125}, + ['orc warlord'] = {taskerStorage = 17608, progressStorage = 17717, killsRequired = 125}, + ['hero'] = {taskerStorage = 17608, progressStorage = 17718, killsRequired = 150}, + ['necromancer'] = {taskerStorage = 17608, progressStorage = 17719, killsRequired = 300}, + ['priestess'] = {taskerStorage = 17608, progressStorage = 17719, killsRequired = 300}, + ['nightmare'] = {taskerStorage = 17608, progressStorage = 17720, killsRequired = 150}, + ['warlock'] = {taskerStorage = 17608, progressStorage = 17721, killsRequired = 300}, + ['demon'] = {taskerStorage = 17608, progressStorage = 17722, killsRequired = 6666}, + + -- Daniel Steelsoul + ['troll'] = {taskerStorage = 17632, progressStorage = 17633, killsRequired = 100}, + ['goblin'] = {taskerStorage = 17632, progressStorage = 17634, killsRequired = 150}, + ['rotworm'] = {taskerStorage = 17632, progressStorage = 17635, killsRequired = 150}, + ['carrion worm'] = {taskerStorage = 17632, progressStorage = 17635, killsRequired = 150}, + ['cyclops'] = {taskerStorage = 17632, progressStorage = 17636, killsRequired = 150}, + + -- Young Vocation Tasks + ['amazon'] = {taskerStorage = 17644, progressStorage = 17645, killsRequired = 50}, + ['minotaur'] = {taskerStorage = 17649, progressStorage = 17648, killsRequired = 50}, + ['orc'] = {taskerStorage = 17652, progressStorage = 17651, killsRequired = 50}, +} + +local maxPlayersInPartyShare = 10 + +function onDeath(creature, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified) + if not creature:isMonster() or creature:getMaster() then + return true + end + + if mostdamagekiller == nil then + return true + end + + local player = mostdamagekiller + if not mostdamagekiller:isPlayer() then + local master = mostdamagekiller:getMaster() + if master and master:isPlayer() then + player = master + else + return true + end + end + + local targetName = creature:getName():lower() + local task = tasks[targetName] + if task ~= nil then + local players + local party = player:getParty() + if party ~= nil and party:isSharedExperienceActive() then + players = party:getMembers() -- all members of the party + players[#players + 1] = party:getLeader() -- don't forget the leader + else + players = { player } -- no party? then just the player + end + + for i, member in ipairs(players) do + if i <= maxPlayersInPartyShare then + local inProgressQuest = member:getStorageValue(task.taskerStorage) + if inProgressQuest == task.progressStorage then + local playerQuestKills = member:getStorageValue(task.progressStorage) + if playerQuestKills < task.killsRequired then + member:setStorageValue(task.progressStorage, playerQuestKills + 1) + member:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, "[Task Tracker] You have killed " .. playerQuestKills + 1 .. "/" .. task.killsRequired .. " " .. targetName .. ".") + end + end + end + end + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/login.lua b/app/SabrehavenServer/data/creaturescripts/scripts/login.lua new file mode 100644 index 0000000..5d1824c --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/login.lua @@ -0,0 +1,326 @@ +local fakePlayers = { + {x = 32864, y = 31997, z = 7}, + {x = 32864, y = 31998, z = 7}, + {x = 32865, y = 31999, z = 7}, + {x = 32871, y = 31999, z = 7}, + {x = 32872, y = 32000, z = 7}, + {x = 32825, y = 32065, z = 7}, + {x = 32828, y = 32065, z = 7}, + {x = 32885, y = 32050, z = 7}, + {x = 32885, y = 32049, z = 7}, + {x = 32823, y = 31887, z = 7}, + {x = 32691, y = 31716, z = 7}, + {x = 32705, y = 31787, z = 7}, + {x = 32716, y = 31928, z = 7}, + {x = 32880, y = 31845, z = 7}, + {x = 32879, y = 31845, z = 7}, + {x = 32882, y = 31840, z = 7}, + {x = 32655, y = 32109, z = 8}, + {x = 32665, y = 32109, z = 8}, + {x = 32254, y = 32019, z = 7}, + {x = 32255, y = 32020, z = 7}, + {x = 32245, y = 32021, z = 7}, + {x = 32238, y = 32330, z = 7}, + {x = 32238, y = 32331, z = 7}, + {x = 32273, y = 32396, z = 7}, + {x = 32465, y = 32315, z = 7}, + {x = 32225, y = 31740, z = 7}, + {x = 32230, y = 31739, z = 7}, + {x = 32257, y = 31838, z = 7}, + {x = 32258, y = 31840, z = 7}, + {x = 32258, y = 31844, z = 7}, + {x = 32260, y = 31846, z = 7}, + {x = 32261, y = 31848, z = 7}, + {x = 32231, y = 31701, z = 7}, + {x = 32516, y = 31599, z = 7}, + {x = 32486, y = 31590, z = 7}, + {x = 32493, y = 31590, z = 7}, + {x = 32503, y = 31670, z = 7}, + {x = 32579, y = 31928, z = 0}, + {x = 32596, y = 31922, z = 0}, + {x = 32621, y = 31921, z = 1}, + {x = 32651, y = 31942, z = 7}, + {x = 32531, y = 32721, z = 7}, + {x = 32531, y = 32720, z = 7}, + {x = 32530, y = 32720, z = 7}, + {x = 32537, y = 32813, z = 7}, + {x = 32537, y = 32814, z = 7}, + {x = 32537, y = 32759, z = 7}, + {x = 32538, y = 32759, z = 7}, + {x = 33275, y = 32829, z = 7}, + {x = 33238, y = 32555, z = 7}, + {x = 33237, y = 32555, z = 7}, + {x = 33187, y = 32343, z = 7}, + {x = 33187, y = 32342, z = 7}, + {x = 33152, y = 32354, z = 7}, + {x = 33154, y = 32354, z = 7}, + {x = 33076, y = 32345, z = 7}, + {x = 32655, y = 31648, z = 10}, + {x = 32656, y = 31648, z = 10}, + {x = 32657, y = 31648, z = 10}, + {x = 33262, y = 31866, z = 7}, + {x = 33263, y = 31866, z = 7}, + {x = 33244, y = 31904, z = 7}, + {x = 33245, y = 31903, z = 7}, + {x = 33219, y = 31926, z = 7}, + {x = 33298, y = 31839, z = 7}, + {x = 33298, y = 31838, z = 7}, + {x = 33358, y = 31692, z = 9}, + {x = 33359, y = 31692, z = 9}, + {x = 33360, y = 31692, z = 9}, + {x = 33362, y = 31696, z = 9}, + {x = 33366, y = 31696, z = 9}, + {x = 33318, y = 31728, z = 7}, + {x = 33318, y = 31729, z = 7}, + {x = 33273, y = 31680, z = 7}, + {x = 33169, y = 31737, z = 7}, + {x = 33274, y = 31791, z = 6}, + {x = 33321, y = 32419, z = 7}, + {x = 33318, y = 32406, z = 7}, + {x = 32372, y = 32840, z = 7}, + {x = 32371, y = 32839, z = 7}, + {x = 32369, y = 32838, z = 7}, + {x = 32347, y = 32691, z = 7}, + {x = 32348, y = 32692, z = 7}, + {x = 32157, y = 32783, z = 7}, + {x = 32225, y = 32880, z = 7}, + {x = 32386, y = 32695, z = 7}, + {x = 32314, y = 32830, z = 8}, + {x = 32593, y = 31885, z = 12}, + {x = 32593, y = 31884, z = 12}, + {x = 32586, y = 31919, z = 10}, + {x = 32547, y = 31921, z = 10}, + {x = 32763, y = 31941, z = 7}, + {x = 32763, y = 31942, z = 7}, + {x = 32700, y = 31842, z = 7}, + {x = 32700, y = 31841, z = 7}, + {x = 32991, y = 32377, z = 7}, + {x = 32314, y = 32282, z = 7}, + {x = 32659, y = 31632, z = 15}, + {x = 32660, y = 31632, z = 15}, + {x = 32661, y = 31632, z = 15}, + {x = 32663, y = 31632, z = 15}, + {x = 32661, y = 31634, z = 15}, + {x = 32714, y = 31649, z = 15}, + {x = 32715, y = 31649, z = 15}, + {x = 32716, y = 31649, z = 15}, + {x = 32717, y = 31649, z = 15}, + {x = 32577, y = 31601, z = 11}, + {x = 32577, y = 31602, z = 11}, + {x = 32577, y = 31603, z = 11}, + {x = 32577, y = 31604, z = 11}, + {x = 32577, y = 31605, z = 11}, + {x = 32602, y = 31611, z = 11}, + {x = 32601, y = 31611, z = 11}, + {x = 32600, y = 31610, z = 11}, + {x = 32599, y = 31610, z = 11}, + {x = 32598, y = 31609, z = 11}, + {x = 32598, y = 31608, z = 11}, + {x = 32604, y = 31670, z = 7}, + {x = 32605, y = 31670, z = 7}, + {x = 32606, y = 31670, z = 7}, + {x = 32607, y = 31670, z = 7}, + {x = 32608, y = 31670, z = 7}, + {x = 32609, y = 31670, z = 7}, + {x = 32537, y = 31772, z = 4}, + {x = 32537, y = 31772, z = 3}, + {x = 32382, y = 32130, z = 10}, + {x = 32410, y = 32123, z = 10}, + {x = 32408, y = 32123, z = 10}, + {x = 32445, y = 32213, z = 8}, + {x = 32445, y = 32212, z = 8}, + {x = 32444, y = 32210, z = 8}, + {x = 32392, y = 31805, z = 8}, + {x = 32127, y = 31660, z = 8}, + {x = 32127, y = 31659, z = 8}, + {x = 32187, y = 31623, z = 4}, + {x = 32188, y = 31623, z = 4}, + {x = 32189, y = 31623, z = 4}, + {x = 32189, y = 31624, z = 4}, + {x = 32187, y = 31625, z = 4}, + {x = 32190, y = 31656, z = 7}, + {x = 32030, y = 31691, z = 7}, + {x = 32030, y = 31692, z = 7}, + {x = 32030, y = 31693, z = 7}, + {x = 31960, y = 31583, z = 7}, + {x = 31960, y = 31584, z = 7}, + {x = 31961, y = 31585, z = 7}, + {x = 31961, y = 31582, z = 7}, + {x = 32029, y = 31536, z = 10}, + {x = 32258, y = 31641, z = 7}, + {x = 32316, y = 31747, z = 2}, + {x = 32317, y = 31747, z = 2}, + {x = 32910, y = 32085, z = 5}, + {x = 33021, y = 32046, z = 5}, + {x = 32978, y = 32254, z = 7}, + {x = 32977, y = 32254, z = 7}, + {x = 32976, y = 32254, z = 7}, + {x = 32975, y = 32254, z = 7}, + {x = 32974, y = 32254, z = 7}, + {x = 32950, y = 32271, z = 7}, + {x = 32952, y = 32270, z = 7}, + {x = 32951, y = 32270, z = 7}, + {x = 32953, y = 32264, z = 7}, + {x = 32383, y = 32852, z = 6}, + {x = 32393, y = 32838, z = 0}, + {x = 32393, y = 32839, z = 0}, + {x = 32393, y = 32840, z = 0}, + {x = 32572, y = 31875, z = 10}, + {x = 32571, y = 31875, z = 10}, + {x = 32724, y = 31975, z = 6}, + {x = 32801, y = 31861, z = 6}, + {x = 32800, y = 31862, z = 6}, + {x = 32801, y = 31862, z = 6}, + {x = 32801, y = 31863, z = 6}, + {x = 32800, y = 31863, z = 6}, + {x = 33227, y = 32389, z = 5}, + {x = 33228, y = 32389, z = 5}, + {x = 33329, y = 32171, z = 5}, + {x = 33330, y = 32171, z = 5}, + {x = 33305, y = 31991, z = 6}, + {x = 33305, y = 31992, z = 6}, + {x = 33311, y = 31990, z = 6}, + {x = 33312, y = 31990, z = 6}, + {x = 33361, y = 32048, z = 7}, + {x = 33363, y = 32047, z = 7}, + {x = 33364, y = 32045, z = 7}, + {x = 33359, y = 32046, z = 7}, + {x = 33331, y = 32056, z = 7}, + {x = 33332, y = 32055, z = 7}, + {x = 33335, y = 32054, z = 7}, + {x = 33334, y = 32049, z = 7}, + {x = 33313, y = 31946, z = 7}, + {x = 33314, y = 31882, z = 7}, + {x = 33280, y = 31842, z = 8}, + {x = 33226, y = 32869, z = 7}, + {x = 33204, y = 31909, z = 7}, + {x = 33218, y = 31924, z = 7}, + {x = 33220, y = 31924, z = 7} +} + +local fakePlayerOutfits = { + [1] = {136, 137, 138, 139, 140, 141, 142, 147, 148, 149, 150}, -- female outfits + [2] = {128, 129, 130, 131, 132, 133, 134, 143, 144, 145, 146} -- male outfits +} + +local fakePlayerRunes = { + [1] = { id = 3189, count = 3}, + [2] = { id = 3152, count = 1}, + [3] = { id = 3198, count = 5} +} + +function onLogin(player) + local loginStr = "Welcome to " .. configManager.getString(configKeys.SERVER_NAME) .. "!" + if player:getLastLoginSaved() <= 0 then + loginStr = loginStr .. " Please choose your outfit." + player:sendOutfitWindow() + else + if loginStr ~= "" then + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + end + + loginStr = string.format("Your last visit on " .. configManager.getString(configKeys.SERVER_NAME) .. ": %s.", os.date("%a %b %d %X %Y", player:getLastLoginSaved())) + end + + if not player:isPremium() then + local dayNow = tonumber(os.date("%d", os.time())) + local hourNow = tonumber(os.date("%H", os.time())) + if dayNow == 8 and hourNow == 20 then + player:addPremiumDays(5) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Congratulations " .. player:getName() .. " on starting your adventure at the " .. configManager.getString(configKeys.SERVER_NAME) .. "! 5 premium days have been added to your account!") + elseif dayNow == 8 then + player:addPremiumDays(2) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Congratulations " .. player:getName() .. " on starting your adventure at the " .. configManager.getString(configKeys.SERVER_NAME) .. "! 2 premium days have been added to your account!") + end + end + + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + + -- Stamina + nextUseStaminaTime[player.uid] = 0 + + -- Promotion + if player:getVocation():getId() ~= 0 and player:getVocation():getId() < 5 and player:getStorageValue(30018) == 1 then + player:setVocation(player:getVocation():getId() + 4) + end + + -- Premium system + if player:isPremium() then + player:setStorageValue(43434, 1) + elseif player:getStorageValue(43434) == 1 then + player:setStorageValue(43434, 0) + end + + -- FakePlayer + if player:isFakePlayer() then + local inFightCondition = Condition(CONDITION_INFIGHT) + inFightCondition:setParameter(CONDITION_PARAM_TICKS, 2000 * 60 * 1000) + player:addCondition(inFightCondition) + + player:addManaSpent(2500) + if player:getLevel() <= 17 then + if player:getVocation():getId() == 2 then + player:addExperience(math.random(2500, 11000), false) + else + player:addExperience(math.random(9000, 11000), false) + end + end + + if player:getItemCount(3578) <= 0 then + local randomFakePlayerPosition = math.random(1, #fakePlayers) + player:teleportTo(fakePlayers[randomFakePlayerPosition]) + Game.sendMagicEffect(fakePlayers[randomFakePlayerPosition], 11) + table.remove(fakePlayers, randomFakePlayerPosition) + + local container = Game.createItem(2854, 1) + container:addItem(3578, math.random(5, 15)) + if math.random(5, 15) >= 10 then + container:addItem(3483, 1) + end + if math.random(5, 15) >= 5 then + container:addItem(3003, 1) + end + + player:addItemEx(container, true, CONST_SLOT_BACKPACK) + + if player:getVocation():getId() == 1 or player:getVocation():getId() == 2 or player:getVocation():getId() == 3 then + local backpackCount = math.random(1, 2) + local runeCount = math.random(5, 20) * backpackCount + for i=1,backpackCount do + local bp = Game.createItem(2854, 1) + for i=1,20 do + if runeCount <= 0 then + bp:addItem(3147, 1) + else + bp:addItem(fakePlayerRunes[player:getVocation():getId()].id, fakePlayerRunes[player:getVocation():getId()].count) + runeCount = runeCount - 1 + end + end + + player:addItemEx(bp, true, CONST_SLOT_BACKPACK) + end + end + end + + if player:getStorageValue(17740) ~= 1 then + local fakeOutfitTypes = fakePlayerOutfits[player:getSex() + 1] + player:setOutfit({ + lookType = fakeOutfitTypes[math.random(#fakeOutfitTypes)], + lookHead = math.random(0, 132), + lookBody = math.random(0, 132), + lookLegs = math.random(0, 132), + lookFeet = math.random(0, 132) + }) + player:setStorageValue(17740, 1) + end + end + + -- Events + player:registerEvent("PlayerDeath") + player:registerEvent("kills") + player:registerEvent("PlayerLogout") + player:registerEvent("FirstItems") + player:registerEvent("RegenerateStamina") + + return true +end diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/logout.lua b/app/SabrehavenServer/data/creaturescripts/scripts/logout.lua new file mode 100644 index 0000000..7c83ff7 --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/logout.lua @@ -0,0 +1,7 @@ +function onLogout(player) + local playerId = player:getId() + if nextUseStaminaTime[playerId] then + nextUseStaminaTime[playerId] = nil + end + return true +end diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/offlinetraining.lua b/app/SabrehavenServer/data/creaturescripts/scripts/offlinetraining.lua new file mode 100644 index 0000000..9767a9a --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/offlinetraining.lua @@ -0,0 +1,76 @@ +function onLogin(player) + local lastLogout = player:getLastLogout() + local offlineTime = lastLogout ~= 0 and math.min(os.time() - lastLogout, 86400 * 21) or 0 + local offlineTrainingSkill = player:getOfflineTrainingSkill() + if offlineTrainingSkill == -1 then + player:addOfflineTrainingTime(offlineTime * 1000) + return true + end + + player:setOfflineTrainingSkill(-1) + + if offlineTime < 600 then + player:setBankBalance(player:getBankBalance() + 1000) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be logged out for more than 10 minutes to start offline training. Your 1000 gold coins entree fee were returned to your bank account.") + return true + end + + local trainingTime = math.max(0, math.min(offlineTime, math.min(43200 / 2, player:getOfflineTrainingTime() / 1000))) + player:removeOfflineTrainingTime(trainingTime * 1000) + + local remainder = offlineTime - trainingTime + if remainder > 0 then + player:addOfflineTrainingTime(remainder * 1000) + end + + if trainingTime < 60 then + return true + end + + local text = "During your absence you trained for" + local hours = math.floor(trainingTime / 3600) + if hours > 1 then + text = string.format("%s %d hours", text, hours) + elseif hours == 1 then + text = string.format("%s 1 hour", text) + end + + local minutes = math.floor((trainingTime % 3600) / 60) + if minutes ~= 0 then + if hours ~= 0 then + text = string.format("%s and", text) + end + + if minutes > 1 then + text = string.format("%s %d minutes", text, minutes) + else + text = string.format("%s 1 minute", text) + end + end + + text = string.format("%s.", text) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, text) + + local vocation = player:getVocation() + local promotion = vocation:getPromotion() + local topVocation = not promotion and vocation or promotion + + local updateSkills = false + if table.contains({SKILL_CLUB, SKILL_SWORD, SKILL_AXE, SKILL_DISTANCE}, offlineTrainingSkill) then + local modifier = topVocation:getAttackSpeed() / 1000 + updateSkills = player:addOfflineTrainingTries(offlineTrainingSkill, (trainingTime / modifier) / (offlineTrainingSkill == SKILL_DISTANCE and 8 or 4)) + elseif offlineTrainingSkill == SKILL_MAGLEVEL then + local gainTicks = topVocation:getManaGainTicks() * 4 + if gainTicks == 0 then + gainTicks = 1 + end + + updateSkills = player:addOfflineTrainingTries(SKILL_MAGLEVEL, trainingTime * (vocation:getManaGainAmount() / gainTicks)) + end + + if updateSkills then + player:addOfflineTrainingTries(SKILL_SHIELD, trainingTime / 8) + end + + return true +end diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/playerdeath.lua b/app/SabrehavenServer/data/creaturescripts/scripts/playerdeath.lua new file mode 100644 index 0000000..8aa8408 --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/playerdeath.lua @@ -0,0 +1,85 @@ +local deathListEnabled = true +local maxDeathRecords = 50 + +function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) + local playerId = player:getId() + if nextUseStaminaTime[playerId] then + nextUseStaminaTime[playerId] = nil + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") + + -- restart blessings values + player:setStorageValue(101,0) + player:setStorageValue(102,0) + player:setStorageValue(103,0) + player:setStorageValue(104,0) + player:setStorageValue(105,0) + + if not deathListEnabled then + return + end + + local byPlayer = false + local killerName + if killer ~= nil then + if killer:isPlayer() then + byPlayer = true + else + local master = killer:getMaster() + if master and master ~= killer and master:isPlayer() then + killer = master + byPlayer = true + end + end + killerName = killer:getName() + else + killerName = "field item" + end + + local byPlayerMostDamage = 0 + local mostDamageKillerName + if mostDamageKiller ~= nil then + if mostDamageKiller:isPlayer() then + byPlayerMostDamage = 1 + else + local master = mostDamageKiller:getMaster() + if master and master ~= mostDamageKiller and master:isPlayer() then + mostDamageKiller = master + byPlayerMostDamage = 1 + end + end + mostDamageName = mostDamageKiller:getName() + else + mostDamageName = "field item" + end + + local playerGuid = player:getGuid() + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. (byPlayer and 1 or 0) .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (unjustified and 1 or 0) .. ", " .. (mostDamageUnjustified and 1 or 0) .. ")") + local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) + + local deathRecords = 0 + local tmpResultId = resultId + while tmpResultId ~= false do + tmpResultId = result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId ~= false then + result.free(resultId) + end + + local limit = deathRecords - maxDeathRecords + if limit > 0 then + db.asyncQuery("DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. " ORDER BY `time` LIMIT " .. limit) + end + + if not byPlayer then + return + end + + local warId = guildwars:isInWar(killer, player) + if warId ~= 0 then + guildwars:processKill(warId, killer, player) + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/regeneratestamina.lua b/app/SabrehavenServer/data/creaturescripts/scripts/regeneratestamina.lua new file mode 100644 index 0000000..02aef45 --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/regeneratestamina.lua @@ -0,0 +1,27 @@ +function onLogin(player) + if not configManager.getBoolean(configKeys.STAMINA_SYSTEM) then + return true + end + + local lastLogout = player:getLastLogout() + local offlineTime = lastLogout ~= 0 and math.min(os.time() - lastLogout, 86400 * 21) or 0 + offlineTime = offlineTime - 600 + + if offlineTime < 180 then + return true + end + + local staminaMinutes = player:getStamina() + local maxNormalStaminaRegen = 3240 - math.min(3240, staminaMinutes) + + local regainStaminaMinutes = offlineTime / 180 + if regainStaminaMinutes > maxNormalStaminaRegen then + local happyHourStaminaRegen = (offlineTime - (maxNormalStaminaRegen * 180)) / 600 + staminaMinutes = math.min(3360, math.max(3240, staminaMinutes) + happyHourStaminaRegen) + else + staminaMinutes = staminaMinutes + regainStaminaMinutes + end + + player:setStamina(staminaMinutes) + return true +end diff --git a/app/SabrehavenServer/data/creaturescripts/scripts/syncoutfit.lua b/app/SabrehavenServer/data/creaturescripts/scripts/syncoutfit.lua new file mode 100644 index 0000000..ada481d --- /dev/null +++ b/app/SabrehavenServer/data/creaturescripts/scripts/syncoutfit.lua @@ -0,0 +1,39 @@ +-- Sync outfits that player own with Znote AAC +-- So its possible to see which full sets player +-- has in characterprofile.php + +znote_outfit_list = { + { -- Female (girl) outfits + 136,137,138,139,140,141,142,147,148, + 149,150,155,156,157,158,252,269,270, + 279,288,324,329,336,366,431,433,464, + 466,471,513,514,542,575,578,618,620, + 632,635,636,664,666,683,694,696,698, + 724,732,745,749,759,845,852,874,885, + 900 + }, + { -- Male (boy) outfits + 128,129,130,131,132,133,134,143,144, + 145,146,151,152,153,154,251,268,273, + 278,289,325,328,335,367,430,432,463, + 465,472,512,516,541,574,577,610,619, + 633,634,637,665,667,684,695,697,699, + 725,733,746,750,760,846,853,873,884, + 899 + } +} + +function onLogin(player) + -- storage_value + 1000 storages (highest outfit id) must not be used in other script. + -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value + local storage_value = 10000 + -- Loop through outfits + for _, outfit in pairs(znote_outfit_list[player:getSex()+1]) do + if player:hasOutfit(outfit,3) then + if player:getStorageValue(storage_value + outfit) ~= 3 then + player:setStorageValue(storage_value + outfit, 3) + end + end + end + return true +end diff --git a/app/SabrehavenServer/data/events/events.xml b/app/SabrehavenServer/data/events/events.xml new file mode 100644 index 0000000..1b75858 --- /dev/null +++ b/app/SabrehavenServer/data/events/events.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/events/scripts/creature.lua b/app/SabrehavenServer/data/events/scripts/creature.lua new file mode 100644 index 0000000..ac4ff89 --- /dev/null +++ b/app/SabrehavenServer/data/events/scripts/creature.lua @@ -0,0 +1,11 @@ +function Creature:onChangeOutfit(outfit) + return true +end + +function Creature:onAreaCombat(tile, isAggressive) + return RETURNVALUE_NOERROR +end + +function Creature:onTargetCombat(target) + return RETURNVALUE_NOERROR +end diff --git a/app/SabrehavenServer/data/events/scripts/party.lua b/app/SabrehavenServer/data/events/scripts/party.lua new file mode 100644 index 0000000..1a6b561 --- /dev/null +++ b/app/SabrehavenServer/data/events/scripts/party.lua @@ -0,0 +1,35 @@ +function Party:onJoin(player) + return true +end + +function Party:onLeave(player) + return true +end + +function Party:onDisband() + return true +end + +function Party:onShareExperience(exp) + local sharedExperienceMultiplier = 1.20 --20% + local vocationsIds = {} + + 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 + + return (exp * sharedExperienceMultiplier) / (#self:getMembers() + 1) +end diff --git a/app/SabrehavenServer/data/events/scripts/player.lua b/app/SabrehavenServer/data/events/scripts/player.lua new file mode 100644 index 0000000..de33ecb --- /dev/null +++ b/app/SabrehavenServer/data/events/scripts/player.lua @@ -0,0 +1,379 @@ +local monsterOfTheDay = { + [8430] = { monster = 'Crocodile' }, + [8431] = { monster = 'Tarantula' }, + [8432] = { monster = 'Carniphila' }, + [8433] = { monster = 'Merlkin' }, + [8434] = { monster = 'Kongra' }, + [8435] = { monster = 'Sibang' }, + [8436] = { monster = 'Thornback Tortoise' }, + [8437] = { monster = 'Gargoyle' }, + [8438] = { monster = 'Quara Constrictor Scout' }, + [8439] = { monster = 'Quara Hydromancer Scout' }, + [8440] = { monster = 'Quara Mantassin Scout' }, + [8441] = { monster = 'Quara Pincher Scout' }, + [8442] = { monster = 'Quara Predator Scout' }, + [8443] = { monster = 'Ancient Scarab' }, + [8444] = { monster = 'Wyvern' }, + [8445] = { monster = 'Bonebeast' }, + [8446] = { monster = 'Dragon' }, + [8447] = { monster = 'Quara Constrictor' }, + [8448] = { monster = 'Quara Hydromancer' }, + [8449] = { monster = 'Quara Mantassin' }, + [8450] = { monster = 'Quara Pincher' }, + [8451] = { monster = 'Quara Predator' }, + [8452] = { monster = 'Giant Spider' }, + [8453] = { monster = 'Banshee' }, + [8454] = { monster = 'Lich' }, + [8455] = { monster = 'Acolyte of The Cult' }, + [8456] = { monster = 'Adept of The Cult' }, + [8457] = { monster = 'Enlightened of The Cult' }, + [8458] = { monster = 'Novice of The Cult' }, + [8459] = { monster = 'Hydra' }, + [8460] = { monster = 'Serpent Spawn' }, + [8461] = { monster = 'Behemoth' }, + [8462] = { monster = 'Dragon Lord' }, + [8463] = { monster = 'Hand of Cursed Fate' }, + [8464] = { monster = 'Juggernaut' }, + [8465] = { monster = 'Frost Troll' }, + [8466] = { monster = 'Swamp Troll' }, + [8467] = { monster = 'Rat' }, + [8468] = { monster = 'Cave Rat' }, + [8469] = { monster = 'Wolf' }, + [8470] = { monster = 'Winter Wolf' }, + [8471] = { monster = 'Wasp' }, + [8472] = { monster = 'Larva' }, + [8473] = { monster = 'Dwarf' }, + [8474] = { monster = 'Skeleton' }, + [8475] = { monster = 'Ghoul' }, + [8476] = { monster = 'Elf' }, + [8477] = { monster = 'Elf Scout' }, + [8478] = { monster = 'Elf Arcanist' }, + [8479] = { monster = 'Bug' }, + [8480] = { monster = 'Smuggler' }, + [8481] = { monster = 'Wild Warrior' }, + [8482] = { monster = 'Bandit' }, + [8483] = { monster = 'Hyaena' }, + [8484] = { monster = 'Lion' }, + [8485] = { monster = 'Bear' }, + [8486] = { monster = 'Slime' }, + [8487] = { monster = 'Beholder' }, + [8488] = { monster = 'Elder Beholder' }, + [8489] = { monster = 'Green Djinn' }, + [8490] = { monster = 'Blue Djinn' }, + [8491] = { monster = 'Marid' }, + [8492] = { monster = 'Efreet' }, + [8493] = { monster = 'Pirate Skeleton' }, + [8494] = { monster = 'Pirate Marauder' }, + [8495] = { monster = 'Pirate Cutthroat' }, + [8496] = { monster = 'Pirate Ghost' }, + [8497] = { monster = 'Pirate Buccaneer' }, + [8498] = { monster = 'Pirate Corsair' }, + [8499] = { monster = 'Orc Spearman' }, + [8500] = { monster = 'Orc Shaman' }, + [8501] = { monster = 'Orc Rider' }, + [8502] = { monster = 'Orc Warrior' }, + [8503] = { monster = 'Orc Berserker' }, + [8504] = { monster = 'Minotaur Archer' }, + [8505] = { monster = 'Minotaur Guard' }, + [8506] = { monster = 'Minotaur Mage' }, + [8507] = { monster = 'Lizard Templar' }, + [8508] = { monster = 'Lizard Sentinel' }, + [8509] = { monster = 'Lizard Snakecharmer' }, + [8510] = { monster = 'Dwarf Soldier' }, + [8511] = { monster = 'Dwarf Guard' }, + [8512] = { monster = 'Dwarf Geomancer' }, + [8513] = { monster = 'Ghost' }, + [8514] = { monster = 'Demon Skeleton' }, + [8515] = { monster = 'Vampire' }, + [8516] = { monster = 'Orc Leader' }, + [8517] = { monster = 'Orc Warlord' }, + [8518] = { monster = 'Hero' }, + [8519] = { monster = 'Necromancer' }, + [8520] = { monster = 'Priestess' }, + [8521] = { monster = 'Nightmare' }, + [8522] = { monster = 'Warlock' }, + [8523] = { monster = 'Demon' }, + [8524] = { monster = 'Troll' }, + [8525] = { monster = 'Goblin' }, + [8526] = { monster = 'Rotworm' }, + [8527] = { monster = 'Carrion Worm' }, + [8528] = { monster = 'Cyclops' }, + [8529] = { monster = 'Amazon' }, + [8530] = { monster = 'Minotaur' }, + [8531] = { monster = 'Orc' } +} + +local trainingStatues = {17724, 17725, 17726, 17727, 17728} + +function Player:onLook(thing, position, distance) + local description = "You see " .. thing:getDescription(distance) + + if thing:isItem() and thing:getId() == 2028 then + if isInArray(trainingStatues, thing:getActionId()) then + local trainingTime = math.min(43200 / 2, self:getOfflineTrainingTime() / 1000) + local text = "You have" + local hours = math.floor(trainingTime / 3600) + if hours > 1 then + text = string.format("%s %d hours", text, hours) + elseif hours == 1 then + text = string.format("%s 1 hour", text) + end + + local minutes = math.floor((trainingTime % 3600) / 60) + if minutes ~= 0 then + if hours ~= 0 then + text = string.format("%s and", text) + end + + if minutes > 1 then + text = string.format("%s %d minutes", text, minutes) + else + text = string.format("%s 1 minute", text) + end + end + + text = string.format(" %s offline training time remaining.", text) + + description = string.format(description .. "%s", text) + end + end + + 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_MOVEMENTID) + if uniqueId > 0 and uniqueId < 65536 then + description = string.format("%s, Movement 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 + if thing:getAttribute(ITEM_ATTRIBUTE_DECAYSTATE) == 1 then + description = string.format("%s\nDecaying in %d minutes (%d seconds).", description, thing:getAttribute(ITEM_ATTRIBUTE_DURATION) / 1000 / 60, thing:getAttribute(ITEM_ATTRIBUTE_DURATION) / 1000) + 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:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder) + return true +end + +function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder, toCylinder) +end + +function Player:onMoveCreature(creature, fromPosition, toPosition) + return true +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) + 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 + +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 + if (vocation:getId() > 0 or self:getLevel() < 8) then + exp = exp * Game.getExperienceStage(self:getLevel()) + end + + -- Stamina modifier + if configManager.getBoolean(configKeys.STAMINA_SYSTEM) then + useStamina(self) + + if self:getStorageValue(17582) > os.time() then + exp = exp * 1.1 + end + + local staminaMinutes = self:getStamina() + if staminaMinutes <= 840 then + exp = exp * 0.5 + end + end + + if getGlobalStorageValue(17589) > os.time() then + exp = exp * (1 + getGlobalStorageValue(17585) / 100) + end + + if source:isMonster() and not source:getMaster() then + if getGlobalStorageValue(8420) > 0 then + if source:getName():lower() == monsterOfTheDay[getGlobalStorageValue(8420)].monster:lower() then + exp = exp * (1 + getGlobalStorageValue(8421) / 100) + end + end + end + + if self:isPremium() then + exp = exp * (1 + 10 / 100) + 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 + tries = tries * configManager.getNumber(configKeys.RATE_MAGIC) + + if getGlobalStorageValue(17591) > os.time() then + tries = tries * (1 + getGlobalStorageValue(17587) / 100) + end + + return tries + end + + tries = tries * configManager.getNumber(configKeys.RATE_SKILL) + + if getGlobalStorageValue(17590) > os.time() then + tries = tries * (1 + getGlobalStorageValue(17586) / 100) + end + + return tries +end diff --git a/app/SabrehavenServer/data/global.lua b/app/SabrehavenServer/data/global.lua new file mode 100644 index 0000000..dfb8606 --- /dev/null +++ b/app/SabrehavenServer/data/global.lua @@ -0,0 +1,82 @@ +dofile('data/lib/lib.lua') + +function getDistanceBetween(firstPosition, secondPosition) + local xDif = math.abs(firstPosition.x - secondPosition.x) + local yDif = math.abs(firstPosition.y - secondPosition.y) + local posDif = math.max(xDif, yDif) + if firstPosition.z ~= secondPosition.z then + posDif = posDif + 15 + end + return posDif +end + +function getFormattedWorldTime() + local worldTime = getWorldTime() + local hours = math.floor(worldTime / 60) + + local minutes = worldTime % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + return hours .. ':' .. minutes +end + +string.split = function(str, sep) + local res = {} + for v in str:gmatch("([^" .. sep .. "]+)") do + res[#res + 1] = v + end + return res +end + +string.trim = function(str) + return str:match'^()%s*$' and '' or str:match'^%s*(.*%S)' +end + +table.contains = function(array, value) + for _, targetColumn in pairs(array) do + if targetColumn == value then + return true + end + end + return false +end + +function isNumber(str) + return tonumber(str) ~= nil +end + +if not nextUseStaminaTime then + nextUseStaminaTime = {} +end + +function isInArray(array, value, isCaseSensitive) + local compareLowerCase = false + if value ~= nil and type(value) == "string" and not isCaseSensitive then + value = string.lower(value) + compareLowerCase = true + end + if array == nil or value == nil then + return (array == value), nil + end + local t = type(array) + if t ~= "table" then + if compareLowerCase and t == "string" then + return (string.lower(array) == string.lower(value)), nil + else + return (array == value), nil + end + end + for k,v in pairs(array) do + local newV + if compareLowerCase and type(v) == "string" then + newV = string.lower(v) + else + newV = v + end + if newV == value then + return true, k + end + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/globalevents/globalevents.xml b/app/SabrehavenServer/data/globalevents/globalevents.xml new file mode 100644 index 0000000..f5b1a59 --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/globalevents.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/SabrehavenServer/data/globalevents/lib/globalevents.lua b/app/SabrehavenServer/data/globalevents/lib/globalevents.lua new file mode 100644 index 0000000..6de6c7d --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/lib/globalevents.lua @@ -0,0 +1 @@ +-- empty file -- \ No newline at end of file diff --git a/app/SabrehavenServer/data/globalevents/scripts/powergamers.lua b/app/SabrehavenServer/data/globalevents/scripts/powergamers.lua new file mode 100644 index 0000000..4a49e3d --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/scripts/powergamers.lua @@ -0,0 +1,30 @@ +local function getEternalStorage(key, parser) + local value = result.getDataString(db.storeQuery("SELECT `value` FROM `znote_global_storage` WHERE `key` = ".. key .. ";"), "value") + if not value then + if parser then + return false + else + return -1 + end + end + return tonumber(value) or value +end + +local function setEternalStorage(key, value) + if getEternalStorage(key, true) then + db.query("UPDATE `znote_global_storage` SET `value` = '".. value .. "' WHERE `key` = ".. key .. ";") + else + db.query("INSERT INTO `znote_global_storage` (`key`, `value`) VALUES (".. key ..", ".. value ..");") + end + return true +end + +function onThink(interval, lastExecution, thinkInterval) + if tonumber(os.date("%d")) ~= getEternalStorage(23856) then + setEternalStorage(23856, (tonumber(os.date("%d")))) + db.query("UPDATE `znote_players` SET `onlinetime7`=`onlinetime6`, `onlinetime6`=`onlinetime5`, `onlinetime5`=`onlinetime4`, `onlinetime4`=`onlinetime3`, `onlinetime3`=`onlinetime2`, `onlinetime2`=`onlinetime1`, `onlinetime1`=`onlinetimetoday`, `onlinetimetoday`=0;") + db.query("UPDATE `znote_players` `z` INNER JOIN `players` `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist7`=`z`.`exphist6`, `z`.`exphist6`=`z`.`exphist5`, `z`.`exphist5`=`z`.`exphist4`, `z`.`exphist4`=`z`.`exphist3`, `z`.`exphist3`=`z`.`exphist2`, `z`.`exphist2`=`z`.`exphist1`, `z`.`exphist1`=`p`.`experience`-`z`.`exphist_lastexp`, `z`.`exphist_lastexp`=`p`.`experience`;") + end + db.query("UPDATE `znote_players` SET `onlinetimetoday` = `onlinetimetoday` + 60, `onlinetimeall` = `onlinetimeall` + 60 WHERE `player_id` IN (SELECT `player_id` FROM `players_online` WHERE `players_online`.`player_id` = `znote_players`.`player_id`)") + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/globalevents/scripts/record.lua b/app/SabrehavenServer/data/globalevents/scripts/record.lua new file mode 100644 index 0000000..1c6a492 --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/scripts/record.lua @@ -0,0 +1,4 @@ +function onRecord(current, old) + addEvent(broadcastMessage, 150, "New record: " .. current .. " players are logged in.", MESSAGE_STATUS_DEFAULT) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/globalevents/scripts/rookgaard_book.lua b/app/SabrehavenServer/data/globalevents/scripts/rookgaard_book.lua new file mode 100644 index 0000000..97834fb --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/scripts/rookgaard_book.lua @@ -0,0 +1,16 @@ +local text = { + "Grrr!", + "", + "Shhh!", + "I SMELL FEEEEAAAAAR!", + "CHAMEK ATH UTHUL ARAK!", + "469", + "LET ME OUT!", + "Sacrifice!", + "More! More!" +} + +function onThink(interval, lastExecution) + Position({x=32095, y=32216, z=7}):sendMonsterSay(text[math.random(#text)]) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/globalevents/scripts/serversave.lua b/app/SabrehavenServer/data/globalevents/scripts/serversave.lua new file mode 100644 index 0000000..887d672 --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/scripts/serversave.lua @@ -0,0 +1,33 @@ +local shutdownAtServerSave = true +local cleanMapAtServerSave = false + +local function serverSave() + if shutdownAtServerSave then + Game.setGameState(GAME_STATE_SHUTDOWN) + else + Game.setGameState(GAME_STATE_CLOSED) + + if cleanMapAtServerSave then + cleanMap() + end + + Game.setGameState(GAME_STATE_NORMAL) + end +end + +local function secondServerSaveWarning() + broadcastMessage("Server is saving game in one minute.\nPlease log out.", MESSAGE_STATUS_WARNING) + addEvent(serverSave, 60000) +end + +local function firstServerSaveWarning() + broadcastMessage("Server is saving game in 3 minutes.\nPlease come back in 10 minutes.", MESSAGE_STATUS_WARNING) + addEvent(secondServerSaveWarning, 120000) +end + +function onTime(interval) + broadcastMessage("Server is saving game in 5 minutes.\nPlease come back in 10 minutes.", MESSAGE_STATUS_WARNING) + Game.setGameState(GAME_STATE_STARTUP) + addEvent(firstServerSaveWarning, 120000) + return not shutdownAtServerSave +end diff --git a/app/SabrehavenServer/data/globalevents/scripts/startup.lua b/app/SabrehavenServer/data/globalevents/scripts/startup.lua new file mode 100644 index 0000000..4f9fc7d --- /dev/null +++ b/app/SabrehavenServer/data/globalevents/scripts/startup.lua @@ -0,0 +1,203 @@ +local monsterOfTheDay = { + { id = 8430, monster = 'Crocodile' }, + { id = 8431, monster = 'Tarantula' }, + { id = 8432, monster = 'Carniphila' }, + { id = 8433, monster = 'Merlkin' }, + { id = 8434, monster = 'Kongra' }, + { id = 8435, monster = 'Sibang' }, + { id = 8436, monster = 'Thornback Tortoise' }, + { id = 8437, monster = 'Gargoyle' }, + { id = 8438, monster = 'Quara Constrictor Scout' }, + { id = 8439, monster = 'Quara Hydromancer Scout' }, + { id = 8440, monster = 'Quara Mantassin Scout' }, + { id = 8441, monster = 'Quara Pincher Scout' }, + { id = 8442, monster = 'Quara Predator Scout' }, + { id = 8443, monster = 'Ancient Scarab' }, + { id = 8444, monster = 'Wyvern' }, + { id = 8445, monster = 'Bonebeast' }, + { id = 8446, monster = 'Dragon' }, + { id = 8447, monster = 'Quara Constrictor' }, + { id = 8448, monster = 'Quara Hydromancer' }, + { id = 8449, monster = 'Quara Mantassin' }, + { id = 8450, monster = 'Quara Pincher' }, + { id = 8451, monster = 'Quara Predator' }, + { id = 8452, monster = 'Giant Spider' }, + { id = 8453, monster = 'Banshee' }, + { id = 8454, monster = 'Lich' }, + { id = 8455, monster = 'Acolyte of The Cult' }, + { id = 8456, monster = 'Adept of The Cult' }, + { id = 8457, monster = 'Enlightened of The Cult' }, + { id = 8458, monster = 'Novice of The Cult' }, + { id = 8459, monster = 'Hydra' }, + { id = 8460, monster = 'Serpent Spawn' }, + { id = 8461, monster = 'Behemoth' }, + { id = 8462, monster = 'Dragon Lord' }, + { id = 8463, monster = 'Hand of Cursed Fate' }, + { id = 8464, monster = 'Juggernaut' }, + { id = 8465, monster = 'Frost Troll' }, + { id = 8466, monster = 'Swamp Troll' }, + { id = 8467, monster = 'Rat' }, + { id = 8468, monster = 'Cave Rat' }, + { id = 8469, monster = 'Wolf' }, + { id = 8470, monster = 'Winter Wolf' }, + { id = 8471, monster = 'Wasp' }, + { id = 8472, monster = 'Larva' }, + { id = 8473, monster = 'Dwarf' }, + { id = 8474, monster = 'Skeleton' }, + { id = 8475, monster = 'Ghoul' }, + { id = 8476, monster = 'Elf' }, + { id = 8477, monster = 'Elf Scout' }, + { id = 8478, monster = 'Elf Arcanist' }, + { id = 8479, monster = 'Bug' }, + { id = 8480, monster = 'Smuggler' }, + { id = 8481, monster = 'Wild Warrior' }, + { id = 8482, monster = 'Bandit' }, + { id = 8483, monster = 'Hyaena' }, + { id = 8484, monster = 'Lion' }, + { id = 8485, monster = 'Bear' }, + { id = 8486, monster = 'Slime' }, + { id = 8487, monster = 'Beholder' }, + { id = 8488, monster = 'Elder Beholder' }, + { id = 8489, monster = 'Green Djinn' }, + { id = 8490, monster = 'Blue Djinn' }, + { id = 8491, monster = 'Marid' }, + { id = 8492, monster = 'Efreet' }, + { id = 8493, monster = 'Pirate Skeleton' }, + { id = 8494, monster = 'Pirate Marauder' }, + { id = 8495, monster = 'Pirate Cutthroat' }, + { id = 8496, monster = 'Pirate Ghost' }, + { id = 8497, monster = 'Pirate Buccaneer' }, + { id = 8498, monster = 'Pirate Corsair' }, + { id = 8499, monster = 'Orc Spearman' }, + { id = 8500, monster = 'Orc Shaman' }, + { id = 8501, monster = 'Orc Rider' }, + { id = 8502, monster = 'Orc Warrior' }, + { id = 8503, monster = 'Orc Berserker' }, + { id = 8504, monster = 'Minotaur Archer' }, + { id = 8505, monster = 'Minotaur Guard' }, + { id = 8506, monster = 'Minotaur Mage' }, + { id = 8507, monster = 'Lizard Templar' }, + { id = 8508, monster = 'Lizard Sentinel' }, + { id = 8509, monster = 'Lizard Snakecharmer' }, + { id = 8510, monster = 'Dwarf Soldier' }, + { id = 8511, monster = 'Dwarf Guard' }, + { id = 8512, monster = 'Dwarf Geomancer' }, + { id = 8513, monster = 'Ghost' }, + { id = 8514, monster = 'Demon Skeleton' }, + { id = 8515, monster = 'Vampire' }, + { id = 8516, monster = 'Orc Leader' }, + { id = 8517, monster = 'Orc Warlord' }, + { id = 8518, monster = 'Hero' }, + { id = 8519, monster = 'Necromancer' }, + { id = 8520, monster = 'Priestess' }, + { id = 8521, monster = 'Nightmare' }, + { id = 8522, monster = 'Warlock' }, + { id = 8523, monster = 'Demon' }, + { id = 8524, monster = 'Troll' }, + { id = 8525, monster = 'Goblin' }, + { id = 8526, monster = 'Rotworm' }, + { id = 8527, monster = 'Carrion Worm' }, + { id = 8528, monster = 'Cyclops' }, + { id = 8529, monster = 'Amazon' }, + { id = 8530, monster = 'Minotaur' }, + { id = 8531, monster = 'Orc' } +} + +local function spawnRashid() + local rashidSpawns = { + ['Monday'] = Position(32349, 32231, 6), + ['Tuesday'] = Position(32306, 32835, 7), + ['Wednesday'] = Position(32579, 32754, 7), + ['Thursday'] = Position(33065, 32879, 6), + ['Friday'] = Position(33233, 32484, 7), + ['Saturday'] = Position(33168, 31810, 6), + ['Sunday'] = Position(32328, 31782, 6), + } + + local position = rashidSpawns[os.date("%A")] + local rashid = Game.createNpc("rashid", position) + if rashid ~= nil then + rashid:setMasterPos(position) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + end +end + +local function setBloomingGriffinclaw() + local position = {x = 32024, y = 32830, z = 4} + if Game.isItemThere(position,5687) then + Game.removeItemOnMap(position, 5687) + Game.createItem(5658, 1, position) + Game.sendMagicEffect(position, 15) + end +end + +function onStartup() + math.randomseed(os.mtime()) + + db.query("TRUNCATE TABLE `players_online`") + db.asyncQuery("DELETE FROM `guild_wars` WHERE `status` = 0") + db.asyncQuery("DELETE FROM `players` WHERE `deletion` != 0 AND `deletion` < " .. os.time()) + db.asyncQuery("DELETE FROM `ip_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time()) + + -- Move expired bans to ban history + local resultId = db.storeQuery("SELECT * FROM `account_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time()) + if resultId ~= false then + repeat + local accountId = result.getDataInt(resultId, "account_id") + db.asyncQuery("INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" .. accountId .. ", " .. db.escapeString(result.getDataString(resultId, "reason")) .. ", " .. result.getDataLong(resultId, "banned_at") .. ", " .. result.getDataLong(resultId, "expires_at") .. ", " .. result.getDataInt(resultId, "banned_by") .. ")") + db.asyncQuery("DELETE FROM `account_bans` WHERE `account_id` = " .. accountId) + until not result.next(resultId) + result.free(resultId) + end + + -- Check house auctions + local resultId = db.storeQuery("SELECT `id`, `highest_bidder`, `last_bid`, (SELECT `balance` FROM `players` WHERE `players`.`id` = `highest_bidder`) AS `balance` FROM `houses` WHERE `owner` = 0 AND `bid_end` != 0 AND `bid_end` < " .. os.time()) + if resultId ~= false then + repeat + local house = House(result.getDataInt(resultId, "id")) + if house ~= nil then + local highestBidder = result.getDataInt(resultId, "highest_bidder") + local balance = result.getDataLong(resultId, "balance") + local lastBid = result.getDataInt(resultId, "last_bid") + if balance >= lastBid then + db.query("UPDATE `players` SET `balance` = " .. (balance - lastBid) .. " WHERE `id` = " .. highestBidder) + house:setOwnerGuid(highestBidder) + end + db.asyncQuery("UPDATE `houses` SET `last_bid` = 0, `bid_end` = 0, `highest_bidder` = 0, `bid` = 0 WHERE `id` = " .. house:getId()) + end + until not result.next(resultId) + result.free(resultId) + end + + -- Remove murders that are more than 60 days old + local resultId = db.storeQuery("SELECT * FROM `player_murders` WHERE `date` <= " .. os.time() - 60 * 24 * 60 * 60) + if resultId ~= false then + repeat + local playerId = result.getDataInt(resultId, "player_id") + local id = result.getDataLong(resultId, "id") + + db.asyncQuery("DELETE FROM `player_murders` WHERE `player_id` = " .. playerId .. " AND `id` = " .. id) + until not result.next(resultId) + result.free(resultId) + end + + -- blooming griffinclaw + local dayNow = tonumber(os.date("%d", os.time())) + if (dayNow == 1) then + setGlobalStorageValue(1, 0) + end + + if getGlobalStorageValue(1) == 0 then + local randomDay = math.random(dayNow, 28) + if (randomDay == 28) then + setGlobalStorageValue(1, 1) + addEvent(setBloomingGriffinclaw, 10000) + end + end + + spawnRashid() + --addEvent(function () Game.setGameState(GAME_STATE_CLOSED) end, 5000) + setGlobalStorageValue(17657, 0) -- reset POI levers + setGlobalStorageValue(8420, monsterOfTheDay[math.random(1, #monsterOfTheDay)].id) + setGlobalStorageValue(8421, math.random(10, 25)) +end diff --git a/app/SabrehavenServer/data/items780/items.srv b/app/SabrehavenServer/data/items780/items.srv new file mode 100644 index 0000000..1594160 --- /dev/null +++ b/app/SabrehavenServer/data/items780/items.srv @@ -0,0 +1,27855 @@ +# items.srv - Tibia Item definitions +# --- begin of server specific object types --- + +TypeID = 1 +Name = "water" + +TypeID = 2 +Name = "wine" + +TypeID = 3 +Name = "beer" + +TypeID = 4 +Name = "mud" + +TypeID = 5 +Name = "blood" + +TypeID = 6 +Name = "slime" + +TypeID = 7 +Name = "oil" + +TypeID = 8 +Name = "urine" + +TypeID = 9 +Name = "milk" + +TypeID = 10 +Name = "manafluid" + +TypeID = 11 +Name = "lifefluid" + +TypeID = 12 +Name = "lemonade" + +TypeID = 13 +Name = "rum" + +TypeID = 14 +Name = "coconut milk" + +TypeID = 15 +Name = "fruit juice" + +# --- end of server specific object types --- + +TypeID = 100 +Name = "void" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 101 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 102 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 103 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 104 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 105 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 106 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 107 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 108 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 109 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 110 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 111 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 112 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 113 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 114 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 115 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 116 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 117 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 118 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 119 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 120 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 121 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 122 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 123 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 124 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 125 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 126 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 127 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 128 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 129 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 130 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 131 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 132 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 133 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 134 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 135 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 136 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 137 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 138 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 139 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 140 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 141 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 142 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 143 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 144 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 145 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 146 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 147 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 148 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 149 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 150 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 151 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 152 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 153 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 154 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 155 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 156 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 157 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 158 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 159 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 160 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 161 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 162 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 163 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 164 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 165 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 166 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 167 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 168 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 169 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 170 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 171 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 172 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 173 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 174 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 175 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 176 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 177 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 178 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 179 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 180 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 181 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 182 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 183 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 184 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 185 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 186 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 187 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 188 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 189 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 190 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 191 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 192 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 193 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 194 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 195 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 196 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 197 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 198 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 199 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 200 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 201 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 202 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 203 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 204 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 205 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 206 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 207 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 208 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 209 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 210 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 211 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 212 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 213 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 214 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 215 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 216 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 217 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 218 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 219 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 220 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 221 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 222 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 223 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 224 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 225 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 226 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 227 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 228 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 229 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 230 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 231 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 232 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 233 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 234 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 235 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 236 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 237 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 238 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 239 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 240 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 241 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 242 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 243 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 244 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 245 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 246 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 247 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 248 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 249 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 250 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 251 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 252 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 253 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 254 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 255 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 256 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 257 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 258 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 259 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 260 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 261 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 262 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 263 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 264 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 265 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 266 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 267 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 268 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 269 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 270 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 271 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 272 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 273 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 274 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 275 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 276 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 277 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 278 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 279 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 280 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 281 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 282 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 283 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 284 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 285 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 286 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 287 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 288 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 289 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 290 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 291 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 292 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 293 +Name = "grass" +Flags = {Bank,,CollisionEvent,Unmove} +Attributes = {Waypoints=150} + +TypeID = 294 +Name = "a pitfall" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=150,ExpireTarget=293,TotalExpireTime=300} + +TypeID = 295 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 296 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 297 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 298 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 299 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 300 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 301 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 302 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 303 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 304 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 305 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 306 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 307 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 308 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 309 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 310 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 311 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 312 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 313 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 314 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 315 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 316 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 317 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 318 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 319 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 320 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 321 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 322 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 323 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 324 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 325 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 326 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 327 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 328 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 329 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 330 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 331 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 332 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 333 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 334 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 335 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 336 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 337 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 338 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 339 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 340 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 341 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 342 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 343 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 344 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 345 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 346 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 347 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 348 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 349 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 350 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 351 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 352 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 353 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 354 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 355 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200,FluidSource=MUD} + +TypeID = 356 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 357 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 358 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 359 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 360 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 361 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 362 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 363 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 364 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 365 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 366 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 367 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 368 +Name = "earth ground" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 369 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 370 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 371 +Name = "dirt floor" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 372 +Name = "muddy floor" +Flags = {Bank,UseEvent,Unmove,Disguise} +Attributes = {Waypoints=200,FluidSource=MUD,DisguiseTarget=355} + +TypeID = 373 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 374 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 375 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 376 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 377 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 378 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 379 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 380 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 381 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 382 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 383 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 384 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 385 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 386 +Name = "dirt floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=120} + +TypeID = 387 +Name = "a small hole" +Description = "It seems too narrow to climb through" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 388 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 389 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 390 +Name = "a lava hole" +Description = "It seems to be inactive" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 391 +Name = "a lava hole" +Description = "It emits heat and light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=215} + +TypeID = 392 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 393 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 394 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=110,ExpireTarget=372,TotalExpireTime=300} + +TypeID = 395 +Name = "dirt floor" +Flags = {Bank,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 396 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 397 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 398 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 399 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 400 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 401 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 402 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 403 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 404 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 405 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 406 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 407 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 408 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 409 +Name = "white marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 410 +Name = "black marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 411 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 412 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 413 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 414 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 415 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 416 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 417 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 418 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 419 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 420 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 421 +Name = "sandy floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=100} + +TypeID = 422 +Name = "a sandstone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 423 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 424 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 425 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 426 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=424} + +TypeID = 427 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=425} + +TypeID = 428 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 429 +Name = "a stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 430 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 431 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 432 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 433 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 434 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 435 +Name = "a sewer grate" +Flags = {UseEvent,Unmove} + +TypeID = 436 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 437 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 438 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 439 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 440 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 441 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 442 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 443 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 444 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 445 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 446 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 447 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 448 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 449 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 450 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 451 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 452 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 453 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 454 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 455 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 456 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 457 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 458 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 459 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 460 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 461 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 462 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 463 +Name = "a white stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 464 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 465 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 466 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 467 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 468 +Name = "nothing special" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=470} + +TypeID = 469 +Name = "stairs" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=95} + +TypeID = 470 +Name = "nothing special" +Flags = {Bank,Unmove} +Attributes = {Waypoints=95} + +TypeID = 471 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 472 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 473 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 474 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 475 +Name = "a closed trapdoor" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 476 +Name = "an open trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Expire} +Attributes = {Waypoints=100,ExpireTarget=475,TotalExpireTime=2} + +TypeID = 477 +Name = "a pedestal" +Flags = {Unmove,Avoid,Height} + +TypeID = 478 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 479 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 480 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 481 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 482 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 483 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 484 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 485 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 486 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 487 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 488 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 489 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 490 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 491 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 492 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 493 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 494 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 495 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 496 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 497 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 498 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 499 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 500 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 501 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 502 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 503 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 504 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 505 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 506 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 507 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 508 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 509 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 510 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 511 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 512 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 513 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 514 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 515 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 516 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 517 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 518 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 519 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 520 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 521 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 522 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 523 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 524 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 525 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 526 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 527 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 528 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 529 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 530 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 531 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 532 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 533 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 534 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 535 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 536 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 537 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 538 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 539 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 540 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 541 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 542 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 543 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 544 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 545 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 546 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 547 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 548 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 549 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 550 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 551 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 552 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 553 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 554 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 555 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 556 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 557 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 558 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 559 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 560 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 561 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 562 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 563 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 564 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 565 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 566 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 567 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 568 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 569 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 570 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 571 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 572 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 573 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 574 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 575 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 576 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 577 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 578 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 579 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 580 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 581 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 582 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 583 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 584 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 585 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 586 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 587 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 588 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 589 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 590 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 591 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 592 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 593 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 594 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=160,ExpireTarget=593,TotalExpireTime=300} + +TypeID = 595 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 596 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 597 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 598 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 599 +Name = "a strange carving" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 600 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 601 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 602 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 603 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 604 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 605 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 606 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=170} + +TypeID = 607 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=606,TotalExpireTime=300} + +TypeID = 608 +Name = "a loose ice pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=120} + +TypeID = 609 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=120,ExpireTarget=608,TotalExpireTime=300} + +TypeID = 610 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 611 +Name = "a snow heap" +Flags = {UseEvent,Unmove,Avoid} + +TypeID = 612 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 613 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 614 +Name = "sand" +Flags = {UseEvent,Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 615 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=614,TotalExpireTime=30} + +TypeID = 616 +Name = "sand" +Flags = {Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 617 +Name = "sand" +Flags = {Bank,Unmove,Expire,Disguise} +Attributes = {Waypoints=160,ExpireTarget=616,TotalExpireTime=4000,DisguiseTarget=231} + +TypeID = 618 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=618,TotalExpireTime=2200} + +TypeID = 621 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=620} + +TypeID = 622 +Name = "water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 623 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 624 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 625 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 626 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 627 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 628 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 629 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 630 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 631 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 632 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 633 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 634 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 635 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 636 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 637 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 638 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 639 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 640 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 641 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 642 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 643 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 644 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 645 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 646 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 647 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 648 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 649 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 650 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 651 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 652 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 653 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 654 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 655 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 656 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 657 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 658 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 659 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 660 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 661 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 662 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 663 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 664 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 665 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 666 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 667 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 668 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 669 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 670 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 671 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 672 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 673 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 674 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 675 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 676 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 677 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 678 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 679 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 680 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 681 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 682 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 683 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 684 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 685 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 686 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 687 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 688 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 689 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 690 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 691 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 692 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 693 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 694 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 695 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 696 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 697 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 698 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 699 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 700 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 701 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 702 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 703 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 704 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 705 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 706 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 707 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 708 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 709 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 710 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 711 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 712 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 713 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 714 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 715 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 716 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 717 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 718 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 719 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 720 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 721 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 722 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 723 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 724 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 725 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 726 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 727 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 728 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 729 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 730 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 731 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 732 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 733 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 734 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 735 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 736 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 737 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 738 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 739 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 740 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 741 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 742 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 743 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 744 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 745 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 746 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 747 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 748 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 749 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 750 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 751 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 752 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 753 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 754 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 755 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 756 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 757 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 758 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 759 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 760 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 761 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 762 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 763 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 764 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 765 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 766 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 767 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 768 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 769 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 770 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 771 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 772 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 773 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 774 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 775 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 776 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 777 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 778 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 779 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 780 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 781 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 782 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 783 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 784 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 785 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 786 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 787 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 788 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 789 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 790 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 791 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 792 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 793 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 794 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 795 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 796 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 797 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 798 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 799 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 800 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 801 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 802 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 803 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 804 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 805 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 806 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 807 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 808 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 809 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 810 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 811 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 812 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 813 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 814 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 815 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 816 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 817 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 818 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 819 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 820 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 821 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 822 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 823 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 824 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 825 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 826 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 827 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 828 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 829 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 830 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 831 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 832 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 833 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 834 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 835 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 836 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 837 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 838 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 839 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 840 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 841 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 842 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 843 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 844 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 845 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 846 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 847 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 848 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 849 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 850 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 851 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 852 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 853 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 854 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 855 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 856 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 857 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 858 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 859 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 860 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 861 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 862 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 863 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 864 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 865 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 866 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 867 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 868 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 869 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 870 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 871 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 872 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 873 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 874 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 875 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 876 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 877 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 878 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 879 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 880 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 881 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 882 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 883 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 884 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 885 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 886 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 887 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 888 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 889 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 890 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 891 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 892 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 893 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 894 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 895 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 896 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 897 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 898 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 899 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 900 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 901 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 902 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 903 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 904 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 905 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 906 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 907 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 908 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 909 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 910 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 911 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 912 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 913 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 914 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 915 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 916 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 917 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 918 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 919 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 920 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 921 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 922 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 923 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 924 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 925 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 926 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 927 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 928 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 929 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 930 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 931 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 932 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 933 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 934 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 935 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 936 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 937 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 938 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 939 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 940 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 941 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 942 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 943 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 944 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 945 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 946 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 947 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 948 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 949 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 950 +Name = "soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 951 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 952 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 953 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 954 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 955 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 956 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 957 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 958 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 959 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 960 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 961 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 962 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 963 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 964 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 965 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 966 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 967 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 968 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 969 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 970 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 971 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 972 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 973 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 974 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 975 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 976 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 977 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 978 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 979 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 980 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 981 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 982 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 983 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 984 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 985 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 986 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 987 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 988 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 989 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 990 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 991 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 992 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 993 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 994 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 995 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 996 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 997 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 998 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 999 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1000 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1001 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1002 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1003 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1004 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1005 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1006 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1007 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1008 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1009 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1010 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1011 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1012 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1013 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1014 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1015 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1016 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1017 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1018 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1019 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1020 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1021 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1022 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1023 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1024 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1025 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1026 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1027 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1028 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1029 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1030 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1031 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1032 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1033 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1034 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1035 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1036 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1037 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1038 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1039 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1040 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1041 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1042 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1043 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1044 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1045 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1046 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1047 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1048 +Name = "jungle grass " +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1049 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1050 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1051 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1052 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1053 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1054 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1055 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1056 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1057 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1058 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1059 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1060 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1061 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1062 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1063 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1064 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1065 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1066 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 1067 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {ExpireTarget=1066,TotalExpireTime=75} + +TypeID = 1068 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1069 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1070 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1071 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1072 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1073 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1074 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1075 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1076 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1077 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1078 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1079 +Name = "an ant-hill" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1080 +Name = "an earth hole" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} + +TypeID = 1081 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1082 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1083 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1084 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1085 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1086 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1087 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1088 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1089 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1090 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1091 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1092 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1093 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1094 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1095 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1096 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1097 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1098 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1099 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=0,DisguiseTarget=1128} + +TypeID = 1100 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1101 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1102 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1103 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1104 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1105 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1106 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1107 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1108 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1109 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1110 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1111 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1112 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1113 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1114 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1115 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1116 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1117 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1118 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1119 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1120 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1121 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1122 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1123 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1124 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1125 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1126 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1127 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1128 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1129 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1130 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1131 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1132 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1133 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1134 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1135 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1136 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1137 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1138 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1139 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1140 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1141 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1142 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1143 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1144 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1145 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1146 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1147 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1148 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1149 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1150 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1151 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1152 +Name = "a flat roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1153 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1154 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1155 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1156 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 1157 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1158 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1159 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1160 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1161 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1162 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1163 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1164 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1165 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1166 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1167 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1168 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1169 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1170 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1171 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1172 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1173 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1174 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1175 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1176 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1177 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1178 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1179 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1180 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1181 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1182 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1183 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1184 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1185 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1186 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1187 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1188 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1189 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1190 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1191 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1192 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1193 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1194 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1195 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1196 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1197 +Name = "a dried grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1198 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1199 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1200 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1201 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1202 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1203 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1204 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1205 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1206 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1207 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1208 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1209 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1210 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1211 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1212 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1213 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1214 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1215 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1216 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1217 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1218 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1219 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1220 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1221 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1222 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1223 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1224 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1225 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1226 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1227 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1228 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1229 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1230 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1231 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1232 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1233 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1234 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1235 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1236 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1237 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1238 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1239 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1240 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1241 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1242 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1243 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1244 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1245 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1246 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1247 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1248 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1249 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1250 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1251 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1252 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1253 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1254 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1255 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1256 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1257 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1258 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1259 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1260 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1261 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1262 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1263 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1264 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1265 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1266 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1267 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1268 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1269 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1270 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1271 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1272 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1273 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1274 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1275 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1276 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1277 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1278 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1279 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1280 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1281 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1282 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1283 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1284 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1285 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1286 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1287 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1288 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1289 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1290 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1291 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1292 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1293 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1294 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1295 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1296 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1297 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1298 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1299 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1300 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1301 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1302 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1303 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1304 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1305 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1306 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1307 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1308 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1309 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1310 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1311 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1312 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1313 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1314 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1315 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1316 +Name = "sandstone" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1317 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1318 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1319 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1320 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1321 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1322 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1323 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1324 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1325 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1326 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1327 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1328 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1329 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1330 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1331 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1332 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1333 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1334 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1335 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1336 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1337 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1338 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1339 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1340 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1341 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1342 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1343 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1344 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1345 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1346 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1347 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1348 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1349 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1350 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1351 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1352 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1353 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1354 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1355 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1356 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1357 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1358 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1359 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1360 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1361 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1362 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1363 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1364 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1365 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1366 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1367 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1368 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1369 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1370 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1371 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1372 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1373 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1374 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1375 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1376 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1377 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1378 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1379 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1380 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1381 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1382 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1383 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1384 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1385 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1386 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1387 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1388 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1389 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1390 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1391 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1392 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1393 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1394 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1395 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1396 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1397 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1398 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1399 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1400 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1401 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1402 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1403 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1404 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1405 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1406 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1407 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1408 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1409 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1410 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1411 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1412 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1413 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1414 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1415 +Name = "a paravent" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1416 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1417 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1418 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1419 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1420 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1421 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1422 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1423 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1424 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1425 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1426 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1427 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1428 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1429 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1430 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1431 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1432 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1433 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1434 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1435 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1436 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1437 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1438 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1439 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1440 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1441 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1442 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1443 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1444 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1445 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1446 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1447 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1448 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1449 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1450 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1451 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1452 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1453 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1454 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1455 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1456 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1457 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1458 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1459 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1460 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1461 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1462 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1463 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1464 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1465 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1466 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1467 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1468 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1469 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1470 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1471 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1472 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1473 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1474 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1475 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1476 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1477 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1478 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1479 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1480 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1481 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1482 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1483 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1484 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1485 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1486 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1487 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1488 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1489 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1490 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1491 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1492 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1493 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1494 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1495 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1496 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1497 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1498 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1499 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1500 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1501 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1502 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1503 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1504 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1505 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1506 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1507 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1508 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1509 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1510 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1511 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1512 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1513 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1514 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1515 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1516 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1517 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1518 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1519 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1520 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1521 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1522 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1523 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1524 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1525 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1526 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1527 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1528 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1529 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1530 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1531 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1532 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1533 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1534 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1535 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1536 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1537 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1538 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1539 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1540 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1541 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1542 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1543 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1544 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1545 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1546 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1547 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1548 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1549 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1550 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1551 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1552 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1553 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1554 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1555 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1556 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1557 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1558 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1559 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1560 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1561 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1562 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1563 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1564 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1565 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1566 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1567 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1568 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1569 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1570 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1571 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1572 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1573 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1574 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1575 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1576 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1577 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1578 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1579 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1580 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1581 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1582 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1583 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1584 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1585 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1586 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1587 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1588 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1589 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1590 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1591 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1592 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1593 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1594 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1595 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1596 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1597 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1598 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1599 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1600 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1601 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1602 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1603 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1604 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1605 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1606 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1607 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1608 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1609 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1610 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1611 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1612 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1613 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1614 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1615 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1616 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1617 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1618 +Name = "a temple wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1619 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1620 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1621 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1622 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1623 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1624 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1625 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1626 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1627 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1628 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1629 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1630 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1631 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1632 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1633 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1634 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1635 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1636 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1637 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1638 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1639 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1640 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1641 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1642 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1643 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1644 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1645 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1646 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1647 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1648 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1649 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1650 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1651 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1652 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1653 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1654 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1655 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1656 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1657 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1658 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1659 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1660 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1661 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1662 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1663 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1664 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1665 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1666 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1667 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1668 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1669 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1670 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1671 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1672 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1673 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1674 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1675 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1676 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1677 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1678 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1679 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1680 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1681 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1682 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1683 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1684 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1685 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1686 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1687 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1688 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1689 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1690 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1691 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1692 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1693 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1694 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1695 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1696 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1697 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1698 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1699 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1700 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1701 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1702 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1703 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1704 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1705 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1706 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1707 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1708 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1709 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1710 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1711 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1712 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1713 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1714 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1715 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1716 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1717 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1718 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1719 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1720 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1721 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1722 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1723 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1724 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1725 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1726 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1727 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1728 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1729 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1730 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1731 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1732 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1733 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1734 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1735 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1736 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1737 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1738 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1739 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1740 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1741 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1742 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1743 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1744 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1745 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1746 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1747 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1748 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1749 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1750 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1751 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1752 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1753 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1754 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1755 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1756 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1757 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1758 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1759 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1760 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1761 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1762 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1763 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1764 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1765 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1766 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1767 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1768 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1769 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1770 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1771 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 1772 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1773 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1774 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1775 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1776 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1777 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1778 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1779 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1780 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=41000} + +TypeID = 1781 +Name = "a small stone" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=360,Range=7,Attack=10,Defense=0,MissileEffect=10,Fragility=7} + +TypeID = 1782 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=78000} + +TypeID = 1783 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1784 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1785 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1786 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1787 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1788 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1789 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1790 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1791 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1792 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1793 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1794 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1795 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1796 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1797 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1798 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1799 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1800 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1801 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1802 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1803 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1804 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1805 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1806 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1807 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1808 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1809 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1810 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1811 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1812 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1813 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1814 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1815 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1816 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1817 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1818 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1819 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1820 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1821 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1822 +Name = "a stone pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1823 +Name = "a stone pile" +Flags = {Unmove} + +TypeID = 1824 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1825 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1826 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1827 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1828 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1829 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1830 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1831 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1832 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1833 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1834 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1835 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1836 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1837 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1838 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1839 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1840 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1841 +Name = "a blue shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1842 +Name = "a red shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1843 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1844 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1845 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1846 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1847 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1848 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1849 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1850 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1851 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1852 +Name = "stones" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1853 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1854 +Name = "stones" +Flags = {Unmove} + +TypeID = 1855 +Name = "stones" +Flags = {Unmove} + +TypeID = 1856 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1857 +Name = "stones" +Flags = {Unmove} + +TypeID = 1858 +Name = "stones" +Flags = {Unmove} + +TypeID = 1859 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1860 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1861 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1862 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1863 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1864 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1865 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1866 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1867 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1868 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1869 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1870 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1871 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1872 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1873 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1874 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1875 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1876 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1877 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1878 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1879 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1880 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1881 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1882 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1883 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1884 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1885 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1886 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1887 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1888 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1889 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1890 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1891 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1892 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1893 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1894 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1895 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1896 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1897 +Name = "debris" +Flags = {Unmove} + +TypeID = 1898 +Name = "debris" +Flags = {Unmove} + +TypeID = 1899 +Name = "debris" +Flags = {Unmove} + +TypeID = 1900 +Name = "debris" +Flags = {Unmove} + +TypeID = 1901 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1902 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1903 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1904 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1905 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1906 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1907 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1908 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1909 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1910 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1911 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1912 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1913 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1914 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1915 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1916 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1917 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1918 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1919 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1920 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1921 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1922 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1923 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1924 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1925 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1926 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1927 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1928 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1929 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1930 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1931 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1932 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1933 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1934 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1935 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1936 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1937 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1938 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1939 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1940 +Name = "a small basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1941 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1942 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1943 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1944 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1945 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1946 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1947 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1948 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1949 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 1950 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1951 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1952 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1953 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1954 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1955 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1956 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1957 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1958 +Name = "wooden stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1959 +Name = "a mystic flame" +Description = "You feel drawn to the mesmerizing light" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=173} + +TypeID = 1960 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1961 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1962 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1963 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1964 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1965 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1966 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1967 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1968 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1969 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1970 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1971 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1972 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1973 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1974 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1975 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1976 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1977 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1978 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1979 +Name = "a grave" +Flags = {Bottom,Unmove,AllowDistRead} + +TypeID = 1980 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1981 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1982 +Name = "a grave stone" +Flags = {Unmove,AllowDistRead} + +TypeID = 1983 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1984 +Name = "a stone coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1985 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1986 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1987 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1988 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1989 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1990 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2474} + +TypeID = 1991 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2476} + +TypeID = 1992 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1993 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1994 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1995 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1996 +Name = "a stone circle" +Flags = {Unmove} + +TypeID = 1997 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 1998 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=7,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 1999 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=5,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2000 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=3,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2001 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 2002 +Name = "a campfire" +Flags = {Unpass,Unmove} + +TypeID = 2003 +Name = "a campfire" +Flags = {Unpass,Unmove} +Attributes = {Brightness=5,LightColor=206} + +TypeID = 2004 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2005 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2006 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2007 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2008 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2009 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2010 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2011 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2012 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2013 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2014 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2015 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2016 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2017 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2018 +Name = "a dragon flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2019 +Name = "a castle flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2020 +Name = "a flag of Tibia" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2021 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2022 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2023 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2024 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2025 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2059,DestroyTarget=3141} + +TypeID = 2026 +Name = "a statue" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2027 +Name = "a hero statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2028 +Name = "a monument" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2029 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2045,DestroyTarget=3142} + +TypeID = 2030 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2048,DestroyTarget=3142} + +TypeID = 2031 +Name = "an angel statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2032 +Name = "a dwarven statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2033 +Name = "a watchdog statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2034 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2035 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2036 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2037 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2038 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2039 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2040 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2041 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2042 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2043 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2044,DestroyTarget=3142} + +TypeID = 2044 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2029,DestroyTarget=3142} + +TypeID = 2045 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2043,DestroyTarget=3142} + +TypeID = 2046 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2047,DestroyTarget=3142} + +TypeID = 2047 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2030,DestroyTarget=3142} + +TypeID = 2048 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2046,DestroyTarget=3142} + +TypeID = 2049 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2050 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2051 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2052 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2053 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2054 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2055 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2056 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2057 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2058 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2059 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2060,DestroyTarget=3141} + +TypeID = 2060 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2061,DestroyTarget=3141} + +TypeID = 2061 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2025,DestroyTarget=3141} + +TypeID = 2062 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2063} + +TypeID = 2063 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2062,Brightness=6,LightColor=206} + +TypeID = 2064 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2065} + +TypeID = 2065 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2064,Brightness=6,LightColor=206} + +TypeID = 2066 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2067 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2068 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2069 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2070 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2071 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2072 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2073 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2074 +Name = "a small pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2075 +Name = "a small lit pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=207} + +TypeID = 2076 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2077 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2078 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2079 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2080 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2081 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2082 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2083 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2084 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2085 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2086 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2087 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2088 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2089 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2090 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2091 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2092 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2093 +Name = "a stone snake pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2094 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2095 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2096 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2097 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2098 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2099 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2100 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2101 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2102 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2103 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2104 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2105 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2106 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2107 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2108 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2109,Brightness=0,LightColor=215} + +TypeID = 2109 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2108,Brightness=7,LightColor=207} + +TypeID = 2110 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=207} + +TypeID = 2111 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2112 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2113 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2114 +Name = "an empty coal basin" +Flags = {CollisionEvent,Unpass,Unmove,Height} +Attributes = {Brightness=0,LightColor=215} + +TypeID = 2115 +Name = "a stone coal basin" +Flags = {Unpass,Unmove,Unlay,Height} +Attributes = {Brightness=7,LightColor=206} + +TypeID = 2116 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2117,Brightness=0,LightColor=215} + +TypeID = 2117 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 2118 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200,ExpireTarget=2119,TotalExpireTime=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2119 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2120,TotalExpireTime=150} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2120 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=206,ExpireTarget=0,TotalExpireTime=100} + +TypeID = 2121 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104,ExpireTarget=0,TotalExpireTime=250} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2122 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137,ExpireTarget=0,TotalExpireTime=100} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2123 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2124 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2125 +Name = "a fire" +Flags = {Unmove,MagicField} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 2126 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2127 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2128 +Name = "a magic wall" +Flags = {Unpass,CollisionEvent,Unmove,Unthrow,Unlay,MagicField,Expire} +Attributes = {Brightness=3,LightColor=5,ExpireTarget=0,TotalExpireTime=20} + +TypeID = 2129 +Name = "a magic wall" +Flags = {Unpass,Unmove,Unthrow,Unlay,MagicField} +Attributes = {Brightness=3,LightColor=5} + +TypeID = 2130 +Name = "rush wood" +Flags = {Unpass,Unmove,Unlay,MagicField,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=45} + +TypeID = 2131 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=206,ExpireTarget=2132,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2132 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2133,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2133 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=207,ExpireTarget=0,TotalExpireTime=5} + +TypeID = 2134 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=214,ExpireTarget=0,TotalExpireTime=8} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2135 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=214,ExpireTarget=0,TotalExpireTime=5} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2136 +Name = "smoke" +Flags = {Unmove,MagicField} + +TypeID = 2137 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2138,TotalExpireTime=7} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2138 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=203,ExpireTarget=2151,TotalExpireTime=2} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2139 +Name = "a fire" +Flags = {Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2140,TotalExpireTime=2,DisguiseTarget=2140} + +TypeID = 2140 +Name = "ashes" +Flags = {Unmove,Avoid,Expire} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2137,TotalExpireTime=8} + +TypeID = 2141 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2142 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2140,TotalExpireTime=7,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2143 +Name = "ashes" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2139,TotalExpireTime=1,DisguiseTarget=2140} + +TypeID = 2144 +Name = "lava" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=150,Brightness=4,LightColor=193} + +TypeID = 2145 +Name = "strange slits" +Flags = {CollisionEvent,Unmove} + +TypeID = 2146 +Name = "blades" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2145,TotalExpireTime=3} + +TypeID = 2147 +Name = "strange holes" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2148,TotalExpireTime=1} + +TypeID = 2148 +Name = "spikes" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2147,TotalExpireTime=3} + +TypeID = 2149 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=209,ExpireTarget=2150,TotalExpireTime=5,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2150 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=209,ExpireTarget=2149,TotalExpireTime=1,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2151 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=2,LightColor=209,ExpireTarget=2139,TotalExpireTime=2,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2152 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2153 +Name = "a marble pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2154 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2155 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2156 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2157 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2158 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2159 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2160 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2161 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2162 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2163 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2164 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2165 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2166 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2167 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2168 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2169 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2170 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2171 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2172 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2173 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2174 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2175 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2176 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2177 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2178} + +TypeID = 2178 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2177} + +TypeID = 2179 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2180} + +TypeID = 2180 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2179} + +TypeID = 2181 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2182 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2183 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2184 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2185 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2186 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2186} + +TypeID = 2187 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2188 +Name = "a sandstone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2189 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2190 +Name = "an oriental pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2191 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2192 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2193 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2194 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2195 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2196 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2197 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2198 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2199 +Name = "an obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2200 +Name = "a broken obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2201 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2202 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2203 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2204 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2205 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2206 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2207 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2208 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2209 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2210 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2211 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2212 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2213 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2214 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2215 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2216 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2217 +Name = "an ominous pillar" +Flags = {Bottom,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2190} + +TypeID = 2218 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2219 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2220 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2221 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2222 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2223 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2224 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2225 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2226 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2227 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2228 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2229 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2230 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2231 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2232 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2233 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2234 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2235 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2236 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2237 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2238 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2239 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2240 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2241 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2242 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2243 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2244 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2245 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2246 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2247 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2248 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2249 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2250 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2251 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2252 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2253 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2254 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2255 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2256 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2257 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2258 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2259 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2260 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2261 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2262 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2263 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2264 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2265 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2266 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2267 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2268 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2269 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2270 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2271 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2272 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2273 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2274 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2275 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2276 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2277 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2278 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2279 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2280 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2281 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2282 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2283 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2284 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2285 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2286 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2287 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2288 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2289 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2290 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2291 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2292 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2293 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2294 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2295 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3146} + +TypeID = 2296 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3145} + +TypeID = 2297 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2298 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2299 +Name = "a small totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2300 +Name = "a large totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2301 +Name = "a totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2302 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2303 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2304 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2305 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2306 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2307 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2308 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2309 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2310 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2311 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2312 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2313 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2314 +Name = "a big table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2315 +Name = "a square table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2316 +Name = "a small round table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3138} + +TypeID = 2317 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2318 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2319 +Name = "a small table" +Flags = {Destroy,Height,Avoid} +Attributes = {DestroyTarget=3140} + +TypeID = 2320 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2321 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2322 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2323 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2324 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2325 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2326 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2327 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2328 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2329 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2330 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2331 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2332 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2333 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2334 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2335 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2336 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2337 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2338 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2339 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2340 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2341 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2342 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2343 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2344 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2345 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2346 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2347,DestroyTarget=3141} + +TypeID = 2347 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2348 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2349,DestroyTarget=3137} + +TypeID = 2349 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2348,DestroyTarget=3137} + +TypeID = 2350 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2351,DestroyTarget=3137} + +TypeID = 2351 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2350,DestroyTarget=3137} + +TypeID = 2352 +Name = "a thick trunk" +Flags = {Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2353 +Name = "an ornamented stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2354 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2355 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2356 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2357 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2358 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2359,DestroyTarget=3138} + +TypeID = 2359 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2360,DestroyTarget=3138} + +TypeID = 2360 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2361,DestroyTarget=3138} + +TypeID = 2361 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2358,DestroyTarget=3138} + +TypeID = 2362 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2363 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2364 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2365 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2366 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2369,DestroyTarget=3139} + +TypeID = 2367 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2368,DestroyTarget=3139} + +TypeID = 2368 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2366,DestroyTarget=3139} + +TypeID = 2369 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2367,DestroyTarget=3139} + +TypeID = 2370 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2371 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2372 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2373 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2374 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2377,DestroyTarget=3138} + +TypeID = 2375 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2376,DestroyTarget=3138} + +TypeID = 2376 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2374,DestroyTarget=3138} + +TypeID = 2377 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2375,DestroyTarget=3138} + +TypeID = 2378 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2381,DestroyTarget=3138} + +TypeID = 2379 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2380,DestroyTarget=3138} + +TypeID = 2380 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2378,DestroyTarget=3138} + +TypeID = 2381 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2379,DestroyTarget=3138} + +TypeID = 2382 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2385,DestroyTarget=3138} + +TypeID = 2383 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2384,DestroyTarget=3138} + +TypeID = 2384 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2382,DestroyTarget=3138} + +TypeID = 2385 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2383,DestroyTarget=3138} + +TypeID = 2386 +Name = "a small purple pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2387 +Name = "a small green pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2388 +Name = "a small red pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2389 +Name = "a small blue pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2390 +Name = "a small orange pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2391 +Name = "a small turquoise pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2392 +Name = "a small white pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2393 +Name = "a heart pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 2394 +Name = "a blue pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2395 +Name = "a red pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2396 +Name = "a green pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2397 +Name = "a yellow pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2398 +Name = "a round blue pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2399 +Name = "a round red pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2400 +Name = "a round purple pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2401 +Name = "a round turquoise pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2402 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2403 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2404 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2405 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2406 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2407 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2408 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2409 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2410 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2411 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2412 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2413 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2414 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2415 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2416 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2417 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2418 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2421,DestroyTarget=3136} + +TypeID = 2419 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2420,DestroyTarget=3136} + +TypeID = 2420 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2418,DestroyTarget=3136} + +TypeID = 2421 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2419,DestroyTarget=3136} + +TypeID = 2422 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2425,DestroyTarget=3136} + +TypeID = 2423 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2424,DestroyTarget=3136} + +TypeID = 2424 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2422,DestroyTarget=3136} + +TypeID = 2425 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2423,DestroyTarget=3136} + +TypeID = 2426 +Name = "a small trunk" +Flags = {Avoid,Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2427 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2428 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2429 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2430 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2431 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2434,DestroyTarget=3136} + +TypeID = 2432 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2431,DestroyTarget=3136} + +TypeID = 2433 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2432,DestroyTarget=3136} + +TypeID = 2434 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=10,RotateTarget=2433,DestroyTarget=3136} + +TypeID = 2435 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2436 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2437 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2438 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2439 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2440 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2441 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2444,DestroyTarget=3139} + +TypeID = 2442 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2441,DestroyTarget=3139} + +TypeID = 2443 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2442,DestroyTarget=3139} + +TypeID = 2444 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2443,DestroyTarget=3139} + +TypeID = 2445 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2448,DestroyTarget=3139} + +TypeID = 2446 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2445,DestroyTarget=3139} + +TypeID = 2447 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2446,DestroyTarget=3139} + +TypeID = 2448 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2447,DestroyTarget=3139} + +TypeID = 2449 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2452,DestroyTarget=3140} + +TypeID = 2450 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2451,DestroyTarget=3140} + +TypeID = 2451 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2449,DestroyTarget=3140} + +TypeID = 2452 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2450,DestroyTarget=3140} + +TypeID = 2453 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2454,DestroyTarget=3140} + +TypeID = 2454 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2453,DestroyTarget=3140} + +TypeID = 2455 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2456 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2457 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2458 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2459 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2460 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2461 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2462 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2463 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2464 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2465 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2468,DestroyTarget=3136} + +TypeID = 2466 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2465,DestroyTarget=3136} + +TypeID = 2467 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2466,DestroyTarget=3136} + +TypeID = 2468 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2467,DestroyTarget=3136} + +TypeID = 2469 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3135} + +TypeID = 2470 +Name = "a box" +Flags = {Container,Unmove,Avoid,Height,Disguise} +Attributes = {Capacity=10,DisguiseTarget=2469} + +TypeID = 2471 +Name = "a crate" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=15,Weight=8000,DestroyTarget=3135} + +TypeID = 2472 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2482,DestroyTarget=3137} + +TypeID = 2473 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3140} + +TypeID = 2474 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2475 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2476 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2477 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2478 +Name = "a treasure chest" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=9500} + +TypeID = 2479 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2480 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2481,DestroyTarget=3137} + +TypeID = 2481 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2472,DestroyTarget=3137} + +TypeID = 2482 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2480,DestroyTarget=3137} + +TypeID = 2483 +Name = "a large trunk" +Flags = {Container,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2486,DestroyTarget=3140} + +TypeID = 2484 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2485,DestroyTarget=3140} + +TypeID = 2485 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2483,DestroyTarget=3140} + +TypeID = 2486 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2484,DestroyTarget=3140} + +TypeID = 2487 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2495} + +TypeID = 2488 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2496} + +TypeID = 2489 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2501} + +TypeID = 2490 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2502} + +TypeID = 2491 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2499} + +TypeID = 2492 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2500} + +TypeID = 2493 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2497} + +TypeID = 2494 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2498} + +TypeID = 2495 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2487} + +TypeID = 2496 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2488} + +TypeID = 2497 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2493} + +TypeID = 2498 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2494} + +TypeID = 2499 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2491} + +TypeID = 2500 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2492} + +TypeID = 2501 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2489} + +TypeID = 2502 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2490} + +TypeID = 2503 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2507} + +TypeID = 2504 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2508} + +TypeID = 2505 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2509} + +TypeID = 2506 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2510} + +TypeID = 2507 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2503} + +TypeID = 2508 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2504} + +TypeID = 2509 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2505} + +TypeID = 2510 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2506} + +TypeID = 2511 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2512 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2513 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2514 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2515 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2516 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2517 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2518 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2519 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid} +Attributes = {Capacity=25,DestroyTarget=3138} + +TypeID = 2520 +Name = "a water cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 2521 +Name = "a lemonade cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=LEMONADE} + +TypeID = 2522 +Name = "a wine cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2523 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid} +Attributes = {Capacity=25,DestroyTarget=3135} + +TypeID = 2524 +Name = "a trough" +Flags = {MultiUse,FluidContainer,Unpass,Destroy,Height} +Attributes = {DestroyTarget=3135} + +TypeID = 2525 +Name = "a beer cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BEER} + +TypeID = 2526 +Name = "a dustbin" +Flags = {CollisionEvent,Unpass,Unmove} + +TypeID = 2527 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2528 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2529 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2530 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2531 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2532 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2533 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2534 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2535 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2536,Brightness=3,LightColor=199} + +TypeID = 2536 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2535,Brightness=0,LightColor=215} + +TypeID = 2537 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2538,Brightness=3,LightColor=193} + +TypeID = 2538 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2537,Brightness=0,LightColor=215} + +TypeID = 2539 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2540,Brightness=3,LightColor=193} + +TypeID = 2540 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2539,Brightness=0,LightColor=215} + +TypeID = 2541 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2542,Brightness=3,LightColor=193} + +TypeID = 2542 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2541,Brightness=0,LightColor=215} + +TypeID = 2543 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2469} + +TypeID = 2544 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2476} + +TypeID = 2545 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2474} + +TypeID = 2546 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2547 +Name = "a bananapalm" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3639} + +TypeID = 2548 +Name = "a dead dragon" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4025} + +TypeID = 2549 +Name = "a honeyflower patch" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2984} + +TypeID = 2550 +Name = "a dead human" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4240} + +TypeID = 2551 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2473} + +TypeID = 2552 +Name = "a dead tree" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3634} + +TypeID = 2553 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2433} + +TypeID = 2554 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2434} + +TypeID = 2555 +Name = "a small hole" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=130,DisguiseTarget=387} + +TypeID = 2556 +Name = "a loose board" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=100,DisguiseTarget=408} + +TypeID = 2557 +Name = "a pile of bones" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4285} + +TypeID = 2558 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=2435} + +TypeID = 2559 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=2438} + +TypeID = 2560 +Name = "a stone coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1983} + +TypeID = 2561 +Name = "a barrel" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2523} + +TypeID = 2562 +Name = "a hollow stone" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=1777} + +TypeID = 2563 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=4305} + +TypeID = 2564 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1994} + +TypeID = 2565 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1992} + +TypeID = 2566 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2567 +Name = "a lever" +Description = "It doesn't move" +Flags = {UseEvent,Unmove,Expire,Disguise} +Attributes = {ExpireTarget=2566,TotalExpireTime=240,DisguiseTarget=2773} + +TypeID = 2568 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2054} + +TypeID = 2569 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2570 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2773} + +TypeID = 2571 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire,Disguise} +Attributes = {ExpireTarget=0,TotalExpireTime=300,DisguiseTarget=1772} + +TypeID = 2572 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2573 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2574 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2575 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2576 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2577 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2578 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2579 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2580 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2581 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2582 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2583 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2584 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2585 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2586 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2587 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2588 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2589 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2590 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2591 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2592 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2593 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2594 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2595 +Name = "a badger fur" +Flags = {Clip,Unmove} + +TypeID = 2596 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2597 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2598 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2599 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2600 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2601 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2602 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2603 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2604 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2605 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2606 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2607 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2608 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2609 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2610 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2611 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2612 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2613 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2614 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2615 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2616 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2617 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2618 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2619 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2620 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2621 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2622 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2623 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2624 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2625 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2626 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2627 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2628 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2629 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2630 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2631 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2632 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2633 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2634 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2635 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2636 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2637 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2638 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2639 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2640 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2641 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2642 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2643 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2644 +Name = "a purple tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2645 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2646 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2647 +Name = "a green tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2648 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2649 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2650 +Name = "a yellow tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2651 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2652 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2653 +Name = "an orange tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2654 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2655 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2656 +Name = "a red tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2657 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2658 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2659 +Name = "a blue tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2660 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2661,TotalExpireTime=595} + +TypeID = 2661 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2660,TotalExpireTime=5} + +TypeID = 2662 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=595} + +TypeID = 2663 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=5} + +TypeID = 2664 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2668,TotalExpireTime=600} + +TypeID = 2665 +Name = "a white tapestry" +Flags = {Unmove} + +TypeID = 2666 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2667 +Name = "a white tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2668 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2664,TotalExpireTime=5} + +TypeID = 2669 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2670 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2671 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2672 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2673 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2674 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2675 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2676 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2677 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2678 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2679 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2680 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2681 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2682 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2683 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2684 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2685 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2686 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2687 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2688 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2689 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2690 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2691 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2692 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2693 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2694 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2695 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2696 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2697 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2698 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2699 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2700 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2701 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2702 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2703 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2704 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2705 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2706 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2707 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2708 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2709 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2710 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2711 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2712 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2713 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2714 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2715 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2716 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2717 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2718 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2719 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2720 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2721 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2722 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2723 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2724 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2725 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2726 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2727 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2728 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2729 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2730 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2731 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2732 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2733 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2734 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2735 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2736 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2737 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2738 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2739 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2740 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2741 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2742 +Name = "a pile of chopped wood" +Flags = {Unpass,Unmove} + +TypeID = 2743 +Name = "a block of wood" +Description = "It's a lumberjack's working place" +Flags = {Unpass,Unmove} + +TypeID = 2744 +Name = "some pieces of wood" +Flags = {Unmove} + +TypeID = 2745 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2746 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2747 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2748 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2749 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2750 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2751 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2752 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2753 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2754 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2755 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2756 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2757 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2758 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2759 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2760 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2761 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2762 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2763 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2764 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2765 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2766 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2767 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2768 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2769 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2770 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2771 +Name = "a sundial" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2772 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2773 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2774 +Name = "a torch bearer" +Flags = {UseEvent,Unmove,Hang,Disguise} +Attributes = {Brightness=0,LightColor=215,DisguiseTarget=2928} + +TypeID = 2775 +Name = "a furniture package" +Description = "It contains a construction kit for a red cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2776 +Name = "a furniture package" +Description = "It contains a construction kit for a green cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2777 +Name = "a furniture package" +Description = "It contains a construction kit for a wooden chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2778 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2779 +Name = "a furniture package" +Description = "It contains a construction kit for a sofa chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2780 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2781 +Name = "a furniture package" +Description = "It contains a construction kit for a ivory chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2782 +Name = "a furniture package" +Description = "It contains a construction kit for a small table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2783 +Name = "a furniture package" +Description = "It contains a construction kit for a round table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2784 +Name = "a furniture package" +Description = "It contains a construction kit for a square table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2785 +Name = "a furniture package" +Description = "It contains a construction kit for a big table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2786 +Name = "a furniture package" +Description = "It contains a construction kit for a stone table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2787 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2788 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2789 +Name = "a furniture package" +Description = "It contains a construction kit for a drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2790 +Name = "a furniture package" +Description = "It contains a construction kit for a dresser" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2791 +Name = "a furniture package" +Description = "It contains a construction kit for a locker" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2792 +Name = "a furniture package" +Description = "It contains a construction kit for a trough" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2793 +Name = "a furniture package" +Description = "It contains a construction kit for a barrel" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2794 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2795 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2796 +Name = "a furniture package" +Description = "It contains a construction kit for a birdcage" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2797 +Name = "a furniture package" +Description = "It contains a construction kit for a globe" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2798 +Name = "a furniture package" +Description = "It contains a construction kit for a table lamp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2799 +Name = "a furniture package" +Description = "It contains a construction kit for a telescope" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2800 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking horse" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2801 +Name = "a furniture package" +Description = "It contains a construction kit for a pendulum clock" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2802 +Name = "a furniture package" +Description = "It contains a construction kit for a knight statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2803 +Name = "a furniture package" +Description = "It contains a construction kit for a minotaur statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2804 +Name = "a furniture package" +Description = "It contains a construction kit for a goblin statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2805 +Name = "a furniture package" +Description = "It contains a construction kit for a large amphora" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2806 +Name = "a furniture package" +Description = "It contains a construction kit for a coal basin" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2807 +Name = "a furniture package" +Description = "It contains a construction kit for a piano" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2808 +Name = "a furniture package" +Description = "It contains a construction kit for a harp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2809 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2810 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2811 +Name = "a furniture package" +Description = "It contains an indoor plant" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2812 +Name = "a furniture package" +Description = "It contains a christmas tree" +Flags = {UseEvent,Avoid,Take,Expire,Height} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2813 +Name = "a blank paper" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2814 +Name = "a parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2815 +Name = "a scroll" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2816 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2817 +Name = "a blank parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2818 +Name = "a document" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=150} + +TypeID = 2819 +Name = "a parchment" +Flags = {Text,Take} +Attributes = {Weight=200} + +TypeID = 2820 +Name = "a paper" +Flags = {Text,Take} +Attributes = {Weight=100} + +TypeID = 2821 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2822 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 2823 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=790} + +TypeID = 2824 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2825 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2826 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2827 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2828 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2829 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2830 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2831 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2832 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2833 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2834 +Name = "a document" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=150} + +TypeID = 2835 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2836 +Name = "the holy Tible" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2837 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2838 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2839 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2840 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2841 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2842 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2843 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2844 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2845 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2846 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2847 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2848 +Name = "a purple tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2849 +Name = "a green tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2850 +Name = "a blue tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2851 +Name = "a grey tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2852 +Name = "a red tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2853 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2854 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2855 +Name = "a basket" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=950} + +TypeID = 2856 +Name = "a present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 2857 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2858 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2859 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2860 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2861 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2862 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2863 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2864 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2865 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2866 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2867 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2868 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2869 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2870 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2871 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2872 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2873 +Name = "a bucket" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=2000} + +TypeID = 2874 +Name = "a vial" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=180} + +TypeID = 2875 +Name = "a bottle" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2876 +Name = "a vase" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2877 +Name = "a green flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2878 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2879 +Name = "an elven vase" +Description = "It is made of very fine glass and covered with decorations" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2880 +Name = "a mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2881 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2882 +Name = "a jug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=750} + +TypeID = 2883 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2884 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2885 +Name = "a brown flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2886 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2887,TotalExpireTime=120} + +TypeID = 2887 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2888,TotalExpireTime=120} + +TypeID = 2888 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2889 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2890,TotalExpireTime=120} + +TypeID = 2890 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2891,TotalExpireTime=120} + +TypeID = 2891 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2892 +Name = "a broken bottle" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 2893 +Name = "an amphora" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=9700} + +TypeID = 2894 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2895 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2896 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2897 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2898 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2899 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2900 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2901 +Name = "a waterskin" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=700} + +TypeID = 2902 +Name = "a bowl" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=380} + +TypeID = 2903 +Name = "a golden mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=470} + +TypeID = 2904 +Name = "a large amphora" +Flags = {MultiUse,FluidContainer,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2905 +Name = "a plate" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 2906 +Name = "a watch" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 2907 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2908,Brightness=0,LightColor=0} + +TypeID = 2908 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2907,Brightness=6,LightColor=206} + +TypeID = 2909 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2910,Brightness=0,LightColor=0} + +TypeID = 2910 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2909,Brightness=6,LightColor=206} + +TypeID = 2911 +Name = "a candelabrum" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2912,Weight=5000,Brightness=0,LightColor=0} + +TypeID = 2912 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206,ExpireTarget=2913,TotalExpireTime=3000} + +TypeID = 2913 +Name = "a used candelabrum" +Flags = {Take} +Attributes = {Weight=4500,Brightness=0,LightColor=0} + +TypeID = 2914 +Name = "a lamp" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2915,Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2915 +Name = "a lit lamp" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2914,Weight=3000,Brightness=6,LightColor=199,ExpireTarget=2916,TotalExpireTime=2000} + +TypeID = 2916 +Name = "a used lamp" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2917 +Name = "a candlestick" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2918,Weight=300,Brightness=0,LightColor=0} + +TypeID = 2918 +Name = "a lit candlestick" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2917,Weight=300,Brightness=4,LightColor=206,ExpireTarget=2919,TotalExpireTime=3000} + +TypeID = 2919 +Name = "a used candlestick" +Flags = {Take} +Attributes = {Weight=250,Brightness=0,LightColor=0} + +TypeID = 2920 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2921,Weight=500,Brightness=0,LightColor=215} + +TypeID = 2921 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2920,Weight=500,Brightness=7,LightColor=206,ExpireTarget=2923,TotalExpireTime=600} + +TypeID = 2922 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2923,Weight=450,Brightness=0,LightColor=215} + +TypeID = 2923 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2922,Weight=450,Brightness=6,LightColor=206,ExpireTarget=2925,TotalExpireTime=300} + +TypeID = 2924 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2925,Weight=400,Brightness=0,LightColor=215} + +TypeID = 2925 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2924,Weight=400,Brightness=5,LightColor=206,ExpireTarget=2926,TotalExpireTime=300} + +TypeID = 2926 +Name = "a burnt down torch" +Flags = {Take,Expire} +Attributes = {Weight=350,Brightness=0,LightColor=215,ExpireTarget=0,TotalExpireTime=300} + +TypeID = 2927 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206} + +TypeID = 2928 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2929,Brightness=0,LightColor=0} + +TypeID = 2929 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2928,Brightness=6,LightColor=206} + +TypeID = 2930 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2931,Brightness=0,LightColor=0} + +TypeID = 2931 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2930,Brightness=6,LightColor=206} + +TypeID = 2932 +Name = "an oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=1400,Brightness=0,LightColor=204} + +TypeID = 2933 +Name = "a small oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=900,Brightness=0,LightColor=204} + +TypeID = 2934 +Name = "a tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2935} + +TypeID = 2935 +Name = "a lit tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2934,Brightness=4,LightColor=207} + +TypeID = 2936 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2937,Brightness=0,LightColor=0} + +TypeID = 2937 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2936,Brightness=6,LightColor=207} + +TypeID = 2938 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2939,Brightness=0,LightColor=0} + +TypeID = 2939 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2938,Brightness=6,LightColor=207} + +TypeID = 2940 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2941,Brightness=0,LightColor=0} + +TypeID = 2941 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2940,Brightness=6,LightColor=206} + +TypeID = 2942 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2943,Brightness=0,LightColor=0} + +TypeID = 2943 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2942,Brightness=6,LightColor=206} + +TypeID = 2944 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2945,Brightness=0,LightColor=0} + +TypeID = 2945 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2944,Brightness=6,LightColor=207} + +TypeID = 2946 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2947,Brightness=0,LightColor=0} + +TypeID = 2947 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2946,Brightness=6,LightColor=207} + +TypeID = 2948 +Name = "a wooden flute" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 2949 +Name = "a lyre" +Flags = {UseEvent,Take} +Attributes = {Weight=1250} + +TypeID = 2950 +Name = "a lute" +Flags = {UseEvent,Take} +Attributes = {Weight=3400} + +TypeID = 2951 +Name = "a bongo drum" +Flags = {Take} +Attributes = {Weight=2900} + +TypeID = 2952 +Name = "a drum" +Flags = {UseEvent,Take} +Attributes = {Weight=3200} + +TypeID = 2953 +Name = "panpipes" +Flags = {UseEvent,Take} +Attributes = {Weight=820} + +TypeID = 2954 +Name = "a simple fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 2955 +Name = "a fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2300} + +TypeID = 2956 +Name = "a royal fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2500} + +TypeID = 2957 +Name = "a post horn" +Description = "It's property of the Postmaster's Guild and only rewarded to loyal members" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2958 +Name = "a war horn" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2959 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2962,DestroyTarget=3139} + +TypeID = 2960 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2961,DestroyTarget=3139} + +TypeID = 2961 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2959,DestroyTarget=3139} + +TypeID = 2962 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2960,DestroyTarget=3139} + +TypeID = 2963 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2964,DestroyTarget=3136} + +TypeID = 2964 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2963,DestroyTarget=3136} + +TypeID = 2965 +Name = "a didgeridoo" +Flags = {UseEvent,Take} +Attributes = {Weight=4200} + +TypeID = 2966 +Name = "a war drum" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 2967 +Name = "a magical key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2968 +Name = "a wooden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2969 +Name = "a silver key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2970 +Name = "a copper key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2971 +Name = "a crystal key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2972 +Name = "a golden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2973 +Name = "a bone key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2974 +Name = "a water pipe" +Flags = {UseEvent,Take,Destroy} +Attributes = {Weight=6500,DestroyTarget=3143} + +TypeID = 2975 +Name = "a birdcage" +Description = "The poor bird seems to have died from a heart attack" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2976 +Name = "a birdcage" +Description = "You see a little bird inside" +Flags = {UseEvent,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2977 +Name = "a pumpkinhead" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=950} + +TypeID = 2978 +Name = "a pumpkinhead" +Flags = {Take,Expire} +Attributes = {Weight=1250,Brightness=3,LightColor=200,ExpireTarget=2977,TotalExpireTime=3000} + +TypeID = 2979 +Name = "a globe" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3143} + +TypeID = 2980 +Name = "a water pipe" +Flags = {Take,Destroy} +Attributes = {Weight=5600,DestroyTarget=3143} + +TypeID = 2981 +Name = "god flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2982 +Name = "an indoor plant" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2983 +Name = "a flower bowl" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2984 +Name = "a honey flower" +Flags = {Avoid,Take} +Attributes = {Weight=1000} + +TypeID = 2985 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2986 +Name = "a christmas tree" +Flags = {Unpass,Unlay,Destroy,Expire} +Attributes = {DestroyTarget=3140,Brightness=4,LightColor=204,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2987 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2988 +Name = "exotic flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2989 +Name = "a wooden doll" +Flags = {Take} +Attributes = {Weight=860} + +TypeID = 2990 +Name = "a football" + +TypeID = 2991 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 2992 +Name = "a snowball" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=80,Range=7,Attack=0,Defense=0,MissileEffect=13,Fragility=100} + +TypeID = 2993 +Name = "a teddy bear" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 2994 +Name = "a model ship" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 2995 +Name = "a piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2996 +Name = "a broken piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2997 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2998,DestroyTarget=3137} + +TypeID = 2998 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2999,DestroyTarget=3137} + +TypeID = 2999 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3000,DestroyTarget=3137} + +TypeID = 3000 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2997,DestroyTarget=3137} + +TypeID = 3001 +Name = "a bear doll" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 3002 +Name = "a voodoo doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3003 +Name = "a rope" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1800} + +TypeID = 3004 +Name = "a wedding ring" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3005 +Name = "an elven brooch" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 3006 +Name = "a ring of the sky" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3007 +Name = "a crystal ring" +Description = "The magical ring will convert the gold you touch" +Flags = {Take,ShowDetail} +Attributes = {Weight=90,SlotType=RING,TotalUses=100} + +TypeID = 3008 +Name = "a crystal necklace" +Flags = {Take} +Attributes = {Weight=490,SlotType=NECKLACE} + +TypeID = 3009 +Name = "a bronze necklace" +Flags = {Take} +Attributes = {Weight=410,SlotType=NECKLACE} + +TypeID = 3010 +Name = "an emerald bangle" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3011 +Name = "a crown" +Flags = {Take} +Attributes = {Weight=1900,SlotType=HEAD} + +TypeID = 3012 +Name = "a wolf tooth chain" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 3013 +Name = "a golden amulet" +Description = "Many gems glitter on the amulet" +Flags = {Take} +Attributes = {Weight=830,SlotType=NECKLACE} + +TypeID = 3014 +Name = "a star amulet" +Flags = {Take} +Attributes = {Weight=610,SlotType=NECKLACE} + +TypeID = 3015 +Name = "a silver necklace" +Flags = {Take} +Attributes = {Weight=480,SlotType=NECKLACE} + +TypeID = 3016 +Name = "a ruby necklace" +Flags = {Take} +Attributes = {Weight=570,SlotType=NECKLACE} + +TypeID = 3017 +Name = "a silver brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3018 +Name = "a scarab amulet" +Flags = {Take} +Attributes = {Weight=770,SlotType=NECKLACE} + +TypeID = 3019 +Name = "a demonbone amulet" +Flags = {Take} +Attributes = {Weight=690,SlotType=NECKLACE} + +TypeID = 3020 +Name = "some golden fruits" +Flags = {Take} +Attributes = {Weight=1070} + +TypeID = 3021 +Name = "a saphire amulet" +Flags = {Take} +Attributes = {Weight=680,SlotType=NECKLACE} + +TypeID = 3022 +Name = "an ancient tiara" +Flags = {Take} +Attributes = {Weight=820,SlotType=HEAD} + +TypeID = 3023 +Name = "a holy scarab" +Flags = {Take} +Attributes = {Weight=870} + +TypeID = 3024 +Name = "a holy falcon" +Flags = {Take} +Attributes = {Weight=840} + +TypeID = 3025 +Name = "an ancient amulet" +Flags = {Take} +Attributes = {Weight=840,SlotType=NECKLACE} + +TypeID = 3026 +Name = "a white pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3027 +Name = "a black pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3028 +Name = "a small diamond" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3029 +Name = "a small sapphire" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3030 +Name = "a small ruby" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3031 +Name = "a gold coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3032 +Name = "a small emerald" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3033 +Name = "a small amethyst" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3034 +Name = "a talon" +Description = "There are many rumours about these magic gems" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3035 +Name = "a platinum coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3036 +Name = "a violet gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3037 +Name = "a yellow gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3038 +Name = "a green gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3039 +Name = "a red gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3040 +Name = "a gold nugget" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 3041 +Name = "a blue gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3042 +Name = "a scarab coin" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3043 +Name = "a crystal coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3044 +Name = "an elephant tusk" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 3045 +Name = "a strange talisman" +Flags = {Take,ShowDetail} +Attributes = {Weight=290,SlotType=NECKLACE,AbsorbEnergy=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3046 +Name = "a magic light wand" +Flags = {ChangeUse,Take,ExpireStop,ShowDetail} +Attributes = {ChangeTarget=3047,Weight=1500,Brightness=0,LightColor=215} + +TypeID = 3047 +Name = "a magic light wand" +Description = "The wand glows" +Flags = {ChangeUse,Take,Expire,ShowDetail} +Attributes = {ChangeTarget=3046,Weight=1500,Brightness=8,LightColor=209,ExpireTarget=0,TotalExpireTime=3000} + +TypeID = 3048 +Name = "a might ring" +Flags = {Take,ShowDetail} +Attributes = {Weight=100,SlotType=RING,AbsorbPhysical=25,AbsorbMagic=25,ExpireTarget=0,TotalUses=20} + +TypeID = 3049 +Name = "a stealth ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=100,SlotType=RING,EquipTarget=3086} + +TypeID = 3050 +Name = "a power ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3087} + +TypeID = 3051 +Name = "an energy ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3088} + +TypeID = 3052 +Name = "a life ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3089} + +TypeID = 3053 +Name = "a time ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3090} + +TypeID = 3054 +Name = "a silver amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbPoison=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3055 +Name = "a platinum amulet" +Description = "It is an amulet of protection" +Flags = {Take,Armor} +Attributes = {Weight=600,SlotType=NECKLACE,ArmorValue=2} + +TypeID = 3056 +Name = "a bronze amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbManaDrain=15,ExpireTarget=0,TotalUses=200} + +TypeID = 3057 +Name = "an amulet of loss" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3058 +Name = "a strange symbol" +Flags = {MultiUse,Take} +Attributes = {Weight=200,Brightness=2,LightColor=215} + +TypeID = 3059 +Name = "a spellbook" +Flags = {Text,Take} +Attributes = {Weight=5800} + +TypeID = 3060 +Name = "an orb" +Flags = {Take} +Attributes = {Weight=800,Brightness=2,LightColor=26} + +TypeID = 3061 +Name = "a life crystal" +Flags = {Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 3062 +Name = "a mind stone" +Flags = {MultiUse,Take} +Attributes = {Weight=250} + +TypeID = 3063 +Name = "a gold ring" +Flags = {Take} +Attributes = {Weight=100,SlotType=RING} + +TypeID = 3064 +Name = "the orb of nature" +Flags = {Unmove} + +TypeID = 3065 +Name = "a quagmire rod" +Description = "It emits clouds of poisonous swamp gas" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2650,Brightness=2,LightColor=67,Vocations=2,Range=2,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Poison,MissileEffect=15} + +TypeID = 3066 +Name = "a snakebite rod" +Description = "It seems to twitch and quiver as if trying to escape your grip. The rod has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=4300,Vocations=2,Range=4,AttackStrength=13,AttackVariation=5,DamageType=Poison,MissileEffect=15} + +TypeID = 3067 +Name = "a tempest rod" +Description = "It grants you the power of striking your foes with furious thunderstorms" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=2100,Brightness=3,LightColor=29,Vocations=2,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Energy,Range=1,MissileEffect=5} + +TypeID = 3068 +Name = "a crystal wand" +Flags = {Take} +Attributes = {Weight=2800} + +TypeID = 3069 +Name = "a volcanic rod" +Description = "It erupts powerful bursts of magma upon everything in your path" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2900,Brightness=2,LightColor=199,Vocations=2,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3070 +Name = "a moonlight rod" +Description = "Shimmering rays of moonlight radiate from its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=1950,Brightness=3,LightColor=143,Vocations=2,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Energy,Range=2,MissileEffect=5} + +TypeID = 3071 +Name = "a wand of inferno" +Description = "It unleashes the very fires of hell" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=3050,Brightness=3,LightColor=205,Vocations=1,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Fire,Range=2,MissileEffect=4} + +TypeID = 3072 +Name = "a wand of plague" +Description = "Infectious goo covers its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2300,Brightness=2,LightColor=67,Vocations=1,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Poison,Range=2,MissileEffect=15} + +TypeID = 3073 +Name = "a wand of cosmic energy" +Description = "The energy of a radiant star is trapped inside its globe" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2300,Brightness=2,LightColor=205,Vocations=1,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Energy,Range=1,MissileEffect=5} + +TypeID = 3074 +Name = "a wand of vortex" +Description = "Surges of energy rush through the tip of this wand. The wand has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=2300,Brightness=2,LightColor=23,Vocations=1,AttackStrength=13,AttackVariation=5,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3075 +Name = "a wand of dragonbreath" +Description = "Legends say that this wand holds the soul of a young dragon" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=2300,Brightness=2,LightColor=192,Vocations=1,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3076 +Name = "a crystal ball" +Flags = {Take} +Attributes = {Weight=3400} + +TypeID = 3077 +Name = "an ankh" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3078 +Name = "a mysterious fetish" +Flags = {MultiUse,Take} +Attributes = {Weight=490} + +TypeID = 3079 +Name = "boots of haste" +Flags = {Take} +Attributes = {Weight=750,SlotType=FEET,SpeedBoost=20} + +TypeID = 3080 +Name = "a broken amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3081 +Name = "a stone skin amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=700,SlotType=NECKLACE,AbsorbPhysical=80,ExpireTarget=0,TotalUses=5} + +TypeID = 3082 +Name = "an elven amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=270,SlotType=NECKLACE,AbsorbPhysical=10,AbsorbMagic=10,ExpireTarget=0,TotalUses=50} + +TypeID = 3083 +Name = "a garlic necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=380,SlotType=NECKLACE,AbsorbLifeDrain=20,ExpireTarget=0,TotalUses=150} + +TypeID = 3084 +Name = "a protection amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=550,SlotType=NECKLACE,AbsorbPhysical=6,ExpireTarget=0,TotalUses=250} + +TypeID = 3085 +Name = "a dragon necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=630,SlotType=NECKLACE,AbsorbFire=8,ExpireTarget=0,TotalUses=200} + +TypeID = 3086 +Name = "a stealth ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=100,SlotType=RING,Invisible=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3049} + +TypeID = 3087 +Name = "a power ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,FistBoost=6,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3050} + +TypeID = 3088 +Name = "an energy ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ManaShield=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3051} + +TypeID = 3089 +Name = "a life ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=6000,HealthGain=2,ManaTicks=6000,ManaGain=8,ExpireTarget=0,TotalExpireTime=1200,DeEquipTarget=3052} + +TypeID = 3090 +Name = "a time ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SpeedBoost=30,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3053} + +TypeID = 3091 +Name = "a sword ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3094} + +TypeID = 3092 +Name = "an axe ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3095} + +TypeID = 3093 +Name = "a club ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3096} + +TypeID = 3094 +Name = "a sword ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SwordBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3091} + +TypeID = 3095 +Name = "an axe ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,AxeBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3092} + +TypeID = 3096 +Name = "a club ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,ClubBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3093} + +TypeID = 3097 +Name = "a dwarven ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=110,SlotType=RING,EquipTarget=3099} + +TypeID = 3098 +Name = "a ring of healing" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3100} + +TypeID = 3099 +Name = "a dwarven ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=110,SlotType=RING,SuppressDrunk=1,ExpireTarget=0,TotalExpireTime=3600,DeEquipTarget=3097} + +TypeID = 3100 +Name = "a ring of healing" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=6000,HealthGain=6,ManaTicks=6000,ManaGain=24,ExpireTarget=0,TotalExpireTime=450,DeEquipTarget=3098} + +TypeID = 3101 +Name = "a screaming spellbook" +Description = "To humble, or not to humble, that is the question" +Flags = {Unmove,Unlay,Unthrow,Unpass,UseEvent} +Attributes = {Weight=5800} + +TypeID = 3102 +Name = "a paw amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3103 +Name = "a cornucopia" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 3104 +Name = "a banana skin" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3105 +Name = "a dirty fur" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3106 +Name = "an old twig" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3107 +Name = "some wood" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3108 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3109 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3110 +Name = "a piece of iron" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 3111 +Name = "a fishbone" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3112 +Name = "rotten meat" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3113 +Name = "broken pottery" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3114 +Name = "a skull" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3115 +Name = "a bone" +Flags = {Take} +Attributes = {Weight=950} + +TypeID = 3116 +Name = "a big bone" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 3117 +Name = "broken brown glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3118 +Name = "broken green glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3119 +Name = "a broken sword" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 3120 +Name = "a moldy cheese" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 3121 +Name = "a torn book" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 3122 +Name = "a dirty cape" +Flags = {Take} +Attributes = {Weight=2950} + +TypeID = 3123 +Name = "worn leather boots" +Flags = {Take} +Attributes = {Weight=900} + +TypeID = 3124 +Name = "a burnt scroll" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3125 +Name = "remains of a fish" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3126 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3127 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3128 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3129 +Name = "some leaves" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3130 +Name = "twigs" +Flags = {Take} +Attributes = {Weight=210} + +TypeID = 3131 +Name = "burnt down firewood" +Flags = {Take} +Attributes = {Weight=420} + +TypeID = 3132 +Name = "an animal skull" +Flags = {Unmove} + +TypeID = 3133 +Name = "humanoid remains" +Flags = {Unmove} + +TypeID = 3134 +Name = "ashes" +Flags = {Unmove} + +TypeID = 3135 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3136 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3137 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3138 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3139 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3140 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3141 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3142 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3143 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3144 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3145 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2296,TotalExpireTime=120} + +TypeID = 3146 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2295,TotalExpireTime=120} + +TypeID = 3147 +Name = "a blank rune" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3148 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3149 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3150 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3151 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3152 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3153 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3154 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3155 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3156 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3157 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3158 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3159 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3160 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3161 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3162 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3163 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3164 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3165 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3166 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3167 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3168 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3169 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3170 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3171 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3172 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3173 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3174 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3175 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3176 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3177 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3178 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3179 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3180 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3181 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3182 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3183 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3184 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3185 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3186 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3187 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3188 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3189 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3190 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3191 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3192 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3193 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3194 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3195 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3196 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3197 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3198 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3199 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3200 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3201 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3202 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3203 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3204 +Name = "your own dead body" +Flags = {Unmove} + +TypeID = 3205 +Name = "a family brooch" +Description = "You see the familyname Windtrouser engraved on this brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3206 +Name = "a dragonfetish" +Flags = {Take} +Attributes = {Weight=490} + +TypeID = 3207 +Name = "the skull of Ratha" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3208 +Name = "a giant smithhammer" +Description = "This cyclopean hammer seems to be an awesome smithing tool" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3209 +Name = "a voodoodoll" +Description = "This voodoodoll looks like a little king" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3210 +Name = "a hat of the mad" +Description = "You have a vague feeling that it looks somewhat silly" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3211 +Name = "a witchesbroom" +Description = "Don't use it without flying license. Not suitable for minors" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3212 +Name = "a monks diary" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 3213 +Name = "an annihilation bear" +Description = "I braved the Annihilator and all I got is this lousy teddy bear" +Flags = {Take} +Attributes = {Weight=4300} + +TypeID = 3214 +Name = "a blessed ankh" +Description = "You see the engraving of a white raven on its surface" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3215 +Name = "a phoenix egg" +Description = "It seems to be burning from inside" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3216 +Name = "a bill" +Description = "This is a bill for an expensive magicians hat and several rabbits" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3217 +Name = "a letterbag" +Description = "This bag is nearly bursting from all the letters inside" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=50000,SlotType=BACKPACK} + +TypeID = 3218 +Name = "a present" +Flags = {UseEvent,Take} +Attributes = {Weight=1200} + +TypeID = 3219 +Name = "Waldos Posthorn" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 3220 +Name = "a letter to Markwin" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3221 +Name = "Santa's Mailbox" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3222 +Name = "a helmet ornament" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=160} + +TypeID = 3223 +Name = "a gem holder" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3224 +Name = "a right horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3225 +Name = "a left horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3226 +Name = "a damaged helmet" +Description = "This item seems to have several parts missing" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=HEAD,ArmorValue=5} + +TypeID = 3227 +Name = "a helmet piece" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=260} + +TypeID = 3228 +Name = "a helmet adornement" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3229 +Name = "a helmet of the ancients" +Description = "The gem of the helmet is burned out and should be replaced" +Flags = {MultiUse,UseEvent,Take,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ArmorValue=8} + +TypeID = 3230 +Name = "a helmet of the ancients" +Description = "The gem is glowing with power" +Flags = {Take,Expire,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ExpireTarget=3229,TotalExpireTime=1800,ArmorValue=11} + +TypeID = 3231 +Name = "a gemmed lamp" +Description = "It is Fa'hradin's enchanted lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3232 +Name = "a spyreport" +Description = "The report is written in some coded language" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3233 +Name = "a tear of daraman" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3234 +Name = "a cookbook" +Description = "It contains several exotic recipes" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 3235 +Name = "an ancient rune" +Description = "This rune vibrates with ancient powers. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=300,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3236 +Name = "blue note" +Description = "The blue crystal is softly humming a ghostly melody. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=250,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3237 +Name = "a sword hilt" +Description = "This was once part of a formidable two handed weapon. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3238 +Name = "a cobrafang dagger" +Description = "This ritual weapon was forged from the sharp fang of a giant cobra. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3239 +Name = "a crystal arrow" +Description = "This arrow seems not suitable for the use with ordinary bows. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=100,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3240 +Name = "a burning heart" +Description = "The burning heart is still beating with unholy life. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=400,Brightness=1,LightColor=193,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3241 +Name = "an ornamented ankh" +Description = "This ancient relic shows signs of untold age. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3242 +Name = "a stuffed bunny" +Flags = {Take} +Attributes = {Weight=350} + +TypeID = 3243 +Name = "a gemmed lamp" +Description = "It is the djinn leader's sleeping lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3244 +Name = "an old and used backpack" +Description = "A label on the backpack reads: Property of Sam, Thais" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 3245 +Name = "a ring of wishes" +Description = "(This item has 3 charges left)" +Flags = {Take} +Attributes = {Weight=50,SlotType=RING} + +TypeID = 3246 +Name = "boots of waterwalking" +Description = "(This item has 5 charges left)" +Flags = {Take} +Attributes = {Weight=770,SlotType=FEET} + +TypeID = 3247 +Name = "a djinn's lamp" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 3248 +Name = "a portable hole" +Description = "(This item has 1 charge left)" +Flags = {Unmove} + +TypeID = 3249 +Name = "frozen starlight" +Flags = {Take} +Attributes = {Weight=20,Brightness=6,LightColor=29} + +TypeID = 3250 +Name = "the carrot of doom" +Description = "You can sense the evil power of the carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3251 +Name = "a blood orb" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3252 +Name = "the horn of sundering" +Description = "(This items has 2 charges left)" +Flags = {Take} +Attributes = {Weight=2300} + +TypeID = 3253 +Name = "a backpack of holding" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3254 +Name = "a roc feather" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3255 +Name = "a drum" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3256 +Name = "a trumpet" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3257 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3258 +Name = "a mandolin" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3259 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3260 +Name = "a lyre" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3261 +Name = "a panpipe" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3262 +Name = "a flute" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3263 +Name = "a gemmed lamp" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3264 +Name = "a sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=14,Defense=12} + +TypeID = 3265 +Name = "a two handed sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=30,Defense=25} + +TypeID = 3266 +Name = "a battle axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,SlotType=TWOHANDED,WeaponType=AXE,Attack=25,Defense=10} + +TypeID = 3267 +Name = "a dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=950,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3268 +Name = "a hand axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1800,WeaponType=AXE,Attack=10,Defense=5} + +TypeID = 3269 +Name = "a halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=14} + +TypeID = 3270 +Name = "a club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=CLUB,Attack=7,Defense=7} + +TypeID = 3271 +Name = "a spike sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=SWORD,Attack=24,Defense=21} + +TypeID = 3272 +Name = "a rapier" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,WeaponType=SWORD,Attack=10,Defense=8} + +TypeID = 3273 +Name = "a sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 3274 +Name = "an axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=AXE,Attack=12,Defense=6} + +TypeID = 3275 +Name = "a double axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3276 +Name = "a hatchet" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=AXE,Attack=15,Defense=8} + +TypeID = 3277 +Name = "a spear" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=2000,Range=7,Attack=25,Defense=0,MissileEffect=1,Fragility=3} + +TypeID = 3278 +Name = "a magic longsword" +Description = "It's the magic Cyclopmania Sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4300,SlotType=TWOHANDED,WeaponType=SWORD,Attack=55,Defense=40} + +TypeID = 3279 +Name = "a war hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8500,SlotType=TWOHANDED,WeaponType=CLUB,Attack=45,Defense=10} + +TypeID = 3280 +Name = "a fire sword" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2300,Brightness=3,LightColor=199,WeaponType=SWORD,Attack=35,Defense=20} + +TypeID = 3281 +Name = "a giant sword" +Description = "This sword has been forged by ancient giants" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=18000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=46,Defense=22} + +TypeID = 3282 +Name = "a morning star" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5400,WeaponType=CLUB,Attack=25,Defense=11} + +TypeID = 3283 +Name = "a carlin sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=SWORD,Attack=15,Defense=13} + +TypeID = 3284 +Name = "an ice rapier" +Description = "A deadly but fragile weapon" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,ExpireTarget=0,TotalUses=1,WeaponType=SWORD,Attack=100,Defense=1} + +TypeID = 3285 +Name = "a longsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=17,Defense=14} + +TypeID = 3286 +Name = "a mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=16,Defense=11} + +TypeID = 3287 +Name = "a throwing star" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=200,Range=7,Attack=35,Defense=0,MissileEffect=8,Fragility=10} + +TypeID = 3288 +Name = "a magic sword" +Description = "It's the Sword of Valor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=48,Defense=35} + +TypeID = 3289 +Name = "a staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,WeaponType=CLUB,Attack=10,Defense=25} + +TypeID = 3290 +Name = "a silver dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1020,WeaponType=SWORD,Attack=8,Defense=7} + +TypeID = 3291 +Name = "a knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=420,WeaponType=SWORD,Attack=7,Defense=5} + +TypeID = 3292 +Name = "a combat knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=870,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3293 +Name = "a sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1050,WeaponType=AXE,Attack=7,Defense=4} + +TypeID = 3294 +Name = "a short sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=11,Defense=11} + +TypeID = 3295 +Name = "a bright sword" +Description = "The blade shimmers in light blue" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,Brightness=2,LightColor=143,WeaponType=SWORD,Attack=36,Defense=30} + +TypeID = 3296 +Name = "a warlord sword" +Description = "Strong powers flow in this magic sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=53,Defense=38} + +TypeID = 3297 +Name = "a serpent sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=SWORD,Attack=26,Defense=15} + +TypeID = 3298 +Name = "a throwing knife" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=500,Range=7,Attack=25,Defense=0,MissileEffect=9,Fragility=7} + +TypeID = 3299 +Name = "a poison dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=880,WeaponType=SWORD,Attack=18,Defense=8} + +TypeID = 3300 +Name = "a katana" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3100,WeaponType=SWORD,Attack=16,Defense=12} + +TypeID = 3301 +Name = "a broadsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=SWORD,Attack=26,Defense=23} + +TypeID = 3302 +Name = "a dragon lance" +Description = "The extraordinary sharp blade penetrates every armor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,SlotType=TWOHANDED,WeaponType=AXE,Attack=47,Defense=16} + +TypeID = 3303 +Name = "a great axe" +Description = "A masterpiece of a dwarven smith" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=52,Defense=22} + +TypeID = 3304 +Name = "a crowbar" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=2100,WeaponType=CLUB,Attack=6,Defense=6} + +TypeID = 3305 +Name = "a battle hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3306 +Name = "a golden sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1950,WeaponType=AXE,Attack=13,Defense=6} + +TypeID = 3307 +Name = "a scimitar" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=19,Defense=13} + +TypeID = 3308 +Name = "a machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1650,WeaponType=SWORD,Attack=12,Defense=9} + +TypeID = 3309 +Name = "a thunder hammer" +Description = "It is blessed by the gods of Tibia" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=12500,WeaponType=CLUB,Attack=49,Defense=35} + +TypeID = 3310 +Name = "an iron hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6600,WeaponType=CLUB,Attack=18,Defense=10} + +TypeID = 3311 +Name = "a clerical mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,WeaponType=CLUB,Attack=28,Defense=15} + +TypeID = 3312 +Name = "a silver mace" +Description = "You feel an aura of protection" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3313 +Name = "an obsidian lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=34,Defense=10} + +TypeID = 3314 +Name = "a naginata" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7800,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=25} + +TypeID = 3315 +Name = "a guardian halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=AXE,Attack=46,Defense=15} + +TypeID = 3316 +Name = "an orcish axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4500,WeaponType=AXE,Attack=23,Defense=12} + +TypeID = 3317 +Name = "a barbarian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5100,WeaponType=AXE,Attack=28,Defense=18} + +TypeID = 3318 +Name = "a knight axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5900,WeaponType=AXE,Attack=33,Defense=21} + +TypeID = 3319 +Name = "a stonecutter axe" +Description = "You feel the power of this mighty axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9900,WeaponType=AXE,Attack=50,Defense=30} + +TypeID = 3320 +Name = "a fire axe" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,Brightness=3,LightColor=199,WeaponType=AXE,Attack=38,Defense=16} + +TypeID = 3321 +Name = "an enchanted staff" +Description = "Temporal magic powers enchant this staff" +Flags = {MultiUse,Take,Expire,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,ExpireTarget=3289,TotalExpireTime=60,WeaponType=CLUB,Attack=39,Defense=45} + +TypeID = 3322 +Name = "a dragon hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9700,WeaponType=CLUB,Attack=32,Defense=20} + +TypeID = 3323 +Name = "a dwarven axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8200,WeaponType=AXE,Attack=31,Defense=19} + +TypeID = 3324 +Name = "a skull staff" +Description = "The staff longs for death" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1700,Brightness=2,LightColor=180,WeaponType=CLUB,Attack=36,Defense=12} + +TypeID = 3325 +Name = "a light mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=14,Defense=9} + +TypeID = 3326 +Name = "a foil" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1450,WeaponType=SWORD,Attack=9,Defense=11} + +TypeID = 3327 +Name = "a daramanian mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=21,Defense=12} + +TypeID = 3328 +Name = "a daramanian waraxe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=15} + +TypeID = 3329 +Name = "a daramanian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=AXE,Attack=16,Defense=8} + +TypeID = 3330 +Name = "a heavy machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1840,WeaponType=SWORD,Attack=16,Defense=10} + +TypeID = 3331 +Name = "a ravager's axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=14} + +TypeID = 3332 +Name = "a hammer of wrath" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=48,Defense=12} + +TypeID = 3333 +Name = "a crystal mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,WeaponType=CLUB,Attack=38,Defense=16} + +TypeID = 3334 +Name = "a pharaoh sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=15000,WeaponType=SWORD,Attack=41,Defense=23} + +TypeID = 3335 +Name = "a twin axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=AXE,Attack=45,Defense=24} + +TypeID = 3336 +Name = "a studded club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=CLUB,Attack=9,Defense=8} + +TypeID = 3337 +Name = "a bone club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3900,WeaponType=CLUB,Attack=12,Defense=8} + +TypeID = 3338 +Name = "a bone sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1900,WeaponType=SWORD,Attack=14,Defense=10} + +TypeID = 3339 +Name = "a djinn blade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2450,WeaponType=SWORD,Attack=38,Defense=22} + +TypeID = 3340 +Name = "a heavy mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=50,Defense=15} + +TypeID = 3341 +Name = "an arcane staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=CLUB,Attack=50,Defense=30} + +TypeID = 3342 +Name = "a war axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=20,Defense=10} + +TypeID = 3343 +Name = "a lich staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3344 +Name = "a beastslayer axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3345 +Name = "a templar scytheblade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=23,Defense=15} + +TypeID = 3346 +Name = "a ripper lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=28,Defense=7} + +TypeID = 3347 +Name = "a hunting spear" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=18,Defense=8} + +TypeID = 3348 +Name = "a banana staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=CLUB,Attack=25,Defense=15} + +TypeID = 3349 +Name = "a crossbow" +Flags = {Take,Distance} +Attributes = {Weight=4000,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 3350 +Name = "a bow" +Flags = {Take,Distance} +Attributes = {Weight=3100,SlotType=TWOHANDED,Range=7,AmmoType=ARROW} + +TypeID = 3351 +Name = "a steel helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3352 +Name = "a chain helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3353 +Name = "an iron helmet" +Flags = {Take,Armor} +Attributes = {Weight=3000,SlotType=HEAD,ArmorValue=5} + +TypeID = 3354 +Name = "a brass helmet" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3355 +Name = "a leather helmet" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=1} + +TypeID = 3356 +Name = "a devil helmet" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=HEAD,ArmorValue=7} + +TypeID = 3357 +Name = "a plate armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3358 +Name = "a chain armor" +Flags = {Take,Armor} +Attributes = {Weight=10000,SlotType=BODY,ArmorValue=6} + +TypeID = 3359 +Name = "a brass armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=8} + +TypeID = 3360 +Name = "a golden armor" +Description = "It's an enchanted armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=14} + +TypeID = 3361 +Name = "a leather armor" +Flags = {Take,Armor} +Attributes = {Weight=6000,SlotType=BODY,ArmorValue=4} + +TypeID = 3362 +Name = "studded legs" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=LEGS,ArmorValue=2} + +TypeID = 3363 +Name = "dragon scale legs" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=LEGS,ArmorValue=10} + +TypeID = 3364 +Name = "golden legs" +Flags = {Take,Armor} +Attributes = {Weight=5600,SlotType=LEGS,ArmorValue=9} + +TypeID = 3365 +Name = "a golden helmet" +Description = "It's the famous Helmet of the Stars" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=12} + +TypeID = 3366 +Name = "a magic plate armor" +Description = "An enchanted gem glows on the plate armor" +Flags = {Take,Armor} +Attributes = {Weight=8500,SlotType=BODY,ArmorValue=17} + +TypeID = 3367 +Name = "a viking helmet" +Flags = {Take,Armor} +Attributes = {Weight=3900,SlotType=HEAD,ArmorValue=4} + +TypeID = 3368 +Name = "a winged helmet" +Description = "It's the Helmet of Hermes" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=HEAD,ArmorValue=10} + +TypeID = 3369 +Name = "a warrior helmet" +Flags = {Take,Armor} +Attributes = {Weight=6800,SlotType=HEAD,ArmorValue=8} + +TypeID = 3370 +Name = "a knight armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=12} + +TypeID = 3371 +Name = "knight legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=8} + +TypeID = 3372 +Name = "brass legs" +Flags = {Take,Armor} +Attributes = {Weight=3800,SlotType=LEGS,ArmorValue=5} + +TypeID = 3373 +Name = "a strange helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3374 +Name = "a legion helmet" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=HEAD,ArmorValue=4} + +TypeID = 3375 +Name = "a soldier helmet" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=5} + +TypeID = 3376 +Name = "a studded helmet" +Flags = {Take,Armor} +Attributes = {Weight=2450,SlotType=HEAD,ArmorValue=2} + +TypeID = 3377 +Name = "a scale armor" +Flags = {Take,Armor} +Attributes = {Weight=10500,SlotType=BODY,ArmorValue=9} + +TypeID = 3378 +Name = "a studded armor" +Flags = {Take,Armor} +Attributes = {Weight=7100,SlotType=BODY,ArmorValue=5} + +TypeID = 3379 +Name = "a doublet" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=BODY,ArmorValue=2} + +TypeID = 3380 +Name = "a noble armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=11} + +TypeID = 3381 +Name = "a crown armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3382 +Name = "crown legs" +Flags = {Take,Armor} +Attributes = {Weight=6500,SlotType=LEGS,ArmorValue=8} + +TypeID = 3383 +Name = "a dark armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3384 +Name = "a dark helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3385 +Name = "a crown helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3386 +Name = "a dragon scale mail" +Flags = {Take,Armor} +Attributes = {Weight=11400,SlotType=BODY,ArmorValue=15} + +TypeID = 3387 +Name = "a demon helmet" +Description = "You hear an evil whispering from inside" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=10} + +TypeID = 3388 +Name = "a demon armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=16} + +TypeID = 3389 +Name = "demon legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=9} + +TypeID = 3390 +Name = "a horned helmet" +Flags = {Take,Armor} +Attributes = {Weight=5100,SlotType=HEAD,ArmorValue=11} + +TypeID = 3391 +Name = "a crusader helmet" +Flags = {Take,Armor} +Attributes = {Weight=5200,SlotType=HEAD,ArmorValue=8} + +TypeID = 3392 +Name = "a royal helmet" +Description = "An excellent masterpiece of a smith" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=HEAD,ArmorValue=9} + +TypeID = 3393 +Name = "an amazon helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3394 +Name = "an amazon armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3395 +Name = "a ceremonial mask" +Flags = {Take,Armor} +Attributes = {Weight=4000,SlotType=HEAD,Brightness=3,LightColor=215,ArmorValue=9} + +TypeID = 3396 +Name = "a dwarfen helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3397 +Name = "a dwarven armor" +Flags = {Take,Armor} +Attributes = {Weight=13000,SlotType=BODY,ArmorValue=10} + +TypeID = 3398 +Name = "dwarfen legs" +Flags = {Take,Armor} +Attributes = {Weight=4900,SlotType=LEGS,ArmorValue=6} + +TypeID = 3399 +Name = "an elven mail" +Flags = {Take,Armor} +Attributes = {Weight=9000,SlotType=BODY,ArmorValue=9} + +TypeID = 3400 +Name = "a dragon scale helmet" +Flags = {Take,Armor} +Attributes = {Weight=3250,SlotType=HEAD,ArmorValue=9} + +TypeID = 3401 +Name = "elven legs" +Flags = {Take,Armor} +Attributes = {Weight=3300,SlotType=LEGS,ArmorValue=4} + +TypeID = 3402 +Name = "a native armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=7} + +TypeID = 3403 +Name = "a tribal mask" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=HEAD,ArmorValue=2} + +TypeID = 3404 +Name = "a leopard armor" +Flags = {Take,Armor} +Attributes = {Weight=9500,SlotType=BODY,ArmorValue=9} + +TypeID = 3405 +Name = "a horseman helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3406 +Name = "a feather headdress" +Flags = {Take,Armor} +Attributes = {Weight=2100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3407 +Name = "a charmer's tiara" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3408 +Name = "a beholder helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=7} + +TypeID = 3409 +Name = "a steel shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=21} + +TypeID = 3410 +Name = "a plate shield" +Flags = {Take,Shield} +Attributes = {Weight=6500,Defense=17} + +TypeID = 3411 +Name = "a brass shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=16} + +TypeID = 3412 +Name = "a wooden shield" +Flags = {Take,Shield} +Attributes = {Weight=4000,Defense=14} + +TypeID = 3413 +Name = "a battle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=23} + +TypeID = 3414 +Name = "a mastermind shield" +Description = "It's an enchanted shield" +Flags = {Take,Shield} +Attributes = {Weight=5700,Defense=37} + +TypeID = 3415 +Name = "a guardian shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=30} + +TypeID = 3416 +Name = "a dragon shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=31} + +TypeID = 3417 +Name = "a shield of honour" +Description = "A mighty shield warded by the gods of Tibia" +Flags = {Take,Shield} +Attributes = {Weight=5400,Defense=33} + +TypeID = 3418 +Name = "a beholder shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=28} + +TypeID = 3419 +Name = "a crown shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3420 +Name = "a demon shield" +Description = "This powerful shield seems to be as light as air" +Flags = {Take,Shield} +Attributes = {Weight=2600,Defense=35} + +TypeID = 3421 +Name = "a dark shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=25} + +TypeID = 3422 +Name = "a great shield" +Description = "The shield is made of dragon scales" +Flags = {Take,Shield} +Attributes = {Weight=8400,Defense=38} + +TypeID = 3423 +Name = "a blessed shield" +Description = "The shield grants divine protection" +Flags = {Take,Shield} +Attributes = {Weight=6800,Defense=40} + +TypeID = 3424 +Name = "an ornamented shield" +Description = "Many gems sparkle on the shield" +Flags = {Take,Shield} +Attributes = {Weight=6700,Defense=22} + +TypeID = 3425 +Name = "a dwarven shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=26} + +TypeID = 3426 +Name = "a studded shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=15} + +TypeID = 3427 +Name = "a rose shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=27} + +TypeID = 3428 +Name = "a tower shield" +Flags = {Take,Shield} +Attributes = {Weight=8200,Defense=32} + +TypeID = 3429 +Name = "a black shield" +Description = "An unholy creature covers the shield" +Flags = {Take,Shield} +Attributes = {Weight=4200,Defense=18} + +TypeID = 3430 +Name = "a copper shield" +Flags = {Take,Shield} +Attributes = {Weight=6300,Defense=19} + +TypeID = 3431 +Name = "a viking shield" +Flags = {Take,Shield} +Attributes = {Weight=6600,Defense=22} + +TypeID = 3432 +Name = "an ancient shield" +Flags = {Take,Shield} +Attributes = {Weight=6100,Defense=27} + +TypeID = 3433 +Name = "a griffin shield" +Flags = {Take,Shield} +Attributes = {Weight=5000,Defense=29} + +TypeID = 3434 +Name = "a vampire shield" +Description = "Dark powers enchant this shield" +Flags = {Take,Shield} +Attributes = {Weight=3800,Defense=34} + +TypeID = 3435 +Name = "a castle shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=28} + +TypeID = 3436 +Name = "a medusa shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=33} + +TypeID = 3437 +Name = "an amazon shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3438 +Name = "an eagle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3439 +Name = "a phoenix shield" +Description = "This shield feels warm to the touch" +Flags = {Take,Shield} +Attributes = {Weight=3500,Defense=34} + +TypeID = 3440 +Name = "a scarab shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=25} + +TypeID = 3441 +Name = "a bone shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=20} + +TypeID = 3442 +Name = "a tempest shield" +Flags = {Take,Shield} +Attributes = {Weight=5100,Defense=36} + +TypeID = 3443 +Name = "a tusk shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=27} + +TypeID = 3444 +Name = "a sentinel shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=22} + +TypeID = 3445 +Name = "a salamander shield" +Flags = {Take,Shield} +Attributes = {Weight=5900,Defense=26} + +TypeID = 3446 +Name = "a bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=BOLT,Attack=30,MissileEffect=2,Fragility=100} + +TypeID = 3447 +Name = "an arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=70,AmmoType=ARROW,Attack=25,MissileEffect=3,Fragility=100} + +TypeID = 3448 +Name = "a poison arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=ARROW,Attack=10,MissileEffect=6,Fragility=100,WeaponSpecialEffect=1,AttackStrength=50} + +TypeID = 3449 +Name = "a burst arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=ARROW,Attack=0,MissileEffect=7,Fragility=100,WeaponSpecialEffect=2,AttackStrength=30} + +TypeID = 3450 +Name = "a power bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=BOLT,Attack=40,MissileEffect=14,Fragility=100} + +TypeID = 3451 +Name = "a pitchfork" +Flags = {MultiUse,Take} +Attributes = {Weight=2500} + +TypeID = 3452 +Name = "a rake" +Flags = {MultiUse,Take} +Attributes = {Weight=1500} + +TypeID = 3453 +Name = "a scythe" +Flags = {UseEvent,MultiUse,Take,Weapon} +Attributes = {Weight=3000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=8,Defense=3} + +TypeID = 3454 +Name = "a broom" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3455 +Name = "a hoe" +Flags = {MultiUse,Take} +Attributes = {Weight=2800} + +TypeID = 3456 +Name = "a pick" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=4500} + +TypeID = 3457 +Name = "a shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3500} + +TypeID = 3458 +Name = "an anvil" +Flags = {Unpass,Unmove,Height} + +TypeID = 3459 +Name = "a wooden hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 3460 +Name = "a hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=1150} + +TypeID = 3461 +Name = "a saw" +Flags = {MultiUse,Take} +Attributes = {Weight=1000} + +TypeID = 3462 +Name = "a small axe" +Flags = {MultiUse,Take} +Attributes = {Weight=2000} + +TypeID = 3463 +Name = "a mirror" +Flags = {MultiUse,Take} +Attributes = {Weight=950} + +TypeID = 3464 +Name = "a baking tray" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 3465 +Name = "a pot" +Flags = {MultiUse,FluidContainer,Unpass,Take,Destroy,Height} +Attributes = {Weight=5250,DestroyTarget=3142} + +TypeID = 3466 +Name = "a pan" +Flags = {Take} +Attributes = {Weight=1800} + +TypeID = 3467 +Name = "a fork" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3468 +Name = "a spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3469 +Name = "a knife" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 3470 +Name = "a wooden spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3471 +Name = "a cleaver" +Flags = {Take} +Attributes = {Weight=660} + +TypeID = 3472 +Name = "an oven spatula" +Flags = {Take} +Attributes = {Weight=1400} + +TypeID = 3473 +Name = "a rolling pin" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3474 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3475 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3476 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3477 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3478 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3479 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3480 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3481 +Name = "a closed trap" +Flags = {UseEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3482 +Name = "a trap" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3483 +Name = "a fishing rod" +Flags = {MultiUse,DistUse,UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 3484 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3487,DestroyTarget=3137} + +TypeID = 3485 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3486,DestroyTarget=3137} + +TypeID = 3486 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3484,DestroyTarget=3137} + +TypeID = 3487 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3485,DestroyTarget=3137} + +TypeID = 3488 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3491} + +TypeID = 3489 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3490} + +TypeID = 3490 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3488} + +TypeID = 3491 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3489} + +TypeID = 3492 +Name = "a worm" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3493 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3494 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3495 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3496 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3497 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3498 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3499 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3500 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3501 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3502 +Name = "a depot chest" +Flags = {Container,Unmove} +Attributes = {Capacity=30} + +TypeID = 3503 +Name = "a parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3504 +Name = "a stamped parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3505 +Name = "a letter" +Flags = {Text,Write,Take} +Attributes = {MaxLength=2000,Weight=50} + +TypeID = 3506 +Name = "a stamped letter" +Flags = {Text,Take} +Attributes = {Weight=50} + +TypeID = 3507 +Name = "a label" +Flags = {Text,Write,Take} +Attributes = {MaxLength=80,Weight=10} + +TypeID = 3508 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3509 +Name = "an inkwell" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3510 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=207} + +TypeID = 3511 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3512 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3513 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3514 +Name = "an empty coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=0,LightColor=215} + +TypeID = 3515 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3516 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3517 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3518 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3519 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3520 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3521 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3522 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3523 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3524 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3525 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3526 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3527 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3528 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3529 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3530 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3531 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3532 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3533 +Name = "a black token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3534 +Name = "a white token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3535 +Name = "a white pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3536 +Name = "a white castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3537 +Name = "a white knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3538 +Name = "a white bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3539 +Name = "the white queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3540 +Name = "the white king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3541 +Name = "a black pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3542 +Name = "a black castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3543 +Name = "a black knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3544 +Name = "a black bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3545 +Name = "the black queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3546 +Name = "the black king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3547 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3548 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3549 +Name = "soft boots" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=800,SlotType=FEET,ExpireTarget=0,TotalExpireTime=14400,HealthGain=1,HealthTicks=2000,ManaGain=2,ManaTicks=1000} + +TypeID = 3550 +Name = "patched boots" +Flags = {Take,Armor} +Attributes = {Weight=1000,SlotType=FEET,ArmorValue=2} + +TypeID = 3551 +Name = "sandals" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3552 +Name = "leather boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3553 +Name = "bunnyslippers" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3554 +Name = "steel boots" +Flags = {Take,Armor} +Attributes = {Weight=2900,SlotType=FEET,ArmorValue=3} + +TypeID = 3555 +Name = "golden boots" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=FEET,ArmorValue=4} + +TypeID = 3556 +Name = "crocodile boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3557 +Name = "plate legs" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=LEGS,ArmorValue=7} + +TypeID = 3558 +Name = "chain legs" +Flags = {Take,Armor} +Attributes = {Weight=3500,SlotType=LEGS,ArmorValue=3} + +TypeID = 3559 +Name = "leather legs" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=LEGS,ArmorValue=1} + +TypeID = 3560 +Name = "a bast skirt" +Flags = {Take} +Attributes = {Weight=350,SlotType=LEGS} + +TypeID = 3561 +Name = "a jacket" +Flags = {Take,Armor} +Attributes = {Weight=2400,SlotType=BODY,ArmorValue=1} + +TypeID = 3562 +Name = "a coat" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=BODY,ArmorValue=1} + +TypeID = 3563 +Name = "a green tunic" +Flags = {Take,Armor} +Attributes = {Weight=930,SlotType=BODY,ArmorValue=1} + +TypeID = 3564 +Name = "a red tunic" +Flags = {Take,Armor} +Attributes = {Weight=1400,SlotType=BODY,ArmorValue=2} + +TypeID = 3565 +Name = "a cape" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3566 +Name = "a red robe" +Description = "The robe is artfully embroidered" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=BODY,ArmorValue=1} + +TypeID = 3567 +Name = "a blue robe" +Description = "It is a magic robe" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=BODY,ArmorValue=11} + +TypeID = 3568 +Name = "a simple dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3569 +Name = "a white dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3570 +Name = "a ball gown" +Flags = {Take} +Attributes = {Weight=2500,SlotType=BODY} + +TypeID = 3571 +Name = "a rangers cloak" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3572 +Name = "a scarf" +Flags = {Take,Armor} +Attributes = {Weight=200,SlotType=NECKLACE,ArmorValue=1} + +TypeID = 3573 +Name = "a magician hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 3574 +Name = "a mystic turban" +Description = "Something is strange about this turban" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 3575 +Name = "a wood cape" +Flags = {Take,Armor} +Attributes = {Weight=1100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3576 +Name = "a post officers hat" +Description = "This hat is the insignia of all tibian post officers" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=1} + +TypeID = 3577 +Name = "meat" +Flags = {Cumulative,Take} +Attributes = {Nutrition=15,Weight=1300} + +TypeID = 3578 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=520} + +TypeID = 3579 +Name = "salmon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=320} + +TypeID = 3580 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=17,Weight=830} + +TypeID = 3581 +Name = "shrimp" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3582 +Name = "ham" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=2000} + +TypeID = 3583 +Name = "dragon ham" +Description = "It still contains a small part of the power of a dragon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=60,Weight=3000} + +TypeID = 3584 +Name = "a pear" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=140} + +TypeID = 3585 +Name = "a red apple" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=150} + +TypeID = 3586 +Name = "an orange" +Flags = {Cumulative,Take} +Attributes = {Nutrition=13,Weight=110} + +TypeID = 3587 +Name = "a banana" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=180} + +TypeID = 3588 +Name = "a blueberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3589 +Name = "a coconut" +Flags = {Cumulative,Take} +Attributes = {Nutrition=18,Weight=480} + +TypeID = 3590 +Name = "a cherry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3591 +Name = "a strawberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=20} + +TypeID = 3592 +Name = "grapes" +Flags = {Take} +Attributes = {Nutrition=9,Weight=250} + +TypeID = 3593 +Name = "a melon" +Flags = {Take} +Attributes = {Nutrition=20,Weight=950} + +TypeID = 3594 +Name = "a pumpkin" +Flags = {Take} +Attributes = {Nutrition=17,Weight=1350} + +TypeID = 3595 +Name = "a carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3596 +Name = "a tomato" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=100} + +TypeID = 3597 +Name = "a corncob" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=350} + +TypeID = 3598 +Name = "a cookie" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=10} + +TypeID = 3599 +Name = "a candy cane" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=50} + +TypeID = 3600 +Name = "a bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 3601 +Name = "a roll" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=100} + +TypeID = 3602 +Name = "a brown bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=400} + +TypeID = 3603 +Name = "flour" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3604 +Name = "a lump of dough" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3605 +Name = "a bunch of wheat" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=1250} + +TypeID = 3606 +Name = "an egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 3607 +Name = "cheese" +Flags = {Take} +Attributes = {Nutrition=9,Weight=400} + +TypeID = 3608 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3609 +Name = "a snowy fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3610 +Name = "a plum tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3617} + +TypeID = 3611 +Name = "a firtree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3614} + +TypeID = 3612 +Name = "a dead tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3634} + +TypeID = 3613 +Name = "a holy tree" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=143} + +TypeID = 3614 +Name = "a fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3615 +Name = "a sycamore" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3616 +Name = "a willow" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3617 +Name = "a plum tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3618 +Name = "a red maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3619 +Name = "a pear tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3620 +Name = "a yellow maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3621 +Name = "a beech" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3622 +Name = "a poplar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3623 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3624 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3625 +Name = "a dwarf tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3626 +Name = "a pine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3627 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3628 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3629 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3630 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3631 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3632 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3633 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3634 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3635 +Name = "old rush wood" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3636 +Name = "an old tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3637 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3638 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3639 +Name = "a palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3640 +Name = "a coconut palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3641 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3642 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3643 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3644 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3645 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3646 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3647 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3648 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3649 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3650 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3651 +Name = "wheat" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3652,TotalExpireTime=43200} + +TypeID = 3652 +Name = "wheat" +Description = "It's not mature yet" +Flags = {Unmove,Avoid,Expire} +Attributes = {ExpireTarget=3653,TotalExpireTime=43200} + +TypeID = 3653 +Name = "wheat" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3654 +Name = "moon flowers" +Flags = {Unmove} + +TypeID = 3655 +Name = "a moon flower" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3656 +Name = "a white flower" +Flags = {Unmove} + +TypeID = 3657 +Name = "a heaven blossom" +Flags = {Unmove} + +TypeID = 3658 +Name = "a red rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3659 +Name = "a blue rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3660 +Name = "a yellow rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3661 +Name = "a grave flower" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3662 +Name = "a love flower" +Flags = {Unmove} + +TypeID = 3663 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3664 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3665 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3666 +Name = "some sunflowers" +Flags = {Unmove,Avoid} + +TypeID = 3667 +Name = "a sunflower" +Flags = {Unmove} + +TypeID = 3668 +Name = "a tulip" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3669 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3670 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3671 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3672 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3673 +Name = "an orange star" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3674 +Name = "a goat grass" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3675 +Name = "an orchid" +Flags = {Unmove} + +TypeID = 3676 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3677 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3678 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3679 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3680 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3681 +Name = "a bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3682 +Name = "a small fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3683 +Name = "a shadow plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3684 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3685 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3686 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3687 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3688 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3689 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3690 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3691 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3692 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3693 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3694 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3695 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3696,TotalExpireTime=300} + +TypeID = 3696 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3697 +Name = "an agave" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3698 +Name = "a dry bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3699 +Name = "a blueberry bush" +Flags = {UseEvent,Bottom,Unpass,Unmove,Unlay} + +TypeID = 3700 +Name = "a blueberry bush" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=3699,TotalExpireTime=3600} + +TypeID = 3701 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3702,TotalExpireTime=300} + +TypeID = 3702 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3703 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3704 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3705 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3706 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3707 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3708 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3709 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3710 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3711 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3712 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3713 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3714 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3715 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3716 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3717 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3718 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3719 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3720 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3721 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3722 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3723 +Name = "a white mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=40} + +TypeID = 3724 +Name = "a red mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3725 +Name = "a brown mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=22,Weight=20} + +TypeID = 3726 +Name = "an orange mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=30} + +TypeID = 3727 +Name = "a wood mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=30} + +TypeID = 3728 +Name = "a dark mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=10} + +TypeID = 3729 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=10} + +TypeID = 3730 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=10} + +TypeID = 3731 +Name = "a fire mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=36,Weight=10} + +TypeID = 3732 +Name = "a green mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 3733 +Name = "dark mushrooms" +Flags = {Unmove,Hang} + +TypeID = 3734 +Name = "a blood herb" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 3735 +Name = "a stone herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3736 +Name = "a star herb" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3737 +Name = "a fern" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3738 +Name = "a sling herb" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 3739 +Name = "a powder herb" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 3740 +Name = "a shadow herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3741 +Name = "a troll green" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 3742 +Name = "an orange tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3743 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3744 +Name = "a jungle dweller bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3745 +Name = "a tower fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3746 +Name = "a snake nest bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3747 +Name = "a green wig bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3748 +Name = "a lizards tongue bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3749 +Name = "a jungle crown plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3750 +Name = "a green fountain bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3751 +Name = "a big fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3752 +Name = "a dragons nest tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3753 +Name = "a purple kiss bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3754 +Name = "a small fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3755 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3756 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3757 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3758 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3759 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3760 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3761 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3762 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3763 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3764 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3765 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3766 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3767 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3768 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3769 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3770 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3771 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3772 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3773 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3774 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3775 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3776 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3777 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3778 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3779 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3780 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3781 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3782 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3783 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3784 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3785 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3786 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3787 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3788 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3789 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3790 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3791 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3792 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3793 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3794 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3795 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3796 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3797 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3798 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3799 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3800 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3801 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3802 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3803 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3804 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3805 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3806 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3807 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3808 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3809 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3810 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3811 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3812 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3813 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3814 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3815 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3816 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3817 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3818 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3819 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3820 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3821 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3822 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3823 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3824 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3825 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3826 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3827 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3828 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3829 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3830 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3831 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3832 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3833 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3834 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3835 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3836 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3837 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3838 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3839 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3840 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3841 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3842 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3843 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3844 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3845 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3846 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3847 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3848 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3849 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3850 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3851 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3852 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3853 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3854 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3855 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3856 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3857 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3858 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3859 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3860 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3861 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3862 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3863 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3864 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3865 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3866 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3867 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3868 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3869 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3870 +Name = "a chill nettle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3871 +Name = "a monkey tail" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3872 +Name = "a fairy queen" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3873 +Name = "a crane plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3874 +Name = "a jungle bells plant" +Flags = {Bottom,Unmove} + +TypeID = 3875 +Name = "a dawn singer" +Flags = {Bottom,Unmove} + +TypeID = 3876 +Name = "a turtle sprouter" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3877 +Name = "a bees ballroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3878 +Name = "a giant jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3879 +Name = "a jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3880 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3881 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3882 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3883 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3884 +Name = "a purple cardinal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3885 +Name = "a witches cauldron plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3886 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3887 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3888 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3889 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3890 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3891 +Name = "a sneeze blossom" +Flags = {Bottom,Unmove} + +TypeID = 3892 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3893 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3894 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3895 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3896 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3897 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3898 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3899 +Name = "a devil's tongue flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3900 +Name = "a small pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3901 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3902 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3903 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3904 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3905 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3906 +Name = "a dead man's saddle" +Flags = {Unmove} + +TypeID = 3907 +Name = "dead man's saddles" +Flags = {Unmove} + +TypeID = 3908 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3909 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3910 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3911 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 3912 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3913 +Name = "slime table mushrooms" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3914 +Name = "a giggle mushroom" +Flags = {Unmove} + +TypeID = 3915 +Name = "giggle mushrooms" +Flags = {Unmove} + +TypeID = 3916 +Name = "a cat's food mushroom" +Flags = {Unmove} + +TypeID = 3917 +Name = "cat's food mushrooms" +Flags = {Unmove} + +TypeID = 3918 +Name = "a glimmer cap mushroom" +Flags = {Unmove} + +TypeID = 3919 +Name = "glimmer cap mushrooms" +Flags = {Unmove} + +TypeID = 3920 +Name = "a giant glimmer cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3921 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3922 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3923 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3924 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3925 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3926 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3927 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3928 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3929 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3930 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3931 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3932 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3933 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3934 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3935 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3936 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3937 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3938 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3939 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3940 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3941 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3942 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3943 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3944 +Name = "a jungle maw" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 3945 +Name = "a jungle maw" +Flags = {Bottom,Unmove,Expire} +Attributes = {ExpireTarget=3944,TotalExpireTime=150} + +TypeID = 3946 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3947 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3948 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3949 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3950 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3951 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3952 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3953 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3954 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3955 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3956 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3957 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3958 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3959 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3960 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3961 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3962 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3963 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3964 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3965 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3966 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3967 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3968 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3969 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3970 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3971 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3972 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3973 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3974 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3975 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3976 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3977 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3978 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3979 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3980 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3981 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3982 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3983 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3984 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3985 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3986 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3987 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=60000,ExpireTarget=3991,TotalExpireTime=1800} + +TypeID = 3988 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=4600,ExpireTarget=4003,TotalExpireTime=1200} + +TypeID = 3989 +Name = "a dead cyclops" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4092,TotalExpireTime=1800} + +TypeID = 3990 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=60000,ExpireTarget=4103,TotalExpireTime=1200} + +TypeID = 3991 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 3992 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 3993 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3994 +Name = "a dead rat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=3995,TotalExpireTime=1200} + +TypeID = 3995 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=3996,TotalExpireTime=1200} + +TypeID = 3996 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 3997 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3998 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=3999,TotalExpireTime=1200} + +TypeID = 3999 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=840,ExpireTarget=4000,TotalExpireTime=1200} + +TypeID = 4000 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=620,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4001 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=82000,ExpireTarget=4002,TotalExpireTime=1800} + +TypeID = 4002 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=65000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4003 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=4004,TotalExpireTime=1200} + +TypeID = 4004 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4005 +Name = "a dead rotworm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4006,TotalExpireTime=1200} + +TypeID = 4006 +Name = "a dead rotworm" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4145,TotalExpireTime=1200} + +TypeID = 4007 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=26000,ExpireTarget=4008,TotalExpireTime=1800} + +TypeID = 4008 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=20000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4009 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=13000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4010 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4011 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=130000,ExpireTarget=4012,TotalExpireTime=1800} + +TypeID = 4012 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=105000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4013 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=85000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4014 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4015 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4016 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=68000,ExpireTarget=4017,TotalExpireTime=1800} + +TypeID = 4017 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=58000,ExpireTarget=4018,TotalExpireTime=1800} + +TypeID = 4018 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4019 +Name = "a dead deer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4020 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=21000,ExpireTarget=4021,TotalExpireTime=1200} + +TypeID = 4021 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=14000,ExpireTarget=4022,TotalExpireTime=1200} + +TypeID = 4022 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=9000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4023 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4024 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=4156,TotalExpireTime=1200} + +TypeID = 4025 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4026,TotalExpireTime=1800} + +TypeID = 4026 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4027 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4028 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4029 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4066,TotalExpireTime=1200} + +TypeID = 4030 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4031,TotalExpireTime=1800} + +TypeID = 4031 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4032,TotalExpireTime=1800} + +TypeID = 4032 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4033 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4034 +Name = "a slain ghoul" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=64000,ExpireTarget=4035,TotalExpireTime=1800} + +TypeID = 4035 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=57000,ExpireTarget=4036,TotalExpireTime=1800} + +TypeID = 4036 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4037 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4038 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4039,TotalExpireTime=1800} + +TypeID = 4039 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4040,TotalExpireTime=1800} + +TypeID = 4040 +Name = "a dead giant spider" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4041 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4042,TotalExpireTime=1800} + +TypeID = 4042 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4043 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4044,TotalExpireTime=1800} + +TypeID = 4044 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4045 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4046,TotalExpireTime=1800} + +TypeID = 4046 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4047 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4048,TotalExpireTime=1800} + +TypeID = 4048 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4049 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4050 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4051 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4052 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4053,TotalExpireTime=1800} + +TypeID = 4053 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4054 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4055 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4056 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4057 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=150000,ExpireTarget=4058,TotalExpireTime=1800} + +TypeID = 4058 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4059 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4060 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4061 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4062 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4063,TotalExpireTime=1800} + +TypeID = 4063 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4064 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4065 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4066 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7000,ExpireTarget=4125,TotalExpireTime=1200} + +TypeID = 4067 +Name = "a dead fire devil" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=80000,ExpireTarget=4068,TotalExpireTime=1800} + +TypeID = 4068 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=60000,ExpireTarget=4069,TotalExpireTime=1800} + +TypeID = 4069 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4070 +Name = "a dead lion" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4071,TotalExpireTime=1800} + +TypeID = 4071 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4072,TotalExpireTime=1800} + +TypeID = 4072 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4073 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4074 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4075,TotalExpireTime=1800} + +TypeID = 4075 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4076,TotalExpireTime=1800} + +TypeID = 4076 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4077 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4078 +Name = "a dead scorpion" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4079,TotalExpireTime=1200} + +TypeID = 4079 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4146,TotalExpireTime=1200} + +TypeID = 4080 +Name = "a dead wasp" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4081,TotalExpireTime=1200} + +TypeID = 4081 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4082,TotalExpireTime=1200} + +TypeID = 4082 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4083 +Name = "a dead bug" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000,ExpireTarget=4084,TotalExpireTime=1200} + +TypeID = 4084 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6500,ExpireTarget=4085,TotalExpireTime=1200} + +TypeID = 4085 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4086 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4087,TotalExpireTime=1800} + +TypeID = 4087 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4088 +Name = "a dead sheep" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=35000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4089 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4090,TotalExpireTime=1800} + +TypeID = 4090 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4091,TotalExpireTime=1800} + +TypeID = 4091 +Name = "a dead beholder" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4092 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4093,TotalExpireTime=1800} + +TypeID = 4093 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4094 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=2200,ExpireTarget=4158,TotalExpireTime=1200} + +TypeID = 4095 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4096,TotalExpireTime=1800} + +TypeID = 4096 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4097 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4098,TotalExpireTime=1800} + +TypeID = 4098 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4099,TotalExpireTime=1800} + +TypeID = 4099 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4100 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4101 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4102,TotalExpireTime=1800} + +TypeID = 4102 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4103 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=4104,TotalExpireTime=1200} + +TypeID = 4104 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4105 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=45000,ExpireTarget=4106,TotalExpireTime=1800} + +TypeID = 4106 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=32000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4107 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=18000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4108 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4109 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=92000,ExpireTarget=4110,TotalExpireTime=1800} + +TypeID = 4110 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=83000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 4111 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=69000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4112 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4113,TotalExpireTime=1800} + +TypeID = 4113 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4114,TotalExpireTime=1800} + +TypeID = 4114 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4115 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4116 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=75000,ExpireTarget=4117,TotalExpireTime=1800} + +TypeID = 4117 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4118,TotalExpireTime=1800} + +TypeID = 4118 +Name = "a dead pig" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4119 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4120,TotalExpireTime=1800} + +TypeID = 4120 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=90000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4121 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=68000,ExpireTarget=4122,TotalExpireTime=1800} + +TypeID = 4122 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=58000,ExpireTarget=4123,TotalExpireTime=1800} + +TypeID = 4123 +Name = "a dead goblin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=37000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4124 +Name = "a dead golin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4125 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4126 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4127,TotalExpireTime=1800} + +TypeID = 4127 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4128 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4129 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=27000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4130 +Name = "remains of a mummy" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=48000,ExpireTarget=4131,TotalExpireTime=1200} + +TypeID = 4131 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=31000,ExpireTarget=4132,TotalExpireTime=1200} + +TypeID = 4132 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4133 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4134,TotalExpireTime=1800} + +TypeID = 4134 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4135,TotalExpireTime=1800} + +TypeID = 4135 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4136 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4137 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=78000,ExpireTarget=4138,TotalExpireTime=1200} + +TypeID = 4138 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=69000,ExpireTarget=4139,TotalExpireTime=1200} + +TypeID = 4139 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=48000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4140 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=33000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4141 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4142,TotalExpireTime=1800} + +TypeID = 4142 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4143 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=52000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4144 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=34000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4145 +Name = "a dead rotworm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4146 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4147 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=51000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4148 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4149,TotalExpireTime=1800} + +TypeID = 4149 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=92000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4150 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4151,TotalExpireTime=1800} + +TypeID = 4151 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4152,TotalExpireTime=1800} + +TypeID = 4152 +Name = "a dead war wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4153 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4154,TotalExpireTime=1800} + +TypeID = 4154 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4155,TotalExpireTime=1800} + +TypeID = 4155 +Name = "a dead orc and wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4156 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=26000,ExpireTarget=4157,TotalExpireTime=1200} + +TypeID = 4157 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4158 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=1300,ExpireTarget=4159,TotalExpireTime=1200} + +TypeID = 4159 +Name = "remains of a ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4160 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4161,TotalExpireTime=1800} + +TypeID = 4161 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4162 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4163,TotalExpireTime=1800} + +TypeID = 4163 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4164 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4165,TotalExpireTime=1800} + +TypeID = 4165 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4166 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4167,TotalExpireTime=1800} + +TypeID = 4167 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4168 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4169,TotalExpireTime=1800} + +TypeID = 4169 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4170 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4171,TotalExpireTime=1800} + +TypeID = 4171 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4172,TotalExpireTime=1800} + +TypeID = 4172 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4173 +Name = "a dead rabbit" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4174,TotalExpireTime=1200} + +TypeID = 4174 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4175,TotalExpireTime=1200} + +TypeID = 4175 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4176 +Name = "a dead swamp troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=SLIME,Weight=60000,ExpireTarget=4177,TotalExpireTime=1800} + +TypeID = 4177 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=4178,TotalExpireTime=1800} + +TypeID = 4178 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4179 +Name = "a slain banshee" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,Weight=11000,ExpireTarget=4180,TotalExpireTime=1800} + +TypeID = 4180 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=4181,TotalExpireTime=1800} + +TypeID = 4181 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4182 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4183,TotalExpireTime=1800} + +TypeID = 4183 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4184,TotalExpireTime=1800} + +TypeID = 4184 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4185 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4186,TotalExpireTime=1800} + +TypeID = 4186 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4187,TotalExpireTime=1800} + +TypeID = 4187 +Name = "a dead scarab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4188 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {FluidSource=BLOOD,Weight=1320,ExpireTarget=4189,TotalExpireTime=1200} + +TypeID = 4189 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=920,ExpireTarget=4190,TotalExpireTime=1200} + +TypeID = 4190 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=680,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4191 +Name = "a dead larva" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=1050,ExpireTarget=4192,TotalExpireTime=1200} + +TypeID = 4192 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=820,ExpireTarget=4193,TotalExpireTime=1200} + +TypeID = 4193 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=530,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4194 +Name = "a dead scarab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=12000,ExpireTarget=4195,TotalExpireTime=1200} + +TypeID = 4195 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7800,ExpireTarget=4196,TotalExpireTime=1200} + +TypeID = 4196 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3600,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4197 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4198,TotalExpireTime=1800} + +TypeID = 4198 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4199,TotalExpireTime=1800} + +TypeID = 4199 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4200 +Name = "a dead hyaena" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4201,TotalExpireTime=1800} + +TypeID = 4201 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4202,TotalExpireTime=1800} + +TypeID = 4202 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4203 +Name = "a dead gargoyle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4204,TotalExpireTime=1800} + +TypeID = 4204 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4205,TotalExpireTime=1800} + +TypeID = 4205 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4206 +Name = "a slain lich" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4207,TotalExpireTime=1800} + +TypeID = 4207 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4208,TotalExpireTime=1800} + +TypeID = 4208 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4209 +Name = "a slain crypt shambler" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4210,TotalExpireTime=1800} + +TypeID = 4210 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4211,TotalExpireTime=1800} + +TypeID = 4211 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4212 +Name = "a slain bonebeast" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4213,TotalExpireTime=1800} + +TypeID = 4213 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4214,TotalExpireTime=1800} + +TypeID = 4214 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4215 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4216,TotalExpireTime=1800} + +TypeID = 4216 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4217,TotalExpireTime=1800} + +TypeID = 4217 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4218 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4219,TotalExpireTime=1800} + +TypeID = 4219 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4220,TotalExpireTime=1800} + +TypeID = 4220 +Name = "a dead efreet" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4221 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4222,TotalExpireTime=1800} + +TypeID = 4222 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4223,TotalExpireTime=1800} + +TypeID = 4223 +Name = "a dead marid" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4224 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5900,ExpireTarget=4225,TotalExpireTime=1800} + +TypeID = 4225 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5500,ExpireTarget=4226,TotalExpireTime=1800} + +TypeID = 4226 +Name = "a dead badger" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4227 +Name = "a dead skunk" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5600,ExpireTarget=4228,TotalExpireTime=1800} + +TypeID = 4228 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=4229,TotalExpireTime=1800} + +TypeID = 4229 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4230 +Name = "a dead gazer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4231,TotalExpireTime=1800} + +TypeID = 4231 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4232,TotalExpireTime=1800} + +TypeID = 4232 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4233 +Name = "a dead elder beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4234,TotalExpireTime=1800} + +TypeID = 4234 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4235,TotalExpireTime=1800} + +TypeID = 4235 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4236 +Name = "a dead yeti" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4237,TotalExpireTime=1800} + +TypeID = 4237 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4238,TotalExpireTime=1800} + +TypeID = 4238 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4239 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4240 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4241,TotalExpireTime=1800} + +TypeID = 4241 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4242 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4243 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4244 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4245 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4246 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4247 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4248,TotalExpireTime=1800} + +TypeID = 4248 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4249 +Name = "a dead troll" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000} + +TypeID = 4250 +Name = "a dead spider" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000} + +TypeID = 4251 +Name = "a dead cyclops" +Flags = {Container} +Attributes = {Capacity=12,FluidSource=BLOOD} + +TypeID = 4252 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4253 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4254 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4255 +Name = "a dead rat" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4256 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4257 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4258 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4259 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4260 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 4261 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4262 +Name = "a dead orc" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000} + +TypeID = 4263 +Name = "a dead orc" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4264 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4265 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4266 +Name = "a dead rotworm" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4267 +Name = "a dead rotworm" + +TypeID = 4268 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=6,FluidSource=BLOOD,Weight=21000} + +TypeID = 4269 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=15000} + +TypeID = 4270 +Name = "a dead wolf" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4271 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4272 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=12,FluidSource=BLOOD,Weight=150000} + +TypeID = 4273 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=7,Weight=110000} + +TypeID = 4274 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=2,Weight=80000} + +TypeID = 4275 +Name = "a dead minotaur" +Flags = {Take} +Attributes = {Weight=40000} + +TypeID = 4276 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=20000} + +TypeID = 4277 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000} + +TypeID = 4278 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=3,Weight=50000} + +TypeID = 4279 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=30000} + +TypeID = 4280 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4281 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=2,FluidSource=BLOOD,Weight=20000} + +TypeID = 4282 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=10000} + +TypeID = 4283 +Name = "a dead dog" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4284 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4285 +Name = "a pile of bones" +Flags = {Container,Take} +Attributes = {Capacity=10,Weight=10000} + +TypeID = 4286 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=16,FluidSource=BLOOD} + +TypeID = 4287 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4288 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4289 +Name = "a pile of bones" + +TypeID = 4290 +Name = "remains of a ghost" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=2200} + +TypeID = 4291 +Name = "a dead bear" +Flags = {Container} +Attributes = {Capacity=8,FluidSource=BLOOD} + +TypeID = 4292 +Name = "a dead bear" + +TypeID = 4293 +Name = "a dead bear" + +TypeID = 4294 +Name = "a pile of bones" + +TypeID = 4295 +Name = "a slain ghoul" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=40000} + +TypeID = 4296 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4297 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4298 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4299 +Name = "a dead cyclops" + +TypeID = 4300 +Name = "a pile of bones" + +TypeID = 4301 +Name = "a dead rabbit" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4302 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4303 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4304 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4305 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4306 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4307 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4308 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4309 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4310 +Name = "a dead elephant" +Flags = {Container} +Attributes = {Capacity=15} + +TypeID = 4311 +Name = "a dead human" +Flags = {Container,Corpse} +Attributes = {Capacity=10} + +TypeID = 4312 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4313 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=50000} + +TypeID = 4314 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4315 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4316 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4317 +Name = "some bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4318 +Name = "a dead crab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4319,TotalExpireTime=1200} + +TypeID = 4319 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4320,TotalExpireTime=1200} + +TypeID = 4320 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4321 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4322,TotalExpireTime=1200} + +TypeID = 4322 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4323,TotalExpireTime=1200} + +TypeID = 4323 +Name = "a dead lizard templar" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4324 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4325,TotalExpireTime=1200} + +TypeID = 4325 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4326,TotalExpireTime=1200} + +TypeID = 4326 +Name = "a dead lizard sentinel" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4327 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4328,TotalExpireTime=1200} + +TypeID = 4328 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4329,TotalExpireTime=1200} + +TypeID = 4329 +Name = "a dead lizard snakecharmer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4330 +Name = "a dead chicken" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4331,TotalExpireTime=1200} + +TypeID = 4331 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4332,TotalExpireTime=1200} + +TypeID = 4332 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4333 +Name = "a dead kongra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4334,TotalExpireTime=1200} + +TypeID = 4334 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4335,TotalExpireTime=1200} + +TypeID = 4335 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4336 +Name = "a dead merlkin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4337,TotalExpireTime=1200} + +TypeID = 4337 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4338,TotalExpireTime=1200} + +TypeID = 4338 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4339 +Name = "a dead sibang" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4340,TotalExpireTime=1200} + +TypeID = 4340 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4341,TotalExpireTime=1200} + +TypeID = 4341 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4342 +Name = "a dead crocodile" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4343,TotalExpireTime=1200} + +TypeID = 4343 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4344,TotalExpireTime=1200} + +TypeID = 4344 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4345 +Name = "a dead carniphila" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4346,TotalExpireTime=1200} + +TypeID = 4346 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4347,TotalExpireTime=1200} + +TypeID = 4347 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4348 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4349,TotalExpireTime=1800} + +TypeID = 4349 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4350,TotalExpireTime=1800} + +TypeID = 4350 +Name = "a dead hydra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4351 +Name = "a dead panda" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4352,TotalExpireTime=1200} + +TypeID = 4352 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4353,TotalExpireTime=1200} + +TypeID = 4353 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4354 +Name = "a dead centipede" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4355,TotalExpireTime=1200} + +TypeID = 4355 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4356,TotalExpireTime=1200} + +TypeID = 4356 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4357 +Name = "a dead tiger" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4358,TotalExpireTime=1200} + +TypeID = 4358 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4359,TotalExpireTime=1200} + +TypeID = 4359 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4360 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4361,TotalExpireTime=1800} + +TypeID = 4361 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4362,TotalExpireTime=1800} + +TypeID = 4362 +Name = "a dead elephant" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4363 +Name = "a dead bat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4364,TotalExpireTime=1200} + +TypeID = 4364 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4365,TotalExpireTime=1200} + +TypeID = 4365 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4366 +Name = "a dead flamingo" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4367,TotalExpireTime=1200} + +TypeID = 4367 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4368,TotalExpireTime=1200} + +TypeID = 4368 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4369 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4370,TotalExpireTime=1200} + +TypeID = 4370 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4371,TotalExpireTime=1200} + +TypeID = 4371 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4372 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4373,TotalExpireTime=1200} + +TypeID = 4373 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4374,TotalExpireTime=1200} + +TypeID = 4374 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4375 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4376,TotalExpireTime=1200} + +TypeID = 4376 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4377,TotalExpireTime=1200} + +TypeID = 4377 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4378 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4379 +Name = "a dead parrot" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4380,TotalExpireTime=1200} + +TypeID = 4380 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4381,TotalExpireTime=1200} + +TypeID = 4381 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4382 +Name = "a dead bird" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4383,TotalExpireTime=1200} + +TypeID = 4383 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4384,TotalExpireTime=1200} + +TypeID = 4384 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4385 +Name = "a dead tarantula" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4386,TotalExpireTime=1200} + +TypeID = 4386 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4387,TotalExpireTime=1200} + +TypeID = 4387 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4388 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4389,TotalExpireTime=1800} + +TypeID = 4389 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4390,TotalExpireTime=1800} + +TypeID = 4390 +Name = "a dead serpent spawn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4391 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4392,TotalExpireTime=1200} + +TypeID = 4392 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4393 +Name = "a drawbridge" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=90,DisguiseTarget=1771} + +TypeID = 4394 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4395 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4396 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4397 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4398 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4399 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4400 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4401 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4402 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4403 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4404 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4405 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4406 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4407 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4408 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4409 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4410 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4411 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4412 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4413 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4414 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4415 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4416 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4417 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4418 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4419 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4420 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4421 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4422 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4423 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4424 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4425 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4426 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4427 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4428 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4429 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4430 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4431 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4432 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4433 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4434 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4435 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4436 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4437 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4438 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4439 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4440 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4441 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4442 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4443 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4444 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4445 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4446 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4447 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4448 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4449 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4450 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4451 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4452 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4453 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4454 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4455 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4456 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4457 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4458 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4459 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4460 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4461 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4462 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4463 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4464 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4465 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4466 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4467 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4468 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4469 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4470 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4471 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4472 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4473 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4474 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4475 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4476 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4477 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4478 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4479 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4480 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4481 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4482 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4483 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4484 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4485 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4486 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4487 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4488 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4489 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4490 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4491 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4492 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4493 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4494 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4495 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4496 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4497 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4498 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4499 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4500 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4501 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4502 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4503 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4504 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4505 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4506 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4507 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4508 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4509 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4510 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4511 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4512 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4513 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4514 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4515 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4516 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4517 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4518 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4519 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4520 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4521 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4522 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4523 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4524 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4525 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4526 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4527 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4528 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4529 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4530 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4531 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4532 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4533 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4534 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4535 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4536 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4537 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4538 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4539 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4540 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4541 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4542 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4543 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4544 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4545 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4546 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4547 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4548 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4549 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4550 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4551 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4552 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4553 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4554 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4555 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4556 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4557 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4558 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4559 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4560 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4561 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4562 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4563 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4564 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4565 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4566 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4567 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4568 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4569 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4570 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4571 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4572 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4573 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4574 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4575 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4576 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4577 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4578 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4579 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4580 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4581 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4582 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4583 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4584 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4585 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4586 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4587 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4588 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4589 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4590 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4591 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4592 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4593 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4594 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4595 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4596 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4597 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4598 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4599 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4600 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4601 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4602 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4603 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4604 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4605 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4606 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4607 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4608 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4609 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4597,TotalExpireTime=2200} + +TypeID = 4610 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4598,TotalExpireTime=2200} + +TypeID = 4611 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4599,TotalExpireTime=2200} + +TypeID = 4612 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4600,TotalExpireTime=2200} + +TypeID = 4613 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4601,TotalExpireTime=2200} + +TypeID = 4614 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4602,TotalExpireTime=2200} + +TypeID = 4615 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4609} + +TypeID = 4616 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4610} + +TypeID = 4617 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4611} + +TypeID = 4618 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4612} + +TypeID = 4619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4613} + +TypeID = 4620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4614} + +TypeID = 4621 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4622 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4623 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4624 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4625 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4626 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4627 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4628 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4629 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4630 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4631 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4632 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4633 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4634 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4635 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4636 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4637 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4638 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4639 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4640 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4641 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4642 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4643 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4644 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4645 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4646 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4647 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4648 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4649 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4650 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4651 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4652 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4653 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4654 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4655 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4656 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4657 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4658 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4659 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4660 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4661 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4662 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4663 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4664 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4665 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4666 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4667 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4668 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4669 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4670 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4671 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4672 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4673 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4674 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4675 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4676 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4677 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4678 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4679 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4680 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4681 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4682 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4683 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4684 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4685 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4686 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4687 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4688 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4689 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4690 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4691 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4692 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4693 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4694 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4695 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4696 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4697 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4698 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4699 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4700 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4701 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4702 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4703 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4704 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4705 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4706 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4707 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4708 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4709 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4710 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4711 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4712 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4713 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4714 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4715 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4716 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4717 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4718 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4719 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4720 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4721 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4722 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4723 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4724 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4725 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4726 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4727 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4728 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4729 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4730 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4731 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4732 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4733 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4734 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4735 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4736 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4737 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4738 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4739 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4740 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4741 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4742 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4743 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4744 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4745 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4746 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4747 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4748 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4749 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4750 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4751 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4752 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4753 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4754 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4755 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4756 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4757 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4758 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4759 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4760 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4761 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4762 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4763 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4764 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4765 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4766 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4767 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4768 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4769 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4770 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4771 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4772 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4773 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4774 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4775 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4776 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4777 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4778 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4779 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4780 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4781 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4782 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4783 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4784 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4785 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4786 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4787 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4788 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4789 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4790 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4791 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4792 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4793 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4794 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4795 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4796 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4797 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4798 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4799 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4800 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4801 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4802 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4803 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4804 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4805 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4806 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4807 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4808 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4809 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4810 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4811 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4812 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4813 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4814 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4815 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4816 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4817 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4818 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4819 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4820 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4821 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4822 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4823 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4824 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4825 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4826 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4827 +Name = "whisper moss" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 4828 +Name = "a flask of cough syrup" +Description = "It smells like herbs" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4829 +Name = "a witches cap mushroom" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4830 +Name = "witches mushrooms" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3919} + +TypeID = 4831 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4832 +Name = "a giant ape's hair" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4833 +Name = "a giant footprint" +Flags = {Bottom,Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=2753} + +TypeID = 4834 +Name = "a family brooch" +Description = "The emblem of a dwarven family is engraved on it" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 4835 +Name = "a snake destroyer" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=6600} + +TypeID = 4836 +Name = "a spectral dress" +Flags = {Take} +Attributes = {Weight=1000,SlotType=BODY} + +TypeID = 4837 +Name = "an icicle" +Description = "It is melting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=1900,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 4838 +Name = "strange powder" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 4839 +Name = "a hydra egg" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4840 +Name = "a spectral stone" +Description = "It is pulsating with spectral energy" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 4841 +Name = "a memory stone" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 4842 +Name = "a sheet of tracing paper" +Description = "It is blank" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=100} + +TypeID = 4843 +Name = "a sheet of tracing paper" +Description = "It contains some strange symbols of the lizard language" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4844 +Name = "an elven poetry book" +Description = "It contains a collection of beautiful elven poems" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 4845 +Name = "a dwarven pickaxe" +Description = "It is a masterpiece of dwarvish smithery and made of especially hard steel" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4846 +Name = "a wrinkled parchment" +Description = "It is covered with strange numbers" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4847 +Name = "a funeral urn" +Description = "It contains the ashes of a lizard high priest" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4848 +Name = "a small cask" +Description = "It is filled with the blood of the snake god" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BLOOD} + +TypeID = 4849 +Name = "wooden trash" +Description = "The blood of the snake god is pouring out" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4848,TotalExpireTime=120} + +TypeID = 4850 +Name = "the statue of the snake god" +Description = "It is emitting an eerie light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=102} + +TypeID = 4851 +Name = "a smashed stone head" +Description = "It seems to repair itself rapidly" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4850,TotalExpireTime=60} + +TypeID = 4852 +Name = "an ectoplasm container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 4853 +Name = "an ectoplasm container" +Description = "It is filled with ectoplasm" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 4854 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4855 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4856 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4857 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4858 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4859 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4860 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4861 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4862 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4863 +Name = "a butterfly conservation kit" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=700} + +TypeID = 4864 +Name = "a butterfly conservation kit" +Description = "It contains a red butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4865 +Name = "a butterfly conservation kit" +Description = "It contains a purple butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4866 +Name = "a butterfly conservation kit" +Description = "It contains a blue butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4867 +Name = "a botanist's container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=1800} + +TypeID = 4868 +Name = "a botanist's container" +Description = "It holds a sample of the jungle bells plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4869 +Name = "a botanist's container" +Description = "It holds a sample of the giant jungle rose" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4870 +Name = "a botanist's container" +Description = "It holds a sample of the witches cauldron plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4871 +Name = "an explorer brooch" +Description = "It is the official badge of the explorer society" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 4872 +Name = "an ice pick" +Description = "It might come in handy in cold regions" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=7000} + +TypeID = 4873 +Name = "a hydra's nest" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=5676} + +TypeID = 4874 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4875 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4876 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4877 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4878 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4879 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4880 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4881 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 4882 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4883 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4884 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 4885 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4886 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4887 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4888 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4889 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4890 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4891 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4892 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4893 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4894 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4895 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4896 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4897 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4898 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4899 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4900 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4901 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4902 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4903 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4904 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4905 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4906 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4907 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4908 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4909 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4910 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4911 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4912 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4913 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4914 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4915 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4916 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4917 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4918 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4919 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4920 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4921 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4922 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4923 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4924 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4925 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4926 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4927 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4928 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4929 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4930 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4931 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4932 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4933 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4934 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4935 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4936 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4937 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4938 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4939 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4940 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4941 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4942 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4943 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4944 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4945 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4946 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4947 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4948 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4949 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4950 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4951 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4952 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4953 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4954 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4955 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4956 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4957 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4958 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4959 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4960 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4961 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4962 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4963 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4964 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4965 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4966 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4967 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4968 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4969 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4970 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4971 +Name = "a ventilation grille" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 4972 +Name = "a bollard" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4973 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4974 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4975 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4976 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4977 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4978 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4979 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4980 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4981 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4982 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4983 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4984 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4985 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4986 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4987 +Name = "a cleat" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4988 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4989 +Name = "a white flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4990 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4991 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4992 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4993 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4994 +Name = "some sharp icicles" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4995 +Name = "a canopic jar" +Description = "You feel an eerie presence" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=4996} + +TypeID = 4996 +Name = "the remains of a canopic jar" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4995,TotalExpireTime=300} + +TypeID = 4997 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4998 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4999 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5000 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 5001 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5002 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5003 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 5004 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 5005 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5006 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5007 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5008 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5009 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5010 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5011 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5012 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5013 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 5014 +Name = "a mandrake" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 5015 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5016 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5017 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5018 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5019 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5020 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5021 +Name = "an orichalcum pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 5022 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5023 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5024 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=215} + +TypeID = 5025 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=208} + +TypeID = 5026 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=201} + +TypeID = 5027 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=194} + +TypeID = 5028 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=187} + +TypeID = 5029 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=180} + +TypeID = 5030 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=214} + +TypeID = 5031 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 5032 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=200} + +TypeID = 5033 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=193} + +TypeID = 5034 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=186} + +TypeID = 5035 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=213} + +TypeID = 5036 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=206} + +TypeID = 5037 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=199} + +TypeID = 5038 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=192} + +TypeID = 5039 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=212} + +TypeID = 5040 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=205} + +TypeID = 5041 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=198} + +TypeID = 5042 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=211} + +TypeID = 5043 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=204} + +TypeID = 5044 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=210} + +TypeID = 5045 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5046 +Name = "a monkey statue" +Description = "The words 'See no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5047 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5048 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5049 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5050 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5051 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5052 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5053 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5054 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5055 +Name = "a monkey statue" +Description = "The words 'Hear no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5056 +Name = "a monkey statue" +Description = "The words 'Speak no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5057 +Name = "a snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5058 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5059 +Name = "a small snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5060 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5061 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5062 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5063 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5064 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5065 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5066 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5067 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5068 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5069 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5070 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5071 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5072 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5073 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5074 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5075 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5076 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5077 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5078 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5079 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5080 +Name = "a panda teddy" +Flags = {UseEvent,Take} +Attributes = {Weight=600} + +TypeID = 5081 +Name = "a ladder" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 5082 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5083 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5084 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5085 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5086 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5087 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5088 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5089 +Name = "a butterfly conservation kit" +Description = "It contains a rare yellow butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 5090 +Name = "a treasure map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 5091 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5092 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5093 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5094 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5095 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5096 +Name = "a mango" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=180} + +TypeID = 5097 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5098 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5099 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5100 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5101 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5102 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5103 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5104 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5105 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5106 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5107 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5108 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5109 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5110 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5111 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5112 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5113 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5114 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5115 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5116 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5117 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5118 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5119 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5120 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5121 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5122 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5123 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5124 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5125 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5126 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5127 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5128 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5129 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5130 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5131 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5132 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5133 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5134 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5135 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5136 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5137 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5138 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5139 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5140 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5141 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5142 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5143 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5144 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5145 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5146 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5147 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5148 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5149 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5150 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5151 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5152 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5153 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5154 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5155 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5156 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5157 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5158 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5159 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5160 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5161 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5162 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5163 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5164 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5165 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5166 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5167 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5168 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5169 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5170 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5171 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5172 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5173 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5174 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5175 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5176 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5177 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5178 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5179 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5180 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5181 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5182 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5183 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5184 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5185 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5186 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5187 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5188 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5189 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5190 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5191 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5192 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5193 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5194 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5195 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5196 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5197 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5198 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5199 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5200 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5201 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5202 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5203 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5204 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5205 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5206 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5207 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5208 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5209 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5210 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5211 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5212 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5213 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5214 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5215 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5216 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5217 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5218 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5219 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5220 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5221 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5222 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5223 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5224 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5225 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5226 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5227 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5228 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5230 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5231 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5232 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5233 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5234 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5235 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5236 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5237 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5238 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5239 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5240 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5241 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5242 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5243 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5244 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5245 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5246 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5247 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5248 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5249 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5250 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5251 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5252 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5253 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5254 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5255 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5256 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5257 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5258 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5259 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5260 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5261 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5262 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5263 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5264 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5265 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5266 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5267 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5268 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5269 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5270 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5271 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5272 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5273 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5274 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5275 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5276 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5277 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5278 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5279 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5280 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5281 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5282 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5283 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5284 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5285 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5286 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5287 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5288 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5289 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5290 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5291 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5292 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5293 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5294 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5295 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5296 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5297 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5298 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5299 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5300 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5301 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5302 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5303 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5304 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5305 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5306 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5307 +Name = "a white stone pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5308 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5309 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5310 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5311 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5312 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5313 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5314 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5315 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5316 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5317 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5318 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5319 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5320 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5321 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5322 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5323 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5324 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5325 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5326 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5327 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5328 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5329 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5330 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5331 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5332 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5333 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5334 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5335 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5336 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5337 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5338 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5339 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5340 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5341 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5342 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5343 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5344 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5345 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5346 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5347 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5348 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5349 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5350 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5351 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5352 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5353 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5354 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5355 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5356 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5357 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5358 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5359 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5360 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5361 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5362 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5363 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5364 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5365 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5366 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5367 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5368 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5369 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5370 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5371 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5372 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5373 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5374 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5375 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5376 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5377 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5378 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5379 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5380 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5381 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5382 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5383 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5384 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5385 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5386 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5387 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5388 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5389 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5390 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5391 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5392 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5393 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5394 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5395 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5396 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5397 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5398 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5399 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5400 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5401 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5402 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5403 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5404 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5405 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5406 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5407 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5408 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5409 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5410 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5411 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5412 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5413 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5414 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5415 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5416 +Name = "a starfish" +Flags = {Unmove} + +TypeID = 5417 +Name = "a sea anemone" +Flags = {Unmove} + +TypeID = 5418 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5419 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5420 +Name = "kelp" +Flags = {Unpass,Unmove} + +TypeID = 5421 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5422 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5423 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5424 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5425 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5426 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5427 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5428 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5429 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5430 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5431 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5432 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5433 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5434 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5435 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5436 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5437 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5438 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5439 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5440 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5441 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5442 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5443 +Name = "a rusty anchor" +Flags = {Unpass,Unmove} + +TypeID = 5444 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5445 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5446 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5447 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5448 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5449 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5450 +Name = "a wrecked figurehead" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5451 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5452 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5453 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5454 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5455 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5456 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5457 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5458 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5459 +Name = "a wrecked ventilation grill" +Flags = {Unmove} + +TypeID = 5460 +Name = "a helmet of the deep" +Description = "Enables underwater exploration" +Flags = {Take,Armor} +Attributes = {Weight=21000,SlotType=HEAD,ArmorValue=2,AbsorbDrown=100} + +TypeID = 5461 +Name = "pirate boots" +Flags = {Take,Armor} +Attributes = {Weight=800,SlotType=FEET,ArmorValue=2} + +TypeID = 5462 +Name = "a sugar cane" +Description = "It has just been harvested" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5470,TotalExpireTime=240} + +TypeID = 5463 +Name = "a sugar cane" +Description = "It can be harvested" +Flags = {Unmove,Avoid} + +TypeID = 5464 +Name = "a burning sugar cane" +Flags = {Unpass,Unmove,Expire} +Attributes = {ExpireTarget=5463,TotalExpireTime=10} + +TypeID = 5465 +Name = "a sugar cane" +Flags = {Unpass,Unmove} + +TypeID = 5466 +Name = "a bunch of sugar cane" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=2250} + +TypeID = 5467 +Name = "a fire bug" +Description = "This strange creature has the tendency to set certain things on fire" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=3050,Brightness=3,LightColor=206} + +TypeID = 5468 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5469 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5470 +Name = "a sugar cane" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5465,TotalExpireTime=240} + +TypeID = 5471 +Name = "a ball on chains" + +TypeID = 5472 +Name = "a ball on chains" + +TypeID = 5473 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5474 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5475 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5476 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5477 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5478 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5479 +Name = "a cat's paw" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 5480 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5481 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5482 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5483 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5484 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5485 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5486 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5487 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5488 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5489 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5490 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5491 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5492 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5493 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5494 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5495 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=5501} + +TypeID = 5496 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=5502} + +TypeID = 5497 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=5499} + +TypeID = 5498 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=5500} + +TypeID = 5499 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} # TODO: Every straw mat should be a bed to lay in house +Attributes = {BedDirection=EAST,BedTarget=5497} + +TypeID = 5500 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=5498} + +TypeID = 5501 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=5495} + +TypeID = 5502 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=5496} + +TypeID = 5503 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5504 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5505 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5506 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5507 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5508 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5509 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5510 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5511 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5512 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5468,TotalExpireTime=10} + +TypeID = 5513 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5469,TotalExpireTime=10} + +TypeID = 5514 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5515 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5516 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5517 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5518 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5519 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5520 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5521 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5536,TotalExpireTime=900} + +TypeID = 5522 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5532,TotalExpireTime=900} + +TypeID = 5523 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5530,TotalExpireTime=900} + +TypeID = 5524 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5528,TotalExpireTime=900} + +TypeID = 5525 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5534,TotalExpireTime=900} + +TypeID = 5526 +Name = "a demon dust" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5527,TotalExpireTime=900} + +TypeID = 5527 +Name = "a demon dust" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5528 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5529,TotalExpireTime=900} + +TypeID = 5529 +Name = "a dead quara hydromancer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5530 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5531,TotalExpireTime=900} + +TypeID = 5531 +Name = "a dead quara constrictor" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5532 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5533,TotalExpireTime=900} + +TypeID = 5533 +Name = "a dead quara mantassin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5534 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5535,TotalExpireTime=900} + +TypeID = 5535 +Name = "a dead quara predator" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5536 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5537,TotalExpireTime=900} + +TypeID = 5537 +Name = "a dead quara pincher" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5538 +Name = "a rum cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=RUM} + +TypeID = 5539 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5540,TotalExpireTime=900} + +TypeID = 5540 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5541,TotalExpireTime=600} + +TypeID = 5541 +Name = "a dead carrion worm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5542 +Name = "a rope-ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 5543 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5544 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 5545 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5546 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5547 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5548 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5549 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5550 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5551 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5552 +Name = "a rum flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=280} + +TypeID = 5553 +Name = "a small fish" +Flags = {Unmove} + +TypeID = 5554 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5555 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5556 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5557 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5558 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5559 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5560 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5561 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5562 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5563 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5564 +Name = "a slain pirate skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=0,TotalExpireTime=900} + +TypeID = 5565 +Name = "a slain pirate ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=20000,ExpireTarget=5566,TotalExpireTime=900} + +TypeID = 5566 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=5567,TotalExpireTime=600} + +TypeID = 5567 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5568 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5569 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5570 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5571 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5572 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5573 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5574 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5575 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5576 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5577 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5578 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5579 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5580 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5581 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5582 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5583 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5584 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5585 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5586 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5587 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5588 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5589 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5590 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5591 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5592 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5593 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5594 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5595 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5596 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5597 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5598 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5599 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5600 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5601 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5602 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5603 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5604 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5605 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5606 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5607 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5608 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5609 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5610 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5611 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5612 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5613 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5614 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5615 +Name = "a pirate tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 5616 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5617 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5618 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5619 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5620 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5621 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5622 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5623 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5624 +Name = "a dead tortoise" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=6,Weight=1000,FluidSource=BLOOD,ExpireTarget=5625,TotalExpireTime=900} + +TypeID = 5625 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=5626,TotalExpireTime=600} + +TypeID = 5626 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5627 +Name = "a dead thornback tortoise" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5628,TotalExpireTime=900} + +TypeID = 5628 +Name = "a dead thornback tortoise" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5629,TotalExpireTime=600} + +TypeID = 5629 +Name = "a dead thornback tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5630 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5631 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5632 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5633 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5634 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5635 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5636 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5637 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5638 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5639 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5640 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5641 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5642 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5643 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5644 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5645 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5646 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5647 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5648 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5649 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5650 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5651 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5652 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5653 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5654 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5655 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5656 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5657 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5658 +Name = "a blooming griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5659 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5660 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5661 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5662 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5663 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5664 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5665 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5666,TotalExpireTime=900} + +TypeID = 5666 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=5667,TotalExpireTime=600} + +TypeID = 5667 +Name = "a dead mammoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5668 +Name = "a mysterious voodoo skull" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 5669 +Name = "a enigmatic voodoo skull" +Description = "It is not time yet" +Flags = {Take,Expire} +Attributes = {Weight=1400,ExpireTarget=5668,TotalExpireTime=72000} + +TypeID = 5670 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5671 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5672 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5673 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5674 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5675 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5676 +Name = "a hydra's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5677 +Name = "a tortoise's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5678 +Name = "a tortoise egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 5679 +Name = "a shell" +Flags = {Unmove} + +TypeID = 5680 +Name = "a spiral shell" +Flags = {Unmove} + +TypeID = 5681 +Name = "some shells" +Flags = {Unmove} + +TypeID = 5682 +Name = "a piece of a shell" +Flags = {Unmove} + +TypeID = 5683 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5684 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5685 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5686 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5687 +Name = "a dry griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5688 +Name = "a dead blood crab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=5689,TotalExpireTime=900} + +TypeID = 5689 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5690,TotalExpireTime=600} + +TypeID = 5690 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5691 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5692 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5693 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5694 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5695 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5696 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5697 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5698 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5699 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5700 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5701 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5702 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5703 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5704 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5705 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5706 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Take} +Attributes = {Weight=830} + +TypeID = 5707 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5708 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5709 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5710 +Name = "a light shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 5711 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5712 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5713 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5714 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5715 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5716 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5717 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5718 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5719 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5720 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5721 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5722 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5723 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5724 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5725 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5726 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5727 +Name = "a dead seagull" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5728,TotalExpireTime=600} + +TypeID = 5728 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5729,TotalExpireTime=600} + +TypeID = 5729 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5730 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2480} + +TypeID = 5731 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 5732 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5733 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5734 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5735 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5736 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5737 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5738 +Name = "a shark fin" +Flags = {Unmove} + +TypeID = 5739 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5740 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5741 +Name = "a skull helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=9} + +TypeID = 5742 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2482} + +TypeID = 5743 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5744 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5745 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5746 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5747 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5748 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5749 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5750 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5751 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5752 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5753 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5754 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5755 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5756 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5757 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5758 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5759 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5760 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5761 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5762 +Name = "a dead quara constrictor" +Flags = {Container,Corpse} +Attributes = {Capacity=10,FluidSource=BLOOD} + +TypeID = 5763 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5764 +Name = "a ventilation grille" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5765 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5766,TotalExpireTime=600} + +TypeID = 5766 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5767,TotalExpireTime=600} + +TypeID = 5767 +Name = "a dead toad" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5768 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5769 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5770 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5771 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5772 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5773 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5774 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5775 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2481} + +TypeID = 5776 +Name = "a Tibianus talon" +Description = "Rumours say that the Gods enchanted these talons for the greatest good, or the greatest evil achievements" +Flags = {Cumulative,Take,Disguise} +Attributes = {DisguiseTarget=3034,Weight=20} + +TypeID = 5777 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5778 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5779 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5780 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5781 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5782 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5783 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5784 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5785 +Name = "a medal of honour" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1000} + +TypeID = 5786 +Name = "a wooden whistle" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5787 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5788 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5789 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5790 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5791 +Name = "a stuffed dragon" +Flags = {UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 5792 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5793 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5794 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5795 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5796 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5797 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5798 +Name = "an abacus" +Description = "If you use it wisely, you may write yourself into the history books" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5799 +Name = "a golden figurine" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5800 +Name = "a grappling hook" +Flags = {Unmove} + +TypeID = 5801 +Name = "a key ring" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=50} + +TypeID = 5802 +Name = "a message in a bottle" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5803 +Name = "an arbalest" +Description = "It is a bit heavy due to the iron mounting, but very precise" +Flags = {Take,Distance} +Attributes = {Weight=9500,SlotType=TWOHANDED,Range=6,AmmoType=BOLT} + +TypeID = 5804 +Name = "a nose ring" +Description = "It was the favourite trinket of the famous Horned Fox" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5805 +Name = "a golden goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5806 +Name = "a silver goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5807 +Name = "a bronze goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5808 +Name = "Orshabaal's brain" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5809 +Name = "a soul stone" +Description = "It contains the essence of countless tormented souls" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 5810 +Name = "a voodoo doll" +Description = "It looks like a small pirate" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 5811 +Name = "a mermaid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5812 +Name = "a skull candle" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=5813,Weight=2200,Brightness=3,LightColor=206,ExpireTarget=3114,TotalExpireTime=3000} + +TypeID = 5813 +Name = "a skull candle" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=5812,Weight=2200,Brightness=0,LightColor=215} + +TypeID = 5814 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unpass,Unmove} + +TypeID = 5815 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 5816 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5817 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5818 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5819 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5820 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5821 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5822 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5823 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5824 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5825 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5826 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5827 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5828 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5829 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5830 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5831 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5832 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5833 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5834 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5835 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5836 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5837 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5838 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5839 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5840 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5841 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5842 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5843 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5844 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5845 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5846 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5847 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5848 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5849 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5850 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5851 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5852 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5853 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5854 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5855 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5856 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5857 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5858 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5859 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5860 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5861 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5862 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5863 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5864 +Name = "" # this is nothing in client + +TypeID = 5865 +Name = "a juice squeezer" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 5866 +Name = "rubble" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 5867 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5868 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5869 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5870 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5871 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5872 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5873 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5874 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5875 +Name = "sniper gloves" +Description = "They are the pride of the paladin guild" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 5876 +Name = "a lizard leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5877 +Name = "a green dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5878 +Name = "a minotaur leather" +Flags = {Cumulative,Take} +Attributes = {Weight=40} + +TypeID = 5879 +Name = "a spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5880 +Name = "an iron ore" +Flags = {Cumulative,Take} +Attributes = {Weight=200} + +TypeID = 5881 +Name = "a lizard scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5882 +Name = "a red dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 5883 +Name = "an ape fur" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5884 +Name = "a spirit container" +Description = "It contains pure fighting spirit" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 5885 +Name = "a flask of warrior's sweat" +Description = "It contains the sweat spilled in many battles and is said to be used for certain perfumes too" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 5886 +Name = "a spool of yarn" +Description = "It is made from fine spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5887 +Name = "a piece of royal steel" +Description = "Even the king would be proud to wear an armour made of this refined steel" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5888 +Name = "a piece of hell steel" +Description = "This rare metal must have been refined in the depths" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5889 +Name = "a piece of draconian steel" +Description = "An armour made of this steel is said to protect against fiery dragon breath" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5890 +Name = "a chicken feather" +Description = "Some thousands of these would probably make an extremely comfortable pillow" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5891 +Name = "an enchanted chicken wing" +Description = "It is said to make your feet fly" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5892 +Name = "a huge chunk of crude iron" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 5893 +Name = "a perfect behemoth fang" +Description = "Collectors all around the world crave for this item" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5894 +Name = "a bat wing" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5895 +Name = "a fish fin" +Description = "It once belonged to a mighty creature of the deep" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5896 +Name = "a bear paw" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5897 +Name = "a wolf paw" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5898 +Name = "a beholder's eye" +Description = "You could swear it just winked at you" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5899 +Name = "a turtle shell" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5900 +Name = "a dwarven beard" +Description = "It was once worn proudly by a dwarfish warrior - or maiden" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5901 +Name = "wood" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5902 +Name = "a honeycomb" +Description = "Some people swear it makes an excellent glue" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5903 +Name = "Ferumbras' hat" +Description = "It is the proof that Ferumbras has fallen. For now. The Edron Academy should be interested in this" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5904 +Name = "a magic sulphur" +Description = "It smells rather badly but is said to be an important catalyst for magical rituals" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5905 +Name = "a vampire dust" +Description = "Sun can be a merciless killer, but so can you" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5906 +Name = "a demon dust" +Description = "It reeks of hatred and malice" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5907 +Name = "a slingshot" +Description = "A hermit near Carlin might be able to tell you more about it" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5908 +Name = "an obsidian knife" +Description = "Sharp and light, this is a useful tool for tanners, doctors and assassins" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5909 +Name = "a white piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5910 +Name = "a green piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5911 +Name = "a red piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5912 +Name = "a blue piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5913 +Name = "a brown piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5914 +Name = "a yellow piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5915 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5916 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5917 +Name = "a bandana" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5918 +Name = "pirate knee breeches" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=LEGS,ArmorValue=1} + +TypeID = 5919 +Name = "a dragon claw" +Description = "It is the claw of Demodras" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 5920 +Name = "a green dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5921 +Name = "a heaven blossom" +Flags = {Cumulative,Take} +Attributes = {Weight=210} + +TypeID = 5922 +Name = "a holy orchid" +Flags = {Cumulative,Take} +Attributes = {Weight=250} + +TypeID = 5923 +Name = "" # this is nothing in client + +TypeID = 5924 +Name = "a damaged steel helmet" +Description = "The words 'Ramsay the Reckless' are engraved inside. It appears to be cracked and broken" +Flags = {Take} +Attributes = {Weight=4600} + +TypeID = 5925 +Name = "a hardened bone" +Flags = {Cumulative,Take} +Attributes = {Weight=600} + +TypeID = 5926 +Name = "a pirate backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5927 +Name = "a pirate bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5928 +Name = "an empty goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5929 +Name = "a goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5930 +Name = "a behemoth claw" +Flags = {Cumulative,Take} +Attributes = {Weight=1250} + +TypeID = 5931 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5932,TotalExpireTime=900} + +TypeID = 5932 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5933,TotalExpireTime=600} + +TypeID = 5933 +Name = "the remains of ferumbras" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5934 +Name = "a dead frog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5935,TotalExpireTime=600} + +TypeID = 5935 +Name = "a dead frog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5936,TotalExpireTime=600} + +TypeID = 5936 +Name = "a dead frog" +Flags = {Corpse,Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5937 +Name = "a botanist's container" +Description = "It holds a sample of the rare griffinclaw flower" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 5938 +Name = "Ceiron's waterskin" +Description = "It is empty" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5939 +Name = "Ceiron's waterskin" +Description = "It contains a special sample of water from a hydra cave" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5940 +Name = "Ceiron's wolf tooth chain" +Description = "It has the letter 'C' carved into one of the teeth" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 5941 +Name = "a wooden stake" +Description = "It is a simple wooden stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5942 +Name = "a blessed wooden stake" +Description = "Many mighty priests have blessed this stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5943 +Name = "Morgaroth's heart" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5944 +Name = "a soul orb" +Description = "This strange object seems to be made of half spirit, half metal. It is unknown to most smiths" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 5945 +Name = "a coral comb" +Description = "It once belonged to a mermaid" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5946 +Name = "a comb" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5947 +Name = "Elane's crossbow" +Description = "'For Elane, with Love' is engraved on it" +Flags = {Take} +Attributes = {Weight=4000} + +TypeID = 5948 +Name = "a red dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=60} + +TypeID = 5949 +Name = "a beach backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5950 +Name = "a beach bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5951 +Name = "a fish tail" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 5952 +Name = "a poem scroll" +Description = "It contains a love poem, written by an unknown elven poet." +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5953 +Name = "a raven herb" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5954 +Name = "a demon horn" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5955 +Name = "" # this is nothing in client + +TypeID = 5956 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5957 +Name = "a lottery ticket" +Description = "It has not been used yet" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5958 +Name = "a winning lottery ticket" +Description = "You were lucky! Go claim your prize at the potion store in Edron" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5959 +Name = "" # this is nothing in client + +TypeID = 5960 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=3987,TotalExpireTime=10} + +TypeID = 5961 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=SLIME,ExpireTarget=3988,TotalExpireTime=10} + +TypeID = 5962 +Name = "a dead cyclops" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=3989,TotalExpireTime=10} + +TypeID = 5963 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=3990,TotalExpireTime=10} + +TypeID = 5964 +Name = "a dead rat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=3994,TotalExpireTime=10} + +TypeID = 5965 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 5966 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4001,TotalExpireTime=10} + +TypeID = 5967 +Name = "a dead rotworm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4005,TotalExpireTime=10} + +TypeID = 5968 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4007,TotalExpireTime=10} + +TypeID = 5969 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4011,TotalExpireTime=10} + +TypeID = 5970 +Name = "a dead deer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4016,TotalExpireTime=10} + +TypeID = 5971 +Name = "a dead dog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4020,TotalExpireTime=10} + +TypeID = 5972 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4024,TotalExpireTime=10} + +TypeID = 5973 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4025,TotalExpireTime=10} + +TypeID = 5974 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4029,TotalExpireTime=10} + +TypeID = 5975 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4030,TotalExpireTime=10} + +TypeID = 5976 +Name = "a slain ghoul" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4034,TotalExpireTime=10} + +TypeID = 5977 +Name = "a dead giant spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4038,TotalExpireTime=10} + +TypeID = 5978 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4041,TotalExpireTime=10} + +TypeID = 5979 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4043,TotalExpireTime=10} + +TypeID = 5980 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4045,TotalExpireTime=10} + +TypeID = 5981 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4047,TotalExpireTime=10} + +TypeID = 5982 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4052,TotalExpireTime=10} + +TypeID = 5983 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4057,TotalExpireTime=10} + +TypeID = 5984 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4062,TotalExpireTime=10} + +TypeID = 5985 +Name = "a dead fire devil" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4067,TotalExpireTime=10} + +TypeID = 5986 +Name = "a dead lion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4070,TotalExpireTime=10} + +TypeID = 5987 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4074,TotalExpireTime=10} + +TypeID = 5988 +Name = "a dead scorpion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4078,TotalExpireTime=10} + +TypeID = 5989 +Name = "a dead wasp" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4080,TotalExpireTime=10} + +TypeID = 5990 +Name = "a dead bug" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4083,TotalExpireTime=10} + +TypeID = 5991 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4086,TotalExpireTime=10} + +TypeID = 5992 +Name = "a dead beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4089,TotalExpireTime=10} + +TypeID = 5993 +Name = "remains of a ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4094,TotalExpireTime=10} + +TypeID = 5994 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4095,TotalExpireTime=10} + +TypeID = 5995 +Name = "a slain demon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4097,TotalExpireTime=10} + +TypeID = 5996 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4101,TotalExpireTime=10} + +TypeID = 5997 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4105,TotalExpireTime=10} + +TypeID = 5998 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4109,TotalExpireTime=10} + +TypeID = 5999 +Name = "a dead behemoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4112,TotalExpireTime=10} + +TypeID = 6000 +Name = "a dead pig" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4116,TotalExpireTime=10} + +TypeID = 6001 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4119,TotalExpireTime=10} + +TypeID = 6002 +Name = "a dead goblin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4121,TotalExpireTime=10} + +TypeID = 6003 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4126,TotalExpireTime=10} + +TypeID = 6004 +Name = "remains of a mummy" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4130,TotalExpireTime=10} + +TypeID = 6005 +Name = "a split stone golem" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4133,TotalExpireTime=10} + +TypeID = 6006 +Name = "a slain vampire" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4137,TotalExpireTime=10} + +TypeID = 6007 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4141,TotalExpireTime=10} + +TypeID = 6008 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4148,TotalExpireTime=10} + +TypeID = 6009 +Name = "a dead war wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4150,TotalExpireTime=10} + +TypeID = 6010 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4153,TotalExpireTime=10} + +TypeID = 6011 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4160,TotalExpireTime=10} + +TypeID = 6012 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4162,TotalExpireTime=10} + +TypeID = 6013 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4164,TotalExpireTime=10} + +TypeID = 6014 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4166,TotalExpireTime=10} + +TypeID = 6015 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4168,TotalExpireTime=10} + +TypeID = 6016 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4170,TotalExpireTime=10} + +TypeID = 6017 +Name = "a dead rabbit" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4173,TotalExpireTime=10} + +TypeID = 6018 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4176,TotalExpireTime=10} + +TypeID = 6019 +Name = "a slain banshee" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4179,TotalExpireTime=10} + +TypeID = 6020 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4182,TotalExpireTime=10} + +TypeID = 6021 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4185,TotalExpireTime=10} + +TypeID = 6022 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6023 +Name = "a dead larva" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4191,TotalExpireTime=10} + +TypeID = 6024 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4194,TotalExpireTime=10} + +TypeID = 6025 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4197,TotalExpireTime=10} + +TypeID = 6026 +Name = "a dead hyaena" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4200,TotalExpireTime=10} + +TypeID = 6027 +Name = "a dead gargoyle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4203,TotalExpireTime=10} + +TypeID = 6028 +Name = "a slain lich" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4206,TotalExpireTime=10} + +TypeID = 6029 +Name = "a slain crypt shambler" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4209,TotalExpireTime=10} + +TypeID = 6030 +Name = "a slain bonebeast" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4212,TotalExpireTime=10} + +TypeID = 6031 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4215,TotalExpireTime=10} + +TypeID = 6032 +Name = "a dead efreet" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4218,TotalExpireTime=10} + +TypeID = 6033 +Name = "a dead marid" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4221,TotalExpireTime=10} + +TypeID = 6034 +Name = "a dead badger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4224,TotalExpireTime=10} + +TypeID = 6035 +Name = "a dead skunk" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4227,TotalExpireTime=10} + +TypeID = 6036 +Name = "a dead gazer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4230,TotalExpireTime=10} + +TypeID = 6037 +Name = "a dead elder beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4233,TotalExpireTime=10} + +TypeID = 6038 +Name = "a dead yeti" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4236,TotalExpireTime=10} + +TypeID = 6039 +Name = "a dead crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4318,TotalExpireTime=10} + +TypeID = 6040 +Name = "a dead lizard sentinel" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4324,TotalExpireTime=10} + +TypeID = 6041 +Name = "a dead lizard snakecharmer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4327,TotalExpireTime=10} + +TypeID = 6042 +Name = "a dead chicken" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4330,TotalExpireTime=10} + +TypeID = 6043 +Name = "a dead kongra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4333,TotalExpireTime=10} + +TypeID = 6044 +Name = "a dead merlkin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4336,TotalExpireTime=10} + +TypeID = 6045 +Name = "a dead sibang" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4339,TotalExpireTime=10} + +TypeID = 6046 +Name = "a dead crocodile" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4342,TotalExpireTime=10} + +TypeID = 6047 +Name = "a dead carniphila" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4345,TotalExpireTime=10} + +TypeID = 6048 +Name = "a dead hydra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4348,TotalExpireTime=10} + +TypeID = 6049 +Name = "a dead panda" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4351,TotalExpireTime=10} + +TypeID = 6050 +Name = "a dead centipede" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4354,TotalExpireTime=10} + +TypeID = 6051 +Name = "a dead tiger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4357,TotalExpireTime=10} + +TypeID = 6052 +Name = "a dead elephant" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4360,TotalExpireTime=10} + +TypeID = 6053 +Name = "a dead bat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4363,TotalExpireTime=10} + +TypeID = 6054 +Name = "a dead flamingo" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4366,TotalExpireTime=10} + +TypeID = 6055 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4369,TotalExpireTime=10} + +TypeID = 6056 +Name = "a dead parrot" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4379,TotalExpireTime=10} + +TypeID = 6057 +Name = "a dead bird" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4382,TotalExpireTime=10} + +TypeID = 6058 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4372,TotalExpireTime=10} + +TypeID = 6059 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4375,TotalExpireTime=10} + +TypeID = 6060 +Name = "a dead tarantula" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4385,TotalExpireTime=10} + +TypeID = 6061 +Name = "a dead serpent spawn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4388,TotalExpireTime=10} + +TypeID = 6062 +Name = "a lifeless nettle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4391,TotalExpireTime=10} + +TypeID = 6063 +Name = "a dead quara pincher" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5521,TotalExpireTime=10} + +TypeID = 6064 +Name = "a dead quara mantassin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5522,TotalExpireTime=10} + +TypeID = 6065 +Name = "a dead quara constrictor" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5523,TotalExpireTime=10} + +TypeID = 6066 +Name = "a dead quara hydromancer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5524,TotalExpireTime=10} + +TypeID = 6067 +Name = "a dead quara predator" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5525,TotalExpireTime=10} + +TypeID = 6068 +Name = "demon dust" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5526,TotalExpireTime=10} + +TypeID = 6069 +Name = "a dead carrion worm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5539,TotalExpireTime=10} + +TypeID = 6070 +Name = "a slain pirate skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5564,TotalExpireTime=10} + +TypeID = 6071 +Name = "a slain pirate ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5565,TotalExpireTime=10} + +TypeID = 6072 +Name = "a dead tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5624,TotalExpireTime=10} + +TypeID = 6073 +Name = "a dead thornback tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5627,TotalExpireTime=10} + +TypeID = 6074 +Name = "a dead mammoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5665,TotalExpireTime=10} + +TypeID = 6075 +Name = "a dead blood crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5688,TotalExpireTime=10} + +TypeID = 6076 +Name = "a dead seagull" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5727,TotalExpireTime=10} + +TypeID = 6077 +Name = "a dead toad" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5765,TotalExpireTime=10} + +TypeID = 6078 +Name = "remains of Ferumbras" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5931,TotalExpireTime=10} + +TypeID = 6079 +Name = "a dead frog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5934,TotalExpireTime=10} + +TypeID = 6080 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4240,TotalExpireTime=10} + +TypeID = 6081 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4247,TotalExpireTime=10} + +TypeID = 6082 +Name = "a dead human" + +TypeID = 6083 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6084 +Name = "a dead human" +Flags = {Container,Expire} +Attributes = {Capacity=10,ExpireTarget=6082,TotalExpireTime=120} + +TypeID = 6085 +Name = "a large trunk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6086 +Name = "a faked label" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 6087 +Name = "a music sheet" +Description = "It contains the first verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6088 +Name = "a music sheet" +Description = "It contains the second verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6089 +Name = "a music sheet" +Description = "It contains the third verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6090 +Name = "a music sheet" +Description = "It contains the fourth and last verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6091 +Name = "a very noble-looking watch" +Description = "Unfortunately it seems to be broken" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6092 +Name = "a very noble-looking watch" +Flags = {Take,Expire} +Attributes = {Weight=50,ExpireTarget=6091,TotalExpireTime=259200} + +TypeID = 6093 +Name = "a crystal ring" +Description = "The initials E.S. are engraved on it" +Flags = {Take} +Attributes = {Weight=90,SlotType=RING} + +TypeID = 6094 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6095 +Name = "a pirate shirt" +Flags = {Take,Armor} +Attributes = {Weight=2000,SlotType=BODY,ArmorValue=3} + +TypeID = 6096 +Name = "a pirate hat" +Flags = {Take,Armor} +Attributes = {Weight=1250,SlotType=HEAD,ArmorValue=3} + +TypeID = 6097 +Name = "a hook" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6098 +Name = "an eye patch" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=150} + +TypeID = 6099 +Name = "Brutus Bloodbeard's hat" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 6100 +Name = "the Lethal Lissy's shirt" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 6101 +Name = "Ron the Ripper's sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 6102 +Name = "Deadeye Devious' eye patch" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6103 +Name = "an unholy boo" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6104 +Name = "a jewel case" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=170} + +TypeID = 6105 +Name = "Striker's favourite pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 6106 +Name = "a bottle of whisper beer" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 6107 +Name = "a staff" +Description = "It must be the one which Simon the Beggar talked about" +Flags = {Take} +Attributes = {Weight=3800} + +TypeID = 6108 +Name = "an atlas" +Description = "It is filled with detailed maps" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 6109 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6110} + +TypeID = 6110 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6109} + +TypeID = 6111 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6112} + +TypeID = 6112 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6111} + +TypeID = 6113 +Name = "a letter to Eremo" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6114 +Name = "an armor rack kit" +Description = "Use it in your house to construct an armor rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6115 +Name = "a weapon rack kit" +Description = "Use it in your house to construct a weapon rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6116 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6117 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6118 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Unmove} + +TypeID = 6119 +Name = "a scroll" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 6120 +Name = "Dragha's spellbook" +Description = "It apparently belonged to someone called Dragha, the apprentice of a voodoomaster" +Flags = {Take} +Attributes = {Weight=5800} + +TypeID = 6121 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6122 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6123 +Name = "a piano" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6124 +Name = "a damaged logbook" +Description = "It must be the one which the explorer society requested" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 6125 +Name = "a tortoise egg from Nargor" +Description = "Handle with care and don't try to eat it" +Flags = {Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 6126 +Name = "a peg leg" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6127 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6128 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6129 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6130 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6131 +Name = "a tortoise shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=26} \ No newline at end of file diff --git a/app/SabrehavenServer/data/items792/items.srv b/app/SabrehavenServer/data/items792/items.srv new file mode 100644 index 0000000..5a374e5 --- /dev/null +++ b/app/SabrehavenServer/data/items792/items.srv @@ -0,0 +1,29882 @@ +# items.srv - Tibia Item definitions +# --- begin of server specific object types --- + +TypeID = 1 +Name = "water" + +TypeID = 2 +Name = "wine" + +TypeID = 3 +Name = "beer" + +TypeID = 4 +Name = "mud" + +TypeID = 5 +Name = "blood" + +TypeID = 6 +Name = "slime" + +TypeID = 7 +Name = "oil" + +TypeID = 8 +Name = "urine" + +TypeID = 9 +Name = "milk" + +TypeID = 10 +Name = "manafluid" + +TypeID = 11 +Name = "lifefluid" + +TypeID = 12 +Name = "lemonade" + +TypeID = 13 +Name = "rum" + +TypeID = 14 +Name = "coconut milk" + +TypeID = 15 +Name = "fruit juice" + +# --- end of server specific object types --- + +TypeID = 100 +Name = "void" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 101 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 102 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 103 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 104 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 105 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 106 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 107 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 108 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 109 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 110 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 111 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 112 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 113 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 114 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 115 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 116 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 117 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 118 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 119 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 120 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 121 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 122 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 123 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 124 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 125 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 126 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 127 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 128 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 129 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 130 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 131 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 132 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 133 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 134 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 135 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 136 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 137 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 138 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 139 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 140 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 141 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 142 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 143 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 144 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 145 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 146 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 147 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 148 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 149 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 150 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 151 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 152 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 153 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 154 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 155 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 156 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 157 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 158 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 159 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 160 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 161 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 162 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 163 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 164 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 165 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 166 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 167 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 168 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 169 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 170 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 171 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 172 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 173 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 174 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 175 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 176 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 177 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 178 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 179 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 180 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 181 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 182 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 183 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 184 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 185 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 186 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 187 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 188 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 189 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 190 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 191 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 192 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 193 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 194 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 195 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 196 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 197 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 198 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 199 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 200 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 201 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 202 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 203 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 204 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 205 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 206 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 207 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 208 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 209 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 210 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 211 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 212 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 213 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 214 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 215 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 216 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 217 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 218 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 219 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 220 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 221 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 222 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 223 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 224 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 225 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 226 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 227 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 228 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 229 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 230 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 231 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 232 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 233 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 234 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 235 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 236 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 237 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 238 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 239 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 240 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 241 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 242 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 243 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 244 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 245 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 246 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 247 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 248 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 249 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 250 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 251 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 252 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 253 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 254 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 255 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 256 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 257 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 258 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 259 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 260 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 261 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 262 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 263 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 264 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 265 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 266 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 267 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 268 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 269 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 270 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 271 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 272 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 273 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 274 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 275 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 276 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 277 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 278 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 279 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 280 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 281 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 282 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 283 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 284 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 285 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 286 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 287 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 288 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 289 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 290 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 291 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 292 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 293 +Name = "grass" +Flags = {Bank,,CollisionEvent,Unmove} +Attributes = {Waypoints=150} + +TypeID = 294 +Name = "a pitfall" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=150,ExpireTarget=293,TotalExpireTime=300} + +TypeID = 295 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 296 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 297 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 298 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 299 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 300 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 301 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 302 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 303 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 304 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 305 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 306 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 307 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 308 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 309 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 310 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 311 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 312 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 313 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 314 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 315 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 316 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 317 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 318 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 319 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 320 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 321 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 322 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 323 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 324 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 325 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 326 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 327 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 328 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 329 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 330 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 331 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 332 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 333 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 334 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 335 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 336 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 337 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 338 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 339 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 340 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 341 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 342 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 343 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 344 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 345 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 346 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 347 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 348 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 349 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 350 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 351 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 352 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 353 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 354 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 355 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200,FluidSource=MUD} + +TypeID = 356 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 357 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 358 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 359 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 360 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 361 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 362 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 363 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 364 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 365 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 366 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 367 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 368 +Name = "earth ground" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 369 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 370 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 371 +Name = "dirt floor" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 372 +Name = "muddy floor" +Flags = {Bank,UseEvent,Unmove,Disguise} +Attributes = {Waypoints=200,FluidSource=MUD,DisguiseTarget=355} + +TypeID = 373 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 374 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 375 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 376 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 377 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 378 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 379 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 380 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 381 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 382 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 383 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 384 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 385 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 386 +Name = "dirt floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=120} + +TypeID = 387 +Name = "a small hole" +Description = "It seems too narrow to climb through" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 388 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 389 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 390 +Name = "a lava hole" +Description = "It seems to be inactive" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 391 +Name = "a lava hole" +Description = "It emits heat and light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=215} + +TypeID = 392 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 393 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 394 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=110,ExpireTarget=372,TotalExpireTime=300} + +TypeID = 395 +Name = "dirt floor" +Flags = {Bank,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 396 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 397 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 398 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 399 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 400 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 401 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 402 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 403 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 404 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 405 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 406 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 407 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 408 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 409 +Name = "white marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 410 +Name = "black marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 411 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 412 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 413 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 414 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 415 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 416 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 417 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 418 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 419 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 420 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 421 +Name = "sandy floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=100} + +TypeID = 422 +Name = "a sandstone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 423 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 424 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 425 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 426 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=424} + +TypeID = 427 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=425} + +TypeID = 428 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 429 +Name = "a stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 430 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 431 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 432 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 433 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 434 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 435 +Name = "a sewer grate" +Flags = {UseEvent,Unmove} + +TypeID = 436 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 437 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 438 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 439 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 440 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 441 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 442 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 443 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 444 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 445 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 446 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 447 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 448 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 449 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 450 +Name = "wooden floor" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 451 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 452 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 453 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 454 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 455 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 456 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 457 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 458 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 459 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 460 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 461 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 462 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 463 +Name = "a white stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 464 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 465 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 466 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 467 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 468 +Name = "nothing special" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=470} + +TypeID = 469 +Name = "stairs" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=95} + +TypeID = 470 +Name = "nothing special" +Flags = {Bank,Unmove} +Attributes = {Waypoints=95} + +TypeID = 471 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 472 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 473 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 474 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 475 +Name = "a closed trapdoor" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 476 +Name = "an open trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Expire} +Attributes = {Waypoints=100,ExpireTarget=475,TotalExpireTime=2} + +TypeID = 477 +Name = "a pedestal" +Flags = {Unmove,Avoid,Height} + +TypeID = 478 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 479 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 480 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 481 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 482 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 483 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 484 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 485 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 486 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 487 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 488 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 489 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 490 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 491 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 492 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 493 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 494 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 495 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 496 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 497 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 498 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 499 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 500 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 501 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 502 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 503 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 504 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 505 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 506 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 507 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 508 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 509 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 510 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 511 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 512 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 513 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 514 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 515 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 516 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 517 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 518 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 519 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 520 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 521 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 522 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 523 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 524 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 525 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 526 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 527 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 528 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 529 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 530 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 531 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 532 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 533 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 534 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 535 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 536 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 537 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 538 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 539 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 540 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 541 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 542 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 543 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 544 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 545 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 546 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 547 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 548 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 549 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 550 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 551 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 552 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 553 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 554 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 555 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 556 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 557 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 558 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 559 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 560 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 561 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 562 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 563 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 564 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 565 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 566 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 567 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 568 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 569 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 570 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 571 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 572 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 573 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 574 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 575 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 576 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 577 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 578 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 579 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 580 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 581 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 582 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 583 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 584 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 585 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 586 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 587 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 588 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 589 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 590 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 591 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 592 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 593 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 594 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=160,ExpireTarget=593,TotalExpireTime=300} + +TypeID = 595 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 596 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 597 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 598 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 599 +Name = "a strange carving" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 600 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 601 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 602 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 603 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 604 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 605 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 606 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=170} + +TypeID = 607 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=606,TotalExpireTime=300} + +TypeID = 608 +Name = "a loose ice pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=120} + +TypeID = 609 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=120,ExpireTarget=608,TotalExpireTime=300} + +TypeID = 610 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 611 +Name = "a snow heap" +Flags = {UseEvent,Unmove,Avoid} + +TypeID = 612 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 613 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 614 +Name = "sand" +Flags = {UseEvent,Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 615 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=614,TotalExpireTime=30} + +TypeID = 616 +Name = "sand" +Flags = {Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 617 +Name = "sand" +Flags = {Bank,Unmove,Expire,Disguise} +Attributes = {Waypoints=160,ExpireTarget=616,TotalExpireTime=4000,DisguiseTarget=231} + +TypeID = 618 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=618,TotalExpireTime=2200} + +TypeID = 621 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=620} + +TypeID = 622 +Name = "water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 623 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 624 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 625 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 626 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 627 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 628 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 629 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 630 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 631 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 632 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 633 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 634 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 635 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 636 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 637 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 638 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 639 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 640 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 641 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 642 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 643 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 644 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 645 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 646 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 647 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 648 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 649 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 650 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 651 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 652 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 653 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 654 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 655 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 656 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 657 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 658 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 659 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 660 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 661 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 662 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 663 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 664 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 665 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 666 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 667 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 668 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 669 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 670 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 671 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 672 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 673 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 674 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 675 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 676 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 677 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 678 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 679 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 680 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 681 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 682 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 683 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 684 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 685 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 686 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 687 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 688 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 689 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 690 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 691 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 692 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 693 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 694 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 695 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 696 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 697 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 698 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 699 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 700 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 701 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 702 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 703 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 704 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 705 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 706 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 707 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 708 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 709 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 710 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 711 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 712 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 713 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 714 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 715 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 716 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 717 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 718 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 719 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 720 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 721 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 722 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 723 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 724 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 725 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 726 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 727 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 728 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 729 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 730 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 731 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 732 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 733 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 734 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 735 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 736 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 737 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 738 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 739 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 740 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 741 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 742 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 743 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 744 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 745 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 746 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 747 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 748 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 749 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 750 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 751 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 752 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 753 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 754 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 755 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 756 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 757 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 758 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 759 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 760 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 761 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 762 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 763 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 764 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 765 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 766 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 767 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 768 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 769 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 770 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 771 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 772 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 773 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 774 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 775 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 776 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 777 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 778 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 779 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 780 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 781 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 782 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 783 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 784 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 785 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 786 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 787 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 788 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 789 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 790 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 791 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 792 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 793 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 794 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 795 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 796 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 797 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 798 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 799 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 800 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 801 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 802 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 803 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 804 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 805 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 806 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 807 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 808 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 809 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 810 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 811 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 812 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 813 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 814 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 815 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 816 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 817 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 818 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 819 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 820 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 821 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 822 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 823 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 824 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 825 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 826 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 827 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 828 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 829 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 830 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 831 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 832 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 833 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 834 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 835 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 836 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 837 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 838 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 839 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 840 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 841 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 842 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 843 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 844 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 845 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 846 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 847 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 848 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 849 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 850 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 851 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 852 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 853 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 854 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 855 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 856 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 857 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 858 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 859 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 860 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 861 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 862 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 863 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 864 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 865 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 866 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 867 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 868 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 869 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 870 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 871 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 872 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 873 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 874 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 875 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 876 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 877 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 878 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 879 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 880 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 881 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 882 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 883 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 884 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 885 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 886 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 887 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 888 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 889 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 890 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 891 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 892 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 893 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 894 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 895 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 896 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 897 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 898 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 899 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 900 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 901 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 902 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 903 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 904 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 905 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 906 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 907 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 908 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 909 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 910 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 911 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 912 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 913 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 914 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 915 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 916 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 917 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 918 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 919 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 920 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 921 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 922 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 923 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 924 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 925 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 926 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 927 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 928 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 929 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 930 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 931 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 932 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 933 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 934 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 935 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 936 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 937 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 938 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 939 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 940 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 941 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 942 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 943 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 944 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 945 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 946 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 947 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 948 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 949 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 950 +Name = "soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 951 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 952 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 953 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 954 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 955 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 956 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 957 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 958 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 959 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 960 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 961 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 962 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 963 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 964 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 965 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 966 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 967 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 968 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 969 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 970 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 971 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 972 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 973 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 974 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 975 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 976 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 977 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 978 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 979 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 980 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 981 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 982 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 983 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 984 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 985 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 986 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 987 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 988 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 989 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 990 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 991 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 992 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 993 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 994 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 995 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 996 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 997 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 998 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 999 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1000 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1001 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1002 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1003 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1004 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1005 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1006 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1007 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1008 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1009 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1010 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1011 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1012 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1013 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1014 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1015 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1016 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1017 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1018 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1019 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1020 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1021 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1022 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1023 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1024 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1025 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1026 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1027 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1028 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1029 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1030 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1031 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1032 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1033 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1034 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1035 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1036 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1037 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1038 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1039 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1040 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1041 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1042 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1043 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1044 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1045 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1046 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1047 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1048 +Name = "jungle grass " +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1049 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1050 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1051 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1052 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1053 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1054 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1055 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1056 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1057 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1058 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1059 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1060 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1061 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1062 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1063 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1064 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1065 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1066 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 1067 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {ExpireTarget=1066,TotalExpireTime=75} + +TypeID = 1068 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1069 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1070 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1071 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1072 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1073 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1074 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1075 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1076 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1077 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1078 +Name = "an ant trail" +Flags = {Unmove} + +TypeID = 1079 +Name = "an ant-hill" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1080 +Name = "an earth hole" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} + +TypeID = 1081 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1082 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1083 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1084 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1085 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1086 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1087 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1088 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1089 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1090 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1091 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1092 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1093 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1094 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1095 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1096 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1097 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1098 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1099 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=0,DisguiseTarget=1128} + +TypeID = 1100 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1101 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1102 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1103 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1104 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1105 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1106 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1107 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1108 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1109 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1110 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1111 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1112 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1113 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1114 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1115 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1116 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1117 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1118 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1119 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1120 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1121 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1122 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1123 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1124 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1125 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1126 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1127 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1128 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1129 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1130 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1131 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1132 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1133 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1134 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1135 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1136 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1137 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1138 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1139 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1140 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1141 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1142 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1143 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1144 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1145 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1146 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1147 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1148 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1149 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1150 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1151 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1152 +Name = "a flat roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1153 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1154 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1155 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1156 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 1157 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1158 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1159 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1160 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1161 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1162 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1163 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1164 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1165 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1166 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1167 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1168 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1169 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1170 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1171 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1172 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1173 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1174 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1175 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1176 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1177 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1178 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1179 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1180 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1181 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1182 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1183 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1184 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1185 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1186 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1187 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1188 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1189 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1190 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1191 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1192 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1193 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1194 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1195 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1196 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1197 +Name = "a dried grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1198 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1199 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1200 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1201 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1202 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1203 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1204 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1205 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1206 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1207 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1208 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1209 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1210 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1211 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1212 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1213 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1214 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1215 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1216 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1217 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1218 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1219 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1220 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1221 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1222 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1223 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1224 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1225 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1226 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1227 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1228 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1229 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1230 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1231 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1232 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1233 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1234 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1235 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1236 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1237 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1238 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1239 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1240 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1241 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1242 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1243 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1244 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1245 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1246 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1247 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1248 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1249 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1250 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1251 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1252 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1253 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1254 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1255 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1256 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1257 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1258 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1259 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1260 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1261 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1262 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1263 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1264 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1265 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1266 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1267 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1268 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1269 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1270 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1271 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1272 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1273 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1274 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1275 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1276 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1277 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1278 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1279 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1280 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1281 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1282 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1283 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1284 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1285 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1286 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1287 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1288 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1289 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1290 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1291 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1292 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1293 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1294 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1295 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1296 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1297 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1298 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1299 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1300 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1301 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1302 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1303 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1304 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1305 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1306 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1307 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1308 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1309 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1310 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1311 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1312 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1313 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1314 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1315 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1316 +Name = "sandstone" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1317 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1318 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1319 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1320 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1321 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1322 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1323 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1324 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1325 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1326 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1327 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1328 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1329 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1330 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1331 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1332 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1333 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1334 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1335 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1336 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1337 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1338 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1339 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1340 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1341 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1342 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1343 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1344 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1345 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1346 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1347 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1348 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1349 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1350 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1351 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1352 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1353 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1354 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1355 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1356 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1357 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1358 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1359 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1360 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1361 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1362 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1363 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1364 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1365 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1366 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1367 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1368 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1369 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1370 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1371 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1372 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1373 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1374 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1375 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1376 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1377 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1378 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1379 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1380 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1381 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1382 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1383 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1384 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1385 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1386 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1387 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1388 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1389 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1390 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1391 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1392 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1393 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1394 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1395 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1396 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1397 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1398 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1399 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1400 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1401 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1402 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1403 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1404 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1405 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1406 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1407 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1408 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1409 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1410 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1411 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1412 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1413 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1414 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1415 +Name = "a paravent" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1416 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1417 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1418 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1419 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1420 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1421 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1422 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1423 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1424 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1425 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1426 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1427 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1428 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1429 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1430 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1431 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1432 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1433 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1434 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1435 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1436 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1437 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1438 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1439 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1440 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1441 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1442 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1443 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1444 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1445 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1446 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1447 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1448 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1449 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1450 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1451 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1452 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1453 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1454 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1455 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1456 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1457 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1458 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1459 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1460 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1461 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1462 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1463 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1464 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1465 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1466 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1467 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1468 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1469 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1470 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1471 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1472 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1473 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1474 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1475 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1476 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1477 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1478 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1479 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1480 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1481 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1482 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1483 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1484 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1485 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1486 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1487 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1488 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1489 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1490 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1491 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1492 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1493 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1494 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1495 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1496 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1497 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1498 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1499 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1500 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1501 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1502 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1503 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1504 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1505 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1506 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1507 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1508 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1509 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1510 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1511 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1512 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1513 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1514 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1515 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1516 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1517 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1518 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1519 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1520 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1521 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1522 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1523 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1524 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1525 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1526 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1527 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1528 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1529 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1530 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1531 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1532 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1533 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1534 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1535 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1536 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1537 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1538 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1539 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1540 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1541 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1542 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1543 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1544 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1545 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1546 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1547 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1548 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1549 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1550 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1551 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1552 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1553 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1554 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1555 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1556 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1557 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1558 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1559 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1560 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1561 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1562 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1563 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1564 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1565 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1566 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1567 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1568 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1569 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1570 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1571 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1572 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1573 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1574 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1575 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1576 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1577 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1578 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1579 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1580 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1581 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1582 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1583 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1584 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1585 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1586 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1587 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1588 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1589 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1590 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1591 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1592 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1593 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1594 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1595 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1596 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1597 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1598 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1599 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1600 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1601 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1602 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1603 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1604 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1605 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1606 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1607 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1608 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1609 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1610 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1611 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1612 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1613 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1614 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1615 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1616 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1617 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1618 +Name = "a temple wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1619 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1620 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1621 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1622 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1623 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1624 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1625 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1626 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1627 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1628 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1629 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1630 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1631 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1632 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1633 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1634 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1635 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1636 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1637 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1638 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1639 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1640 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1641 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1642 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1643 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1644 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1645 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1646 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1647 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1648 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1649 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1650 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1651 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1652 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1653 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1654 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1655 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1656 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1657 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1658 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1659 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1660 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1661 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1662 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1663 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1664 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1665 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1666 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1667 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1668 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1669 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1670 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1671 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1672 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1673 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1674 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1675 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1676 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1677 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1678 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1679 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1680 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1681 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1682 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1683 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1684 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1685 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1686 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1687 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1688 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1689 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1690 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1691 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1692 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1693 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1694 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1695 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1696 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1697 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1698 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1699 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1700 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1701 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1702 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1703 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1704 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1705 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1706 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1707 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1708 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1709 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1710 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1711 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1712 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1713 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1714 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1715 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1716 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1717 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1718 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1719 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1720 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1721 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1722 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1723 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1724 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1725 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1726 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1727 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1728 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1729 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1730 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1731 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1732 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1733 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1734 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1735 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1736 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1737 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1738 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1739 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1740 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1741 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1742 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1743 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1744 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1745 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1746 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1747 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1748 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1749 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1750 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1751 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1752 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1753 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1754 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1755 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1756 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1757 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1758 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1759 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1760 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1761 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1762 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1763 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1764 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1765 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1766 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1767 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1768 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1769 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1770 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1771 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 1772 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1773 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1774 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1775 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1776 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1777 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1778 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1779 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1780 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=41000} + +TypeID = 1781 +Name = "a small stone" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=360,Range=7,Attack=10,Defense=0,MissileEffect=10,Fragility=7} + +TypeID = 1782 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=78000} + +TypeID = 1783 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1784 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1785 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1786 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1787 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1788 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1789 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1790 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1791 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1792 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1793 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1794 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1795 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1796 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1797 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1798 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1799 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1800 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1801 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1802 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1803 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1804 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1805 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1806 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1807 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1808 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1809 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1810 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1811 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1812 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1813 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1814 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1815 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1816 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1817 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1818 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1819 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1820 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1821 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1822 +Name = "a stone pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1823 +Name = "a stone pile" +Flags = {Unmove} + +TypeID = 1824 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1825 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1826 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1827 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1828 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1829 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1830 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1831 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1832 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1833 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1834 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1835 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1836 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1837 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1838 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1839 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1840 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1841 +Name = "a blue shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1842 +Name = "a red shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1843 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1844 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1845 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1846 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1847 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1848 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1849 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1850 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1851 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1852 +Name = "stones" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1853 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1854 +Name = "stones" +Flags = {Unmove} + +TypeID = 1855 +Name = "stones" +Flags = {Unmove} + +TypeID = 1856 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1857 +Name = "stones" +Flags = {Unmove} + +TypeID = 1858 +Name = "stones" +Flags = {Unmove} + +TypeID = 1859 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1860 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1861 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1862 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1863 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1864 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1865 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1866 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1867 +Name = "a stone" +Flags = {Unmove} + +TypeID = 1868 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1869 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1870 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1871 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1872 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1873 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1874 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1875 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1876 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1877 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1878 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1879 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1880 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1881 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1882 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1883 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1884 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1885 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1886 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1887 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1888 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1889 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1890 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1891 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1892 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1893 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1894 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1895 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1896 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1897 +Name = "debris" +Flags = {Unmove} + +TypeID = 1898 +Name = "debris" +Flags = {Unmove} + +TypeID = 1899 +Name = "debris" +Flags = {Unmove} + +TypeID = 1900 +Name = "debris" +Flags = {Unmove} + +TypeID = 1901 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1902 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1903 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1904 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1905 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1906 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1907 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1908 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1909 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1910 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1911 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1912 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1913 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1914 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1915 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1916 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1917 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1918 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1919 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1920 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1921 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1922 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1923 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1924 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1925 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1926 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1927 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1928 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1929 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1930 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1931 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1932 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1933 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1934 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1935 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1936 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1937 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1938 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1939 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1940 +Name = "a small basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1941 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1942 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1943 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1944 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1945 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1946 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1947 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1948 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1949 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 1950 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1951 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1952 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1953 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1954 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1955 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1956 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1957 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1958 +Name = "wooden stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1959 +Name = "a mystic flame" +Description = "You feel drawn to the mesmerizing light" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=173} + +TypeID = 1960 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1961 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1962 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1963 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1964 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1965 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1966 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1967 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1968 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1969 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1970 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1971 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1972 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1973 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1974 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1975 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1976 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1977 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1978 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1979 +Name = "a grave" +Flags = {Bottom,Unmove,AllowDistRead} + +TypeID = 1980 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1981 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1982 +Name = "a grave stone" +Flags = {Unmove,AllowDistRead} + +TypeID = 1983 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1984 +Name = "a stone coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1985 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1986 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1987 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1988 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1989 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1990 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2474} + +TypeID = 1991 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2476} + +TypeID = 1992 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1993 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1994 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1995 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1996 +Name = "a stone circle" +Flags = {Unmove} + +TypeID = 1997 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 1998 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=7,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 1999 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=5,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2000 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=3,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2001 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 2002 +Name = "a campfire" +Flags = {Unpass,Unmove} + +TypeID = 2003 +Name = "a campfire" +Flags = {Unpass,Unmove} +Attributes = {Brightness=5,LightColor=206} + +TypeID = 2004 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2005 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2006 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2007 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2008 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2009 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2010 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2011 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2012 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2013 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2014 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2015 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2016 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2017 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2018 +Name = "a dragon flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2019 +Name = "a castle flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2020 +Name = "a flag of Tibia" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2021 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2022 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2023 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2024 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2025 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2059,DestroyTarget=3141} + +TypeID = 2026 +Name = "a statue" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2027 +Name = "a hero statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2028 +Name = "a monument" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2029 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2045,DestroyTarget=3142} + +TypeID = 2030 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2048,DestroyTarget=3142} + +TypeID = 2031 +Name = "an angel statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2032 +Name = "a dwarven statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2033 +Name = "a watchdog statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2034 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2035 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2036 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2037 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2038 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2039 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2040 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2041 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2042 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2043 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2044,DestroyTarget=3142} + +TypeID = 2044 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2029,DestroyTarget=3142} + +TypeID = 2045 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2043,DestroyTarget=3142} + +TypeID = 2046 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2047,DestroyTarget=3142} + +TypeID = 2047 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2030,DestroyTarget=3142} + +TypeID = 2048 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2046,DestroyTarget=3142} + +TypeID = 2049 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2050 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2051 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2052 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2053 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2054 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2055 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2056 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2057 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2058 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2059 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2060,DestroyTarget=3141} + +TypeID = 2060 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2061,DestroyTarget=3141} + +TypeID = 2061 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2025,DestroyTarget=3141} + +TypeID = 2062 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2063} + +TypeID = 2063 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2062,Brightness=6,LightColor=206} + +TypeID = 2064 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2065} + +TypeID = 2065 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2064,Brightness=6,LightColor=206} + +TypeID = 2066 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2067 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2068 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2069 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2070 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2071 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2072 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2073 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2074 +Name = "a small pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2075 +Name = "a small lit pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=207} + +TypeID = 2076 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2077 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2078 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2079 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2080 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2081 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2082 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2083 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2084 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2085 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2086 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2087 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2088 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2089 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2090 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2091 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2092 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2093 +Name = "a stone snake pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2094 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2095 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2096 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2097 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2098 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2099 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2100 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2101 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2102 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2103 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2104 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2105 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2106 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2107 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2108 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2109,Brightness=0,LightColor=215} + +TypeID = 2109 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2108,Brightness=7,LightColor=207} + +TypeID = 2110 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=207} + +TypeID = 2111 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2112 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2113 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2114 +Name = "an empty coal basin" +Flags = {CollisionEvent,Unpass,Unmove,Height} +Attributes = {Brightness=0,LightColor=215} + +TypeID = 2115 +Name = "a stone coal basin" +Flags = {Unpass,Unmove,Unlay,Height} +Attributes = {Brightness=7,LightColor=206} + +TypeID = 2116 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2117,Brightness=0,LightColor=215} + +TypeID = 2117 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 2118 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200,ExpireTarget=2119,TotalExpireTime=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2119 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2120,TotalExpireTime=150} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2120 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=206,ExpireTarget=0,TotalExpireTime=100} + +TypeID = 2121 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104,ExpireTarget=0,TotalExpireTime=250} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2122 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137,ExpireTarget=0,TotalExpireTime=100} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2123 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2124 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2125 +Name = "a fire" +Flags = {Unmove,MagicField} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 2126 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2127 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2128 +Name = "a magic wall" +Flags = {Unpass,CollisionEvent,Unmove,Unthrow,Unlay,MagicField,Expire} +Attributes = {Brightness=3,LightColor=5,ExpireTarget=0,TotalExpireTime=20} + +TypeID = 2129 +Name = "a magic wall" +Flags = {Unpass,Unmove,Unthrow,Unlay,MagicField} +Attributes = {Brightness=3,LightColor=5} + +TypeID = 2130 +Name = "rush wood" +Flags = {Unpass,Unmove,Unlay,MagicField,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=45} + +TypeID = 2131 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=206,ExpireTarget=2132,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2132 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2133,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2133 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=207,ExpireTarget=0,TotalExpireTime=5} + +TypeID = 2134 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=214,ExpireTarget=0,TotalExpireTime=8} +MagicField = {Type=POISON,Count=100,Damage=5} + +TypeID = 2135 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=214,ExpireTarget=0,TotalExpireTime=5} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2136 +Name = "smoke" +Flags = {Unmove,MagicField} + +TypeID = 2137 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2138,TotalExpireTime=7} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2138 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=203,ExpireTarget=2151,TotalExpireTime=2} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2139 +Name = "a fire" +Flags = {Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2140,TotalExpireTime=2,DisguiseTarget=2140} + +TypeID = 2140 +Name = "ashes" +Flags = {Unmove,Avoid,Expire} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2137,TotalExpireTime=8} + +TypeID = 2141 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2142 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2140,TotalExpireTime=7,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2143 +Name = "ashes" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2139,TotalExpireTime=1,DisguiseTarget=2140} + +TypeID = 2144 +Name = "lava" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=150,Brightness=4,LightColor=193} + +TypeID = 2145 +Name = "strange slits" +Flags = {CollisionEvent,Unmove} + +TypeID = 2146 +Name = "blades" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2145,TotalExpireTime=3} + +TypeID = 2147 +Name = "strange holes" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2148,TotalExpireTime=1} + +TypeID = 2148 +Name = "spikes" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2147,TotalExpireTime=3} + +TypeID = 2149 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=209,ExpireTarget=2150,TotalExpireTime=5,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2150 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=209,ExpireTarget=2149,TotalExpireTime=1,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2151 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=2,LightColor=209,ExpireTarget=2139,TotalExpireTime=2,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2152 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2153 +Name = "a marble pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2154 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2155 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2156 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2157 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2158 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2159 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2160 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2161 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2162 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2163 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2164 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2165 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2166 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2167 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2168 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2169 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2170 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2171 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2172 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2173 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2174 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2175 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2176 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2177 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2178} + +TypeID = 2178 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2177} + +TypeID = 2179 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2180} + +TypeID = 2180 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2179} + +TypeID = 2181 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2182 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2183 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2184 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2185 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2186 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2186} + +TypeID = 2187 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2188 +Name = "a sandstone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2189 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2190 +Name = "an oriental pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2191 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2192 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2193 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2194 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2195 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2196 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2197 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2198 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2199 +Name = "an obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2200 +Name = "a broken obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2201 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2202 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2203 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2204 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2205 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2206 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2207 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2208 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2209 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2210 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2211 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2212 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2213 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2214 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2215 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2216 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2217 +Name = "an ominous pillar" +Flags = {Bottom,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2190} + +TypeID = 2218 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2219 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2220 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2221 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2222 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2223 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2224 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2225 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2226 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2227 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2228 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2229 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2230 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2231 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2232 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2233 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2234 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2235 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2236 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2237 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2238 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2239 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2240 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2241 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2242 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2243 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2244 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2245 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2246 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2247 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2248 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2249 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2250 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2251 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2252 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2253 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2254 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2255 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2256 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2257 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2258 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2259 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2260 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2261 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2262 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2263 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2264 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2265 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2266 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2267 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2268 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2269 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2270 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2271 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2272 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2273 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2274 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2275 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2276 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2277 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2278 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2279 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2280 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2281 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2282 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2283 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2284 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2285 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2286 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2287 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2288 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2289 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2290 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2291 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2292 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2293 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2294 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2295 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3146} + +TypeID = 2296 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3145} + +TypeID = 2297 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2298 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2299 +Name = "a small totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2300 +Name = "a large totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2301 +Name = "a totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2302 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2303 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2304 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2305 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2306 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2307 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2308 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2309 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2310 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2311 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2312 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2313 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2314 +Name = "a big table" +Flags = {Destroy,Height,Avoid,Unpass} +Attributes = {DestroyTarget=3138} + +TypeID = 2315 +Name = "a square table" +Flags = {Destroy,Height,Avoid,Unpass} +Attributes = {DestroyTarget=3138} + +TypeID = 2316 +Name = "a small round table" +Flags = {Destroy,Height,Avoid,Unpass} +Attributes = {DestroyTarget=3138} + +TypeID = 2317 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2318 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2319 +Name = "a small table" +Flags = {Destroy,Height,Avoid,Unpass} +Attributes = {DestroyTarget=3140} + +TypeID = 2320 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2321 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2322 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2323 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2324 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2325 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2326 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2327 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2328 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2329 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2330 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2331 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2332 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2333 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2334 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2335 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2336 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2337 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2338 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2339 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2340 +Name = "a passthrough" +Flags = {Door,Unpass,Unmove,Height} + +TypeID = 2341 +Name = "an open passthrough" +Flags = {Door,Unmove} + +TypeID = 2342 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2343 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2344 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2345 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2346 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2347,DestroyTarget=3141} + +TypeID = 2347 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2348 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2349,DestroyTarget=3137} + +TypeID = 2349 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2348,DestroyTarget=3137} + +TypeID = 2350 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2351,DestroyTarget=3137} + +TypeID = 2351 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2350,DestroyTarget=3137} + +TypeID = 2352 +Name = "a thick trunk" +Flags = {Destroy,Height,Unpass} +Attributes = {DestroyTarget=3136} + +TypeID = 2353 +Name = "an ornamented stone table" +Flags = {Rotate,Destroy,Height,Unpass} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2354 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2355 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2356 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2357 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2358 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2359,DestroyTarget=3138} + +TypeID = 2359 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2360,DestroyTarget=3138} + +TypeID = 2360 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2361,DestroyTarget=3138} + +TypeID = 2361 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2358,DestroyTarget=3138} + +TypeID = 2362 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2363 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2364 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2365 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2366 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2369,DestroyTarget=3139} + +TypeID = 2367 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2368,DestroyTarget=3139} + +TypeID = 2368 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2366,DestroyTarget=3139} + +TypeID = 2369 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2367,DestroyTarget=3139} + +TypeID = 2370 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2371 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2372 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2373 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2374 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2377,DestroyTarget=3138} + +TypeID = 2375 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2376,DestroyTarget=3138} + +TypeID = 2376 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2374,DestroyTarget=3138} + +TypeID = 2377 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2375,DestroyTarget=3138} + +TypeID = 2378 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2381,DestroyTarget=3138} + +TypeID = 2379 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2380,DestroyTarget=3138} + +TypeID = 2380 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2378,DestroyTarget=3138} + +TypeID = 2381 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2379,DestroyTarget=3138} + +TypeID = 2382 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2385,DestroyTarget=3138} + +TypeID = 2383 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2384,DestroyTarget=3138} + +TypeID = 2384 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2382,DestroyTarget=3138} + +TypeID = 2385 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2383,DestroyTarget=3138} + +TypeID = 2386 +Name = "a small purple pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2387 +Name = "a small green pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2388 +Name = "a small red pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2389 +Name = "a small blue pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2390 +Name = "a small orange pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2391 +Name = "a small turquoise pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2392 +Name = "a small white pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2393 +Name = "a heart pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 2394 +Name = "a blue pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2395 +Name = "a red pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2396 +Name = "a green pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2397 +Name = "a yellow pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2398 +Name = "a round blue pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2399 +Name = "a round red pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2400 +Name = "a round purple pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2401 +Name = "a round turquoise pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2402 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2403 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2404 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2405 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2406 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2407 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2408 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2409 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2410 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2411 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2412 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2413 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2414 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2415 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2416 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2417 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2418 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2421,DestroyTarget=3136} + +TypeID = 2419 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2420,DestroyTarget=3136} + +TypeID = 2420 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2418,DestroyTarget=3136} + +TypeID = 2421 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2419,DestroyTarget=3136} + +TypeID = 2422 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2425,DestroyTarget=3136} + +TypeID = 2423 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2424,DestroyTarget=3136} + +TypeID = 2424 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2422,DestroyTarget=3136} + +TypeID = 2425 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2423,DestroyTarget=3136} + +TypeID = 2426 +Name = "a small trunk" +Flags = {Avoid,Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2427 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2428 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2429 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2430 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2431 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=10,RotateTarget=2434,DestroyTarget=3136} + +TypeID = 2432 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=10,RotateTarget=2431,DestroyTarget=3136} + +TypeID = 2433 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=10,RotateTarget=2432,DestroyTarget=3136} + +TypeID = 2434 +Name = "drawers" +Flags = {Container,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=10,RotateTarget=2433,DestroyTarget=3136} + +TypeID = 2435 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2436 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2437 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2438 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2439 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2440 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2441 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2444,DestroyTarget=3139} + +TypeID = 2442 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2441,DestroyTarget=3139} + +TypeID = 2443 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2442,DestroyTarget=3139} + +TypeID = 2444 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2443,DestroyTarget=3139} + +TypeID = 2445 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2448,DestroyTarget=3139} + +TypeID = 2446 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2445,DestroyTarget=3139} + +TypeID = 2447 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2446,DestroyTarget=3139} + +TypeID = 2448 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2447,DestroyTarget=3139} + +TypeID = 2449 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2452,DestroyTarget=3140} + +TypeID = 2450 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2451,DestroyTarget=3140} + +TypeID = 2451 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2449,DestroyTarget=3140} + +TypeID = 2452 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2450,DestroyTarget=3140} + +TypeID = 2453 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2454,DestroyTarget=3140} + +TypeID = 2454 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2453,DestroyTarget=3140} + +TypeID = 2455 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2456 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2457 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2458 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2459 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2460 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2461 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2462 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2463 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2464 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2465 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2468,DestroyTarget=3136} + +TypeID = 2466 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2465,DestroyTarget=3136} + +TypeID = 2467 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2466,DestroyTarget=3136} + +TypeID = 2468 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2467,DestroyTarget=3136} + +TypeID = 2469 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3135} + +TypeID = 2470 +Name = "a box" +Flags = {Container,Unmove,Avoid,Height,Disguise} +Attributes = {Capacity=10,DisguiseTarget=2469} + +TypeID = 2471 +Name = "a crate" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=15,Weight=8000,DestroyTarget=3135} + +TypeID = 2472 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2482,DestroyTarget=3137} + +TypeID = 2473 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3140} + +TypeID = 2474 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2475 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2476 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2477 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2478 +Name = "a treasure chest" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=9500} + +TypeID = 2479 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2480 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2481,DestroyTarget=3137} + +TypeID = 2481 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2472,DestroyTarget=3137} + +TypeID = 2482 +Name = "a chest" +Flags = {Container,Take,Rotate,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2480,DestroyTarget=3137} + +TypeID = 2483 +Name = "a large trunk" +Flags = {Container,Rotate,Destroy,Height,Unpass} +Attributes = {Capacity=18,RotateTarget=2486,DestroyTarget=3140} + +TypeID = 2484 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height,Unpass} +Attributes = {Capacity=18,RotateTarget=2485,DestroyTarget=3140} + +TypeID = 2485 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height,Unpass} +Attributes = {Capacity=18,RotateTarget=2483,DestroyTarget=3140} + +TypeID = 2486 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height,Unpass} +Attributes = {Capacity=18,RotateTarget=2484,DestroyTarget=3140} + +TypeID = 2487 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2495} + +TypeID = 2488 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2496} + +TypeID = 2489 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2501} + +TypeID = 2490 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2502} + +TypeID = 2491 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2499} + +TypeID = 2492 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2500} + +TypeID = 2493 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2497} + +TypeID = 2494 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2498} + +TypeID = 2495 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2487} + +TypeID = 2496 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2488} + +TypeID = 2497 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2493} + +TypeID = 2498 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2494} + +TypeID = 2499 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2491} + +TypeID = 2500 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2492} + +TypeID = 2501 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2489} + +TypeID = 2502 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2490} + +TypeID = 2503 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2507} + +TypeID = 2504 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2508} + +TypeID = 2505 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2509} + +TypeID = 2506 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2510} + +TypeID = 2507 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2503} + +TypeID = 2508 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2504} + +TypeID = 2509 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2505} + +TypeID = 2510 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2506} + +TypeID = 2511 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2512 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2513 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2514 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2515 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2516 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2517 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2518 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2519 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=25,DestroyTarget=3138} + +TypeID = 2520 +Name = "a water cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 2521 +Name = "a lemonade cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=LEMONADE} + +TypeID = 2522 +Name = "a wine cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2523 +Name = "a barrel" +Flags = {Container,Destroy,Height,Avoid,Unpass} +Attributes = {Capacity=25,DestroyTarget=3135} + +TypeID = 2524 +Name = "a trough" +Flags = {MultiUse,FluidContainer,Unpass,Destroy,Height} +Attributes = {DestroyTarget=3135} + +TypeID = 2525 +Name = "a beer cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BEER} + +TypeID = 2526 +Name = "a dustbin" +Flags = {CollisionEvent,Unpass,Unmove} + +TypeID = 2527 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2528 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2529 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2530 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2531 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2532 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2533 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2534 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2535 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2536,Brightness=3,LightColor=199} + +TypeID = 2536 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2535,Brightness=0,LightColor=215} + +TypeID = 2537 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2538,Brightness=3,LightColor=193} + +TypeID = 2538 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2537,Brightness=0,LightColor=215} + +TypeID = 2539 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2540,Brightness=3,LightColor=193} + +TypeID = 2540 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2539,Brightness=0,LightColor=215} + +TypeID = 2541 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2542,Brightness=3,LightColor=193} + +TypeID = 2542 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2541,Brightness=0,LightColor=215} + +TypeID = 2543 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2469} + +TypeID = 2544 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2476} + +TypeID = 2545 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2474} + +TypeID = 2546 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2547 +Name = "a bananapalm" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3639} + +TypeID = 2548 +Name = "a dead dragon" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4025} + +TypeID = 2549 +Name = "a honeyflower patch" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2984} + +TypeID = 2550 +Name = "a dead human" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4240} + +TypeID = 2551 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2473} + +TypeID = 2552 +Name = "a dead tree" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3634} + +TypeID = 2553 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2433} + +TypeID = 2554 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2434} + +TypeID = 2555 +Name = "a small hole" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=130,DisguiseTarget=387} + +TypeID = 2556 +Name = "a loose board" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=100,DisguiseTarget=408} + +TypeID = 2557 +Name = "a pile of bones" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4285} + +TypeID = 2558 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=2435} + +TypeID = 2559 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=2438} + +TypeID = 2560 +Name = "a stone coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1983} + +TypeID = 2561 +Name = "a barrel" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2523} + +TypeID = 2562 +Name = "a hollow stone" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {DisguiseTarget=1777} + +TypeID = 2563 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=4305} + +TypeID = 2564 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1994} + +TypeID = 2565 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=1992} + +TypeID = 2566 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2567 +Name = "a lever" +Description = "It doesn't move" +Flags = {UseEvent,Unmove,Expire,Disguise} +Attributes = {ExpireTarget=2566,TotalExpireTime=240,DisguiseTarget=2773} + +TypeID = 2568 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2054} + +TypeID = 2569 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2570 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2773} + +TypeID = 2571 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire,Disguise} +Attributes = {ExpireTarget=0,TotalExpireTime=300,DisguiseTarget=1772} + +TypeID = 2572 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2573 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2574 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2575 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2576 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2577 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2578 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2579 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2580 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2581 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2582 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2583 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2584 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2585 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2586 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2587 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2588 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2589 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2590 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2591 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2592 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2593 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2594 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2595 +Name = "a badger fur" +Flags = {Clip,Unmove} + +TypeID = 2596 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2597 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2598 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2599 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2600 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2601 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2602 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2603 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2604 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2605 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2606 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2607 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2608 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2609 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2610 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2611 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2612 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2613 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2614 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2615 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2616 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2617 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2618 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2619 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2620 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2621 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2622 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2623 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2624 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2625 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2626 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2627 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2628 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2629 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2630 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2631 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2632 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2633 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2634 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2635 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2636 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2637 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2638 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2639 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2640 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2641 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2642 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2643 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2644 +Name = "a purple tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2645 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2646 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2647 +Name = "a green tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2648 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2649 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2650 +Name = "a yellow tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2651 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2652 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2653 +Name = "an orange tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2654 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2655 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2656 +Name = "a red tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2657 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2658 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2659 +Name = "a blue tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2660 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2661,TotalExpireTime=595} + +TypeID = 2661 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2660,TotalExpireTime=5} + +TypeID = 2662 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=595} + +TypeID = 2663 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=5} + +TypeID = 2664 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2668,TotalExpireTime=600} + +TypeID = 2665 +Name = "a white tapestry" +Flags = {Unmove} + +TypeID = 2666 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2667 +Name = "a white tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2668 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2664,TotalExpireTime=5} + +TypeID = 2669 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2670 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2671 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2672 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2673 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2674 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2675 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2676 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2677 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2678 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2679 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2680 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2681 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2682 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2683 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2684 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2685 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2686 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2687 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2688 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2689 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2690 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2691 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2692 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2693 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2694 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2695 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2696 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2697 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2698 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2699 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2700 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2701 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2702 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2703 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2704 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2705 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2706 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2707 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2708 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2709 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2710 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2711 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2712 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2713 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2714 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2715 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2716 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2717 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2718 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2719 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2720 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2721 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2722 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2723 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2724 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2725 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2726 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2727 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2728 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2729 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2730 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2731 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2732 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2733 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2734 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2735 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2736 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2737 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2738 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2739 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2740 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2741 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2742 +Name = "a pile of chopped wood" +Flags = {Unpass,Unmove} + +TypeID = 2743 +Name = "a block of wood" +Description = "It's a lumberjack's working place" +Flags = {Unpass,Unmove} + +TypeID = 2744 +Name = "some pieces of wood" +Flags = {Unmove} + +TypeID = 2745 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2746 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2747 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2748 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2749 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2750 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2751 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2752 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2753 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2754 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2755 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2756 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2757 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2758 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2759 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2760 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2761 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2762 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2763 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2764 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2765 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2766 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2767 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2768 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2769 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2770 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2771 +Name = "a sundial" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2772 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2773 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2774 +Name = "a torch bearer" +Flags = {UseEvent,Unmove,Hang,Disguise} +Attributes = {Brightness=0,LightColor=215,DisguiseTarget=2928} + +TypeID = 2775 +Name = "a furniture package" +Description = "It contains a construction kit for a red cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2776 +Name = "a furniture package" +Description = "It contains a construction kit for a green cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2777 +Name = "a furniture package" +Description = "It contains a construction kit for a wooden chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2778 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2779 +Name = "a furniture package" +Description = "It contains a construction kit for a sofa chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2780 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2781 +Name = "a furniture package" +Description = "It contains a construction kit for a ivory chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2782 +Name = "a furniture package" +Description = "It contains a construction kit for a small table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2783 +Name = "a furniture package" +Description = "It contains a construction kit for a round table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2784 +Name = "a furniture package" +Description = "It contains a construction kit for a square table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2785 +Name = "a furniture package" +Description = "It contains a construction kit for a big table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2786 +Name = "a furniture package" +Description = "It contains a construction kit for a stone table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2787 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2788 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2789 +Name = "a furniture package" +Description = "It contains a construction kit for a drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2790 +Name = "a furniture package" +Description = "It contains a construction kit for a dresser" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2791 +Name = "a furniture package" +Description = "It contains a construction kit for a locker" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2792 +Name = "a furniture package" +Description = "It contains a construction kit for a trough" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2793 +Name = "a furniture package" +Description = "It contains a construction kit for a barrel" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2794 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2795 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2796 +Name = "a furniture package" +Description = "It contains a construction kit for a birdcage" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2797 +Name = "a furniture package" +Description = "It contains a construction kit for a globe" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2798 +Name = "a furniture package" +Description = "It contains a construction kit for a table lamp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2799 +Name = "a furniture package" +Description = "It contains a construction kit for a telescope" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2800 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking horse" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2801 +Name = "a furniture package" +Description = "It contains a construction kit for a pendulum clock" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2802 +Name = "a furniture package" +Description = "It contains a construction kit for a knight statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2803 +Name = "a furniture package" +Description = "It contains a construction kit for a minotaur statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2804 +Name = "a furniture package" +Description = "It contains a construction kit for a goblin statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2805 +Name = "a furniture package" +Description = "It contains a construction kit for a large amphora" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2806 +Name = "a furniture package" +Description = "It contains a construction kit for a coal basin" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2807 +Name = "a furniture package" +Description = "It contains a construction kit for a piano" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2808 +Name = "a furniture package" +Description = "It contains a construction kit for a harp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2809 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2810 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2811 +Name = "a furniture package" +Description = "It contains an indoor plant" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2812 +Name = "a furniture package" +Description = "It contains a christmas tree" +Flags = {UseEvent,Avoid,Take,Expire,Height} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2813 +Name = "a blank paper" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2814 +Name = "a parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2815 +Name = "a scroll" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2816 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2817 +Name = "a blank parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2818 +Name = "a document" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=150} + +TypeID = 2819 +Name = "a parchment" +Flags = {Text,Take} +Attributes = {Weight=200} + +TypeID = 2820 +Name = "a paper" +Flags = {Text,Take} +Attributes = {Weight=100} + +TypeID = 2821 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2822 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 2823 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=790} + +TypeID = 2824 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2825 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2826 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2827 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2828 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2829 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2830 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2831 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2832 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2833 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2834 +Name = "a document" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=150} + +TypeID = 2835 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2836 +Name = "the holy Tible" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2837 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2838 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2839 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2840 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2841 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2842 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2843 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2844 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2845 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2846 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2847 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2848 +Name = "a purple tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2849 +Name = "a green tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2850 +Name = "a blue tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2851 +Name = "a grey tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2852 +Name = "a red tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2853 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2854 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2855 +Name = "a basket" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=950} + +TypeID = 2856 +Name = "a present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 2857 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2858 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2859 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2860 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2861 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2862 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2863 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2864 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2865 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2866 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2867 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2868 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2869 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2870 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2871 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2872 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2873 +Name = "a bucket" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=2000} + +TypeID = 2874 +Name = "a vial" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=180} + +TypeID = 2875 +Name = "a bottle" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2876 +Name = "a vase" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2877 +Name = "a green flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2878 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2879 +Name = "an elven vase" +Description = "It is made of very fine glass and covered with decorations" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2880 +Name = "a mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2881 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2882 +Name = "a jug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=750} + +TypeID = 2883 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2884 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2885 +Name = "a brown flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2886 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2887,TotalExpireTime=120} + +TypeID = 2887 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2888,TotalExpireTime=120} + +TypeID = 2888 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2889 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2890,TotalExpireTime=120} + +TypeID = 2890 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2891,TotalExpireTime=120} + +TypeID = 2891 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2892 +Name = "a broken bottle" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 2893 +Name = "an amphora" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=9700} + +TypeID = 2894 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2895 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2896 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2897 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2898 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2899 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2900 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2901 +Name = "a waterskin" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=700} + +TypeID = 2902 +Name = "a bowl" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=380} + +TypeID = 2903 +Name = "a golden mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=470} + +TypeID = 2904 +Name = "a large amphora" +Flags = {MultiUse,FluidContainer,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2905 +Name = "a plate" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 2906 +Name = "a watch" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 2907 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2908,Brightness=0,LightColor=0} + +TypeID = 2908 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2907,Brightness=6,LightColor=206} + +TypeID = 2909 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2910,Brightness=0,LightColor=0} + +TypeID = 2910 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2909,Brightness=6,LightColor=206} + +TypeID = 2911 +Name = "a candelabrum" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2912,Weight=5000,Brightness=0,LightColor=0} + +TypeID = 2912 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206,ExpireTarget=2913,TotalExpireTime=3000} + +TypeID = 2913 +Name = "a used candelabrum" +Flags = {Take} +Attributes = {Weight=4500,Brightness=0,LightColor=0} + +TypeID = 2914 +Name = "a lamp" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2915,Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2915 +Name = "a lit lamp" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2914,Weight=3000,Brightness=6,LightColor=199,ExpireTarget=2916,TotalExpireTime=2000} + +TypeID = 2916 +Name = "a used lamp" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2917 +Name = "a candlestick" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2918,Weight=300,Brightness=0,LightColor=0} + +TypeID = 2918 +Name = "a lit candlestick" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2917,Weight=300,Brightness=4,LightColor=206,ExpireTarget=2919,TotalExpireTime=3000} + +TypeID = 2919 +Name = "a used candlestick" +Flags = {Take} +Attributes = {Weight=250,Brightness=0,LightColor=0} + +TypeID = 2920 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2921,Weight=500,Brightness=0,LightColor=215} + +TypeID = 2921 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2920,Weight=500,Brightness=7,LightColor=206,ExpireTarget=2923,TotalExpireTime=600} + +TypeID = 2922 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2923,Weight=450,Brightness=0,LightColor=215} + +TypeID = 2923 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2922,Weight=450,Brightness=6,LightColor=206,ExpireTarget=2925,TotalExpireTime=300} + +TypeID = 2924 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2925,Weight=400,Brightness=0,LightColor=215} + +TypeID = 2925 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2924,Weight=400,Brightness=5,LightColor=206,ExpireTarget=2926,TotalExpireTime=300} + +TypeID = 2926 +Name = "a burnt down torch" +Flags = {Take,Expire} +Attributes = {Weight=350,Brightness=0,LightColor=215,ExpireTarget=0,TotalExpireTime=300} + +TypeID = 2927 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206} + +TypeID = 2928 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2929,Brightness=0,LightColor=0} + +TypeID = 2929 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2928,Brightness=6,LightColor=206} + +TypeID = 2930 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2931,Brightness=0,LightColor=0} + +TypeID = 2931 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2930,Brightness=6,LightColor=206} + +TypeID = 2932 +Name = "an oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=1400,Brightness=0,LightColor=204} + +TypeID = 2933 +Name = "a small oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=900,Brightness=0,LightColor=204} + +TypeID = 2934 +Name = "a tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2935} + +TypeID = 2935 +Name = "a lit tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2934,Brightness=4,LightColor=207} + +TypeID = 2936 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2937,Brightness=0,LightColor=0} + +TypeID = 2937 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2936,Brightness=6,LightColor=207} + +TypeID = 2938 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2939,Brightness=0,LightColor=0} + +TypeID = 2939 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2938,Brightness=6,LightColor=207} + +TypeID = 2940 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2941,Brightness=0,LightColor=0} + +TypeID = 2941 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2940,Brightness=6,LightColor=206} + +TypeID = 2942 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2943,Brightness=0,LightColor=0} + +TypeID = 2943 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2942,Brightness=6,LightColor=206} + +TypeID = 2944 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2945,Brightness=0,LightColor=0} + +TypeID = 2945 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2944,Brightness=6,LightColor=207} + +TypeID = 2946 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2947,Brightness=0,LightColor=0} + +TypeID = 2947 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2946,Brightness=6,LightColor=207} + +TypeID = 2948 +Name = "a wooden flute" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 2949 +Name = "a lyre" +Flags = {UseEvent,Take} +Attributes = {Weight=1250} + +TypeID = 2950 +Name = "a lute" +Flags = {UseEvent,Take} +Attributes = {Weight=3400} + +TypeID = 2951 +Name = "a bongo drum" +Flags = {Take} +Attributes = {Weight=2900} + +TypeID = 2952 +Name = "a drum" +Flags = {UseEvent,Take} +Attributes = {Weight=3200} + +TypeID = 2953 +Name = "panpipes" +Flags = {UseEvent,Take} +Attributes = {Weight=820} + +TypeID = 2954 +Name = "a simple fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 2955 +Name = "a fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2300} + +TypeID = 2956 +Name = "a royal fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2500} + +TypeID = 2957 +Name = "a post horn" +Description = "It's property of the Postmaster's Guild and only rewarded to loyal members" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2958 +Name = "a war horn" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2959 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2962,DestroyTarget=3139} + +TypeID = 2960 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2961,DestroyTarget=3139} + +TypeID = 2961 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2959,DestroyTarget=3139} + +TypeID = 2962 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2960,DestroyTarget=3139} + +TypeID = 2963 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2964,DestroyTarget=3136} + +TypeID = 2964 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2963,DestroyTarget=3136} + +TypeID = 2965 +Name = "a didgeridoo" +Flags = {UseEvent,Take} +Attributes = {Weight=4200} + +TypeID = 2966 +Name = "a war drum" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 2967 +Name = "a magical key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2968 +Name = "a wooden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2969 +Name = "a silver key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2970 +Name = "a copper key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2971 +Name = "a crystal key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2972 +Name = "a golden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2973 +Name = "a bone key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2974 +Name = "a water pipe" +Flags = {UseEvent,Take,Destroy} +Attributes = {Weight=6500,DestroyTarget=3143} + +TypeID = 2975 +Name = "a birdcage" +Description = "The poor bird seems to have died from a heart attack" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2976 +Name = "a birdcage" +Description = "You see a little bird inside" +Flags = {UseEvent,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2977 +Name = "a pumpkinhead" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=950} + +TypeID = 2978 +Name = "a pumpkinhead" +Flags = {Take,Expire} +Attributes = {Weight=1250,Brightness=3,LightColor=200,ExpireTarget=2977,TotalExpireTime=3000} + +TypeID = 2979 +Name = "a globe" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3143} + +TypeID = 2980 +Name = "a water pipe" +Flags = {Take,Destroy} +Attributes = {Weight=5600,DestroyTarget=3143} + +TypeID = 2981 +Name = "god flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2982 +Name = "an indoor plant" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2983 +Name = "a flower bowl" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2984 +Name = "a honey flower" +Flags = {Avoid,Take} +Attributes = {Weight=1000} + +TypeID = 2985 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2986 +Name = "a christmas tree" +Flags = {Unpass,Unlay,Destroy,Expire} +Attributes = {DestroyTarget=3140,Brightness=4,LightColor=204,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2987 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2988 +Name = "exotic flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2989 +Name = "a wooden doll" +Flags = {Take} +Attributes = {Weight=860} + +TypeID = 2990 +Name = "a football" + +TypeID = 2991 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 2992 +Name = "a snowball" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=80,Range=7,Attack=0,Defense=0,MissileEffect=13,Fragility=100} + +TypeID = 2993 +Name = "a teddy bear" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 2994 +Name = "a model ship" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 2995 +Name = "a piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2996 +Name = "a broken piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2997 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2998,DestroyTarget=3137} + +TypeID = 2998 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2999,DestroyTarget=3137} + +TypeID = 2999 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3000,DestroyTarget=3137} + +TypeID = 3000 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2997,DestroyTarget=3137} + +TypeID = 3001 +Name = "a bear doll" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 3002 +Name = "a voodoo doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3003 +Name = "a rope" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1800} + +TypeID = 3004 +Name = "a wedding ring" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3005 +Name = "an elven brooch" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 3006 +Name = "a ring of the sky" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3007 +Name = "a crystal ring" +Description = "The magical ring will convert the gold you touch" +Flags = {Take,ShowDetail} +Attributes = {Weight=90,SlotType=RING,TotalUses=100} + +TypeID = 3008 +Name = "a crystal necklace" +Flags = {Take} +Attributes = {Weight=490,SlotType=NECKLACE} + +TypeID = 3009 +Name = "a bronze necklace" +Flags = {Take} +Attributes = {Weight=410,SlotType=NECKLACE} + +TypeID = 3010 +Name = "an emerald bangle" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3011 +Name = "a crown" +Flags = {Take} +Attributes = {Weight=1900,SlotType=HEAD} + +TypeID = 3012 +Name = "a wolf tooth chain" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 3013 +Name = "a golden amulet" +Description = "Many gems glitter on the amulet" +Flags = {Take} +Attributes = {Weight=830,SlotType=NECKLACE} + +TypeID = 3014 +Name = "a star amulet" +Flags = {Take} +Attributes = {Weight=610,SlotType=NECKLACE} + +TypeID = 3015 +Name = "a silver necklace" +Flags = {Take} +Attributes = {Weight=480,SlotType=NECKLACE} + +TypeID = 3016 +Name = "a ruby necklace" +Flags = {Take} +Attributes = {Weight=570,SlotType=NECKLACE} + +TypeID = 3017 +Name = "a silver brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3018 +Name = "a scarab amulet" +Flags = {Take} +Attributes = {Weight=770,SlotType=NECKLACE} + +TypeID = 3019 +Name = "a demonbone amulet" +Flags = {Take} +Attributes = {Weight=690,SlotType=NECKLACE} + +TypeID = 3020 +Name = "some golden fruits" +Flags = {Take} +Attributes = {Weight=1070} + +TypeID = 3021 +Name = "a saphire amulet" +Flags = {Take} +Attributes = {Weight=680,SlotType=NECKLACE} + +TypeID = 3022 +Name = "an ancient tiara" +Flags = {Take} +Attributes = {Weight=820,SlotType=HEAD} + +TypeID = 3023 +Name = "a holy scarab" +Flags = {Take} +Attributes = {Weight=870} + +TypeID = 3024 +Name = "a holy falcon" +Flags = {Take} +Attributes = {Weight=840} + +TypeID = 3025 +Name = "an ancient amulet" +Flags = {Take} +Attributes = {Weight=840,SlotType=NECKLACE} + +TypeID = 3026 +Name = "a white pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3027 +Name = "a black pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3028 +Name = "a small diamond" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3029 +Name = "a small sapphire" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3030 +Name = "a small ruby" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3031 +Name = "a gold coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3032 +Name = "a small emerald" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3033 +Name = "a small amethyst" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3034 +Name = "a talon" +Description = "There are many rumours about these magic gems" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3035 +Name = "a platinum coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3036 +Name = "a violet gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3037 +Name = "a yellow gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3038 +Name = "a green gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3039 +Name = "a red gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3040 +Name = "a gold nugget" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 3041 +Name = "a blue gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3042 +Name = "a scarab coin" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3043 +Name = "a crystal coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3044 +Name = "an elephant tusk" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 3045 +Name = "a strange talisman" +Flags = {Take,ShowDetail} +Attributes = {Weight=290,SlotType=NECKLACE,AbsorbEnergy=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3046 +Name = "a magic light wand" +Flags = {ChangeUse,Take,ExpireStop,ShowDetail} +Attributes = {ChangeTarget=3047,Weight=1500,Brightness=0,LightColor=215} + +TypeID = 3047 +Name = "a magic light wand" +Description = "The wand glows" +Flags = {ChangeUse,Take,Expire,ShowDetail} +Attributes = {ChangeTarget=3046,Weight=1500,Brightness=8,LightColor=209,ExpireTarget=0,TotalExpireTime=3000} + +TypeID = 3048 +Name = "a might ring" +Flags = {Take,ShowDetail} +Attributes = {Weight=100,SlotType=RING,AbsorbPhysical=25,AbsorbMagic=25,ExpireTarget=0,TotalUses=20} + +TypeID = 3049 +Name = "a stealth ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=100,SlotType=RING,EquipTarget=3086} + +TypeID = 3050 +Name = "a power ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3087} + +TypeID = 3051 +Name = "an energy ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3088} + +TypeID = 3052 +Name = "a life ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3089} + +TypeID = 3053 +Name = "a time ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3090} + +TypeID = 3054 +Name = "a silver amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbPoison=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3055 +Name = "a platinum amulet" +Description = "It is an amulet of protection" +Flags = {Take,Armor} +Attributes = {Weight=600,SlotType=NECKLACE,ArmorValue=2} + +TypeID = 3056 +Name = "a bronze amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbManaDrain=15,ExpireTarget=0,TotalUses=200} + +TypeID = 3057 +Name = "an amulet of loss" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3058 +Name = "a strange symbol" +Flags = {MultiUse,Take} +Attributes = {Weight=200,Brightness=2,LightColor=215} + +TypeID = 3059 +Name = "a spellbook" +Flags = {Text,Take} +Attributes = {Weight=5800} + +TypeID = 3060 +Name = "an orb" +Flags = {Take} +Attributes = {Weight=800,Brightness=2,LightColor=26} + +TypeID = 3061 +Name = "a life crystal" +Flags = {Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 3062 +Name = "a mind stone" +Flags = {MultiUse,Take} +Attributes = {Weight=250} + +TypeID = 3063 +Name = "a gold ring" +Flags = {Take} +Attributes = {Weight=100,SlotType=RING} + +TypeID = 3064 +Name = "the orb of nature" +Flags = {Unmove} + +TypeID = 3065 +Name = "a quagmire rod" +Description = "It emits clouds of poisonous swamp gas" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2650,Brightness=2,LightColor=67,Vocations=2,Range=3,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Poison,MissileEffect=15} + +TypeID = 3066 +Name = "a snakebite rod" +Description = "It seems to twitch and quiver as if trying to escape your grip. The rod has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=4300,Vocations=2,Range=3,AttackStrength=13,AttackVariation=5,DamageType=Poison,MissileEffect=15} + +TypeID = 3067 +Name = "a tempest rod" +Description = "It grants you the power of striking your foes with furious thunderstorms" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=2100,Brightness=3,LightColor=29,Vocations=2,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3068 +Name = "a crystal wand" +Flags = {Take} +Attributes = {Weight=2800} + +TypeID = 3069 +Name = "a volcanic rod" +Description = "It erupts powerful bursts of magma upon everything in your path" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2900,Brightness=2,LightColor=199,Vocations=2,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3070 +Name = "a moonlight rod" +Description = "Shimmering rays of moonlight radiate from its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=1950,Brightness=3,LightColor=143,Vocations=2,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3071 +Name = "a wand of inferno" +Description = "It unleashes the very fires of hell" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=3050,Brightness=3,LightColor=205,Vocations=1,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3072 +Name = "a wand of plague" +Description = "Infectious goo covers its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2300,Brightness=2,LightColor=67,Vocations=1,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Poison,Range=3,MissileEffect=15} + +TypeID = 3073 +Name = "a wand of cosmic energy" +Description = "The energy of a radiant star is trapped inside its globe" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2300,Brightness=2,LightColor=205,Vocations=1,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3074 +Name = "a wand of vortex" +Description = "Surges of energy rush through the tip of this wand. The wand has magical powers inside and requires no mana consumption" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=2300,Brightness=2,LightColor=23,Vocations=1,AttackStrength=13,AttackVariation=5,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3075 +Name = "a wand of dragonbreath" +Description = "Legends say that this wand holds the soul of a young dragon" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=2300,Brightness=2,LightColor=192,Vocations=1,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3076 +Name = "a crystal ball" +Flags = {Take} +Attributes = {Weight=3400} + +TypeID = 3077 +Name = "an ankh" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3078 +Name = "a mysterious fetish" +Flags = {MultiUse,Take} +Attributes = {Weight=490} + +TypeID = 3079 +Name = "boots of haste" +Flags = {Take} +Attributes = {Weight=750,SlotType=FEET,SpeedBoost=20} + +TypeID = 3080 +Name = "a broken amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3081 +Name = "a stone skin amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=700,SlotType=NECKLACE,AbsorbPhysical=80,ExpireTarget=0,TotalUses=5} + +TypeID = 3082 +Name = "an elven amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=270,SlotType=NECKLACE,AbsorbPhysical=10,AbsorbMagic=10,ExpireTarget=0,TotalUses=50} + +TypeID = 3083 +Name = "a garlic necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=380,SlotType=NECKLACE,AbsorbLifeDrain=20,ExpireTarget=0,TotalUses=150} + +TypeID = 3084 +Name = "a protection amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=550,SlotType=NECKLACE,AbsorbPhysical=6,ExpireTarget=0,TotalUses=250} + +TypeID = 3085 +Name = "a dragon necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=630,SlotType=NECKLACE,AbsorbFire=8,ExpireTarget=0,TotalUses=200} + +TypeID = 3086 +Name = "a stealth ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=100,SlotType=RING,Invisible=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3049} + +TypeID = 3087 +Name = "a power ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,FistBoost=6,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3050} + +TypeID = 3088 +Name = "an energy ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ManaShield=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3051} + +TypeID = 3089 +Name = "a life ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=3000,HealthGain=1,ManaTicks=3000,ManaGain=1,ExpireTarget=0,TotalExpireTime=1200,DeEquipTarget=3052} + +TypeID = 3090 +Name = "a time ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SpeedBoost=30,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3053} + +TypeID = 3091 +Name = "a sword ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3094} + +TypeID = 3092 +Name = "an axe ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3095} + +TypeID = 3093 +Name = "a club ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3096} + +TypeID = 3094 +Name = "a sword ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SwordBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3091} + +TypeID = 3095 +Name = "an axe ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,AxeBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3092} + +TypeID = 3096 +Name = "a club ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,ClubBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3093} + +TypeID = 3097 +Name = "a dwarven ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=110,SlotType=RING,EquipTarget=3099} + +TypeID = 3098 +Name = "a ring of healing" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3100} + +TypeID = 3099 +Name = "a dwarven ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=110,SlotType=RING,SuppressDrunk=1,ExpireTarget=0,TotalExpireTime=3600,DeEquipTarget=3097} + +TypeID = 3100 +Name = "a ring of healing" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=2000,HealthGain=1,ManaTicks=2000,ManaGain=4,ExpireTarget=0,TotalExpireTime=450,DeEquipTarget=3098} + +TypeID = 3101 +Name = "a screaming spellbook" +Description = "To humble, or not to humble, that is the question" +Flags = {Unmove,Unlay,Unthrow,Unpass,UseEvent} +Attributes = {Weight=5800} + +TypeID = 3102 +Name = "a paw amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3103 +Name = "a cornucopia" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 3104 +Name = "a banana skin" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3105 +Name = "a dirty fur" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3106 +Name = "an old twig" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3107 +Name = "some wood" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3108 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3109 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3110 +Name = "a piece of iron" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 3111 +Name = "a fishbone" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3112 +Name = "rotten meat" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3113 +Name = "broken pottery" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3114 +Name = "a skull" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3115 +Name = "a bone" +Flags = {Take} +Attributes = {Weight=950} + +TypeID = 3116 +Name = "a big bone" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 3117 +Name = "broken brown glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3118 +Name = "broken green glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3119 +Name = "a broken sword" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 3120 +Name = "a moldy cheese" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 3121 +Name = "a torn book" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 3122 +Name = "a dirty cape" +Flags = {Take} +Attributes = {Weight=2950} + +TypeID = 3123 +Name = "worn leather boots" +Flags = {Take} +Attributes = {Weight=900} + +TypeID = 3124 +Name = "a burnt scroll" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3125 +Name = "remains of a fish" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3126 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3127 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3128 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3129 +Name = "some leaves" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3130 +Name = "twigs" +Flags = {Take} +Attributes = {Weight=210} + +TypeID = 3131 +Name = "burnt down firewood" +Flags = {Take} +Attributes = {Weight=420} + +TypeID = 3132 +Name = "an animal skull" +Flags = {Unmove} + +TypeID = 3133 +Name = "humanoid remains" +Flags = {Unmove} + +TypeID = 3134 +Name = "ashes" +Flags = {Unmove} + +TypeID = 3135 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3136 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3137 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3138 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3139 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3140 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3141 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3142 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3143 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3144 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3145 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2296,TotalExpireTime=120} + +TypeID = 3146 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2295,TotalExpireTime=120} + +TypeID = 3147 +Name = "a blank rune" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3148 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3149 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3150 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3151 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3152 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3153 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3154 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3155 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3156 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3157 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3158 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3159 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3160 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3161 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3162 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3163 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3164 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3165 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3166 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3167 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3168 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3169 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3170 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3171 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3172 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3173 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3174 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3175 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3176 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3177 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3178 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3179 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3180 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3181 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3182 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3183 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3184 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3185 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3186 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3187 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3188 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3189 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3190 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3191 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3192 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3193 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3194 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3195 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3196 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3197 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3198 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3199 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3200 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3201 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3202 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3203 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3204 +Name = "your own dead body" +Flags = {Unmove} + +TypeID = 3205 +Name = "a family brooch" +Description = "You see the familyname Windtrouser engraved on this brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3206 +Name = "a dragonfetish" +Flags = {Take} +Attributes = {Weight=490} + +TypeID = 3207 +Name = "the skull of Ratha" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3208 +Name = "a giant smithhammer" +Description = "This cyclopean hammer seems to be an awesome smithing tool" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3209 +Name = "a voodoodoll" +Description = "This voodoodoll looks like a little king" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3210 +Name = "a hat of the mad" +Description = "You have a vague feeling that it looks somewhat silly" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3211 +Name = "a witchesbroom" +Description = "Don't use it without flying license. Not suitable for minors" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3212 +Name = "a monks diary" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 3213 +Name = "an annihilation bear" +Description = "I braved the Annihilator and all I got is this lousy teddy bear" +Flags = {Take} +Attributes = {Weight=4300} + +TypeID = 3214 +Name = "a blessed ankh" +Description = "You see the engraving of a white raven on its surface" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3215 +Name = "a phoenix egg" +Description = "It seems to be burning from inside" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3216 +Name = "a bill" +Description = "This is a bill for an expensive magicians hat and several rabbits" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3217 +Name = "a letterbag" +Description = "This bag is nearly bursting from all the letters inside" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=50000,SlotType=BACKPACK} + +TypeID = 3218 +Name = "a present" +Flags = {UseEvent,Take} +Attributes = {Weight=1200} + +TypeID = 3219 +Name = "Waldos Posthorn" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 3220 +Name = "a letter to Markwin" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3221 +Name = "Santa's Mailbox" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3222 +Name = "a helmet ornament" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=160} + +TypeID = 3223 +Name = "a gem holder" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3224 +Name = "a right horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3225 +Name = "a left horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3226 +Name = "a damaged helmet" +Description = "This item seems to have several parts missing" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=HEAD,ArmorValue=5} + +TypeID = 3227 +Name = "a helmet piece" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=260} + +TypeID = 3228 +Name = "a helmet adornement" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3229 +Name = "a helmet of the ancients" +Description = "The gem of the helmet is burned out and should be replaced" +Flags = {MultiUse,UseEvent,Take,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ArmorValue=8} + +TypeID = 3230 +Name = "a helmet of the ancients" +Description = "The gem is glowing with power" +Flags = {Take,Expire,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ExpireTarget=3229,TotalExpireTime=1800,ArmorValue=11} + +TypeID = 3231 +Name = "a gemmed lamp" +Description = "It is Fa'hradin's enchanted lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3232 +Name = "a spyreport" +Description = "The report is written in some coded language" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3233 +Name = "a tear of daraman" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3234 +Name = "a cookbook" +Description = "It contains several exotic recipes" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 3235 +Name = "an ancient rune" +Description = "This rune vibrates with ancient powers. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=300,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3236 +Name = "blue note" +Description = "The blue crystal is softly humming a ghostly melody. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=250,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3237 +Name = "a sword hilt" +Description = "This was once part of a formidable two handed weapon. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3238 +Name = "a cobrafang dagger" +Description = "This ritual weapon was forged from the sharp fang of a giant cobra. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3239 +Name = "a crystal arrow" +Description = "This arrow seems not suitable for the use with ordinary bows. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=100,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3240 +Name = "a burning heart" +Description = "The burning heart is still beating with unholy life. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=400,Brightness=1,LightColor=193,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3241 +Name = "an ornamented ankh" +Description = "This ancient relic shows signs of untold age. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3242 +Name = "a stuffed bunny" +Flags = {Take} +Attributes = {Weight=350} + +TypeID = 3243 +Name = "a gemmed lamp" +Description = "It is the djinn leader's sleeping lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3244 +Name = "an old and used backpack" +Description = "A label on the backpack reads: Property of Sam, Thais" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 3245 +Name = "a ring of wishes" +Description = "(This item has 3 charges left)" +Flags = {Take} +Attributes = {Weight=50,SlotType=RING} + +TypeID = 3246 +Name = "boots of waterwalking" +Description = "(This item has 5 charges left)" +Flags = {Take} +Attributes = {Weight=770,SlotType=FEET} + +TypeID = 3247 +Name = "a djinn's lamp" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 3248 +Name = "a portable hole" +Description = "(This item has 1 charge left)" +Flags = {Unmove} + +TypeID = 3249 +Name = "frozen starlight" +Flags = {Take} +Attributes = {Weight=20,Brightness=6,LightColor=29} + +TypeID = 3250 +Name = "the carrot of doom" +Description = "You can sense the evil power of the carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3251 +Name = "a blood orb" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3252 +Name = "the horn of postman" +Description = "The magical horn will grant you the trustworthy postman rank" +Flags = {Take} +Attributes = {Weight=2300} + +TypeID = 3253 +Name = "a backpack of holding" +Flags = {Container,Take} +Attributes = {Capacity=24,Weight=1500,SlotType=BACKPACK} + +TypeID = 3254 +Name = "a roc feather" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3255 +Name = "a drum" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3256 +Name = "a trumpet" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3257 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3258 +Name = "a mandolin" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3259 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3260 +Name = "a lyre" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3261 +Name = "a panpipe" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3262 +Name = "a flute" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3263 +Name = "a gemmed lamp" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3264 +Name = "a sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=14,Defense=12} + +TypeID = 3265 +Name = "a two handed sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=30,Defense=25} + +TypeID = 3266 +Name = "a battle axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,SlotType=TWOHANDED,WeaponType=AXE,Attack=25,Defense=10} + +TypeID = 3267 +Name = "a dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=950,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3268 +Name = "a hand axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1800,WeaponType=AXE,Attack=10,Defense=5} + +TypeID = 3269 +Name = "a halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=14} + +TypeID = 3270 +Name = "a club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=CLUB,Attack=7,Defense=7} + +TypeID = 3271 +Name = "a spike sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=SWORD,Attack=24,Defense=21} + +TypeID = 3272 +Name = "a rapier" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,WeaponType=SWORD,Attack=10,Defense=8} + +TypeID = 3273 +Name = "a sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 3274 +Name = "an axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=AXE,Attack=12,Defense=6} + +TypeID = 3275 +Name = "a double axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3276 +Name = "a hatchet" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=AXE,Attack=15,Defense=8} + +TypeID = 3277 +Name = "a spear" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=2000,Range=7,Attack=25,Defense=0,MissileEffect=1} + +TypeID = 3278 +Name = "a magic longsword" +Description = "It's the magic Cyclopmania Sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4300,SlotType=TWOHANDED,WeaponType=SWORD,Attack=55,Defense=40} + +TypeID = 3279 +Name = "a war hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8500,SlotType=TWOHANDED,WeaponType=CLUB,Attack=45,Defense=10} + +TypeID = 3280 +Name = "a fire sword" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2300,Brightness=3,LightColor=199,WeaponType=SWORD,Attack=35,Defense=20} + +TypeID = 3281 +Name = "a giant sword" +Description = "This sword has been forged by ancient giants" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=18000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=46,Defense=22} + +TypeID = 3282 +Name = "a morning star" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5400,WeaponType=CLUB,Attack=25,Defense=11} + +TypeID = 3283 +Name = "a carlin sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=SWORD,Attack=15,Defense=13} + +TypeID = 3284 +Name = "an ice rapier" +Description = "A deadly but fragile weapon" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,ExpireTarget=0,TotalUses=1,WeaponType=SWORD,Attack=100,Defense=1} + +TypeID = 3285 +Name = "a longsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=17,Defense=14} + +TypeID = 3286 +Name = "a mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=16,Defense=11} + +TypeID = 3287 +Name = "a throwing star" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=200,Range=7,Attack=35,Defense=0,MissileEffect=8,Fragility=10} + +TypeID = 3288 +Name = "a magic sword" +Description = "It's the Sword of Valor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=48,Defense=35} + +TypeID = 3289 +Name = "a staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,WeaponType=CLUB,Attack=10,Defense=25} + +TypeID = 3290 +Name = "a silver dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1020,WeaponType=SWORD,Attack=8,Defense=7} + +TypeID = 3291 +Name = "a knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=420,WeaponType=SWORD,Attack=7,Defense=5} + +TypeID = 3292 +Name = "a combat knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=870,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3293 +Name = "a sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1050,WeaponType=AXE,Attack=7,Defense=4} + +TypeID = 3294 +Name = "a short sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=11,Defense=11} + +TypeID = 3295 +Name = "a bright sword" +Description = "The blade shimmers in light blue" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,Brightness=2,LightColor=143,WeaponType=SWORD,Attack=36,Defense=30} + +TypeID = 3296 +Name = "a warlord sword" +Description = "Strong powers flow in this magic sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=53,Defense=38} + +TypeID = 3297 +Name = "a serpent sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=SWORD,Attack=26,Defense=15} + +TypeID = 3298 +Name = "a throwing knife" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=500,Range=7,Attack=25,Defense=0,MissileEffect=9,Fragility=7} + +TypeID = 3299 +Name = "a poison dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=880,WeaponType=SWORD,Attack=18,Defense=8} + +TypeID = 3300 +Name = "a katana" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3100,WeaponType=SWORD,Attack=16,Defense=12} + +TypeID = 3301 +Name = "a broadsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=SWORD,Attack=26,Defense=23} + +TypeID = 3302 +Name = "a dragon lance" +Description = "The extraordinary sharp blade penetrates every armor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,SlotType=TWOHANDED,WeaponType=AXE,Attack=47,Defense=16} + +TypeID = 3303 +Name = "a great axe" +Description = "A masterpiece of a dwarven smith" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=52,Defense=22} + +TypeID = 3304 +Name = "a crowbar" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=2100,WeaponType=CLUB,Attack=6,Defense=6} + +TypeID = 3305 +Name = "a battle hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3306 +Name = "a golden sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1950,WeaponType=AXE,Attack=13,Defense=6} + +TypeID = 3307 +Name = "a scimitar" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=19,Defense=13} + +TypeID = 3308 +Name = "a machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1650,WeaponType=SWORD,Attack=12,Defense=9} + +TypeID = 3309 +Name = "a thunder hammer" +Description = "It is blessed by the gods of Tibia" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=12500,WeaponType=CLUB,Attack=49,Defense=35} + +TypeID = 3310 +Name = "an iron hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6600,WeaponType=CLUB,Attack=18,Defense=10} + +TypeID = 3311 +Name = "a clerical mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,WeaponType=CLUB,Attack=28,Defense=15} + +TypeID = 3312 +Name = "a silver mace" +Description = "You feel an aura of protection" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3313 +Name = "an obsidian lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=34,Defense=10} + +TypeID = 3314 +Name = "a naginata" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7800,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=25} + +TypeID = 3315 +Name = "a guardian halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=AXE,Attack=46,Defense=15} + +TypeID = 3316 +Name = "an orcish axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4500,WeaponType=AXE,Attack=23,Defense=12} + +TypeID = 3317 +Name = "a barbarian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5100,WeaponType=AXE,Attack=28,Defense=18} + +TypeID = 3318 +Name = "a knight axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5900,WeaponType=AXE,Attack=33,Defense=21} + +TypeID = 3319 +Name = "a stonecutter axe" +Description = "You feel the power of this mighty axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9900,WeaponType=AXE,Attack=50,Defense=30} + +TypeID = 3320 +Name = "a fire axe" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,Brightness=3,LightColor=199,WeaponType=AXE,Attack=38,Defense=16} + +TypeID = 3321 +Name = "an enchanted staff" +Description = "Temporal magic powers enchant this staff" +Flags = {MultiUse,Take,Expire,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,ExpireTarget=3289,TotalExpireTime=60,WeaponType=CLUB,Attack=39,Defense=45} + +TypeID = 3322 +Name = "a dragon hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9700,WeaponType=CLUB,Attack=32,Defense=20} + +TypeID = 3323 +Name = "a dwarven axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8200,WeaponType=AXE,Attack=31,Defense=19} + +TypeID = 3324 +Name = "a skull staff" +Description = "The staff longs for death" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1700,Brightness=2,LightColor=180,WeaponType=CLUB,Attack=36,Defense=12} + +TypeID = 3325 +Name = "a light mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=14,Defense=9} + +TypeID = 3326 +Name = "a foil" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1450,WeaponType=SWORD,Attack=9,Defense=11} + +TypeID = 3327 +Name = "a daramanian mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=21,Defense=12} + +TypeID = 3328 +Name = "a daramanian waraxe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=15} + +TypeID = 3329 +Name = "a daramanian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=AXE,Attack=16,Defense=8} + +TypeID = 3330 +Name = "a heavy machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1840,WeaponType=SWORD,Attack=16,Defense=10} + +TypeID = 3331 +Name = "a ravager's axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=14} + +TypeID = 3332 +Name = "a hammer of wrath" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=48,Defense=12} + +TypeID = 3333 +Name = "a crystal mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,WeaponType=CLUB,Attack=38,Defense=16} + +TypeID = 3334 +Name = "a pharaoh sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=15000,WeaponType=SWORD,Attack=41,Defense=23} + +TypeID = 3335 +Name = "a twin axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=AXE,Attack=45,Defense=24} + +TypeID = 3336 +Name = "a studded club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=CLUB,Attack=9,Defense=8} + +TypeID = 3337 +Name = "a bone club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3900,WeaponType=CLUB,Attack=12,Defense=8} + +TypeID = 3338 +Name = "a bone sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1900,WeaponType=SWORD,Attack=14,Defense=10} + +TypeID = 3339 +Name = "a djinn blade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2450,WeaponType=SWORD,Attack=38,Defense=22} + +TypeID = 3340 +Name = "a heavy mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=50,Defense=15} + +TypeID = 3341 +Name = "an arcane staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=CLUB,Attack=50,Defense=30} + +TypeID = 3342 +Name = "a war axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=20,Defense=10} + +TypeID = 3343 +Name = "a lich staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3344 +Name = "a beastslayer axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3345 +Name = "a templar scytheblade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=23,Defense=15} + +TypeID = 3346 +Name = "a ripper lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=28,Defense=7} + +TypeID = 3347 +Name = "a hunting spear" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=18,Defense=8} + +TypeID = 3348 +Name = "a banana staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=CLUB,Attack=25,Defense=15} + +TypeID = 3349 +Name = "a crossbow" +Flags = {Take,Distance} +Attributes = {Weight=4000,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 3350 +Name = "a bow" +Flags = {Take,Distance} +Attributes = {Weight=3100,SlotType=TWOHANDED,Range=7,AmmoType=ARROW} + +TypeID = 3351 +Name = "a steel helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3352 +Name = "a chain helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3353 +Name = "an iron helmet" +Flags = {Take,Armor} +Attributes = {Weight=3000,SlotType=HEAD,ArmorValue=5} + +TypeID = 3354 +Name = "a brass helmet" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3355 +Name = "a leather helmet" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=1} + +TypeID = 3356 +Name = "a devil helmet" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=HEAD,ArmorValue=7} + +TypeID = 3357 +Name = "a plate armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3358 +Name = "a chain armor" +Flags = {Take,Armor} +Attributes = {Weight=10000,SlotType=BODY,ArmorValue=6} + +TypeID = 3359 +Name = "a brass armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=8} + +TypeID = 3360 +Name = "a golden armor" +Description = "It's an enchanted armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=14} + +TypeID = 3361 +Name = "a leather armor" +Flags = {Take,Armor} +Attributes = {Weight=6000,SlotType=BODY,ArmorValue=4} + +TypeID = 3362 +Name = "studded legs" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=LEGS,ArmorValue=2} + +TypeID = 3363 +Name = "dragon scale legs" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=LEGS,ArmorValue=10} + +TypeID = 3364 +Name = "golden legs" +Flags = {Take,Armor} +Attributes = {Weight=5600,SlotType=LEGS,ArmorValue=9} + +TypeID = 3365 +Name = "a golden helmet" +Description = "It's the famous Helmet of the Stars" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=12} + +TypeID = 3366 +Name = "a magic plate armor" +Description = "An enchanted gem glows on the plate armor" +Flags = {Take,Armor} +Attributes = {Weight=8500,SlotType=BODY,ArmorValue=17} + +TypeID = 3367 +Name = "a viking helmet" +Flags = {Take,Armor} +Attributes = {Weight=3900,SlotType=HEAD,ArmorValue=4} + +TypeID = 3368 +Name = "a winged helmet" +Description = "It's the Helmet of Hermes" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=HEAD,ArmorValue=10} + +TypeID = 3369 +Name = "a warrior helmet" +Flags = {Take,Armor} +Attributes = {Weight=6800,SlotType=HEAD,ArmorValue=8} + +TypeID = 3370 +Name = "a knight armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=12} + +TypeID = 3371 +Name = "knight legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=8} + +TypeID = 3372 +Name = "brass legs" +Flags = {Take,Armor} +Attributes = {Weight=3800,SlotType=LEGS,ArmorValue=5} + +TypeID = 3373 +Name = "a strange helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3374 +Name = "a legion helmet" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=HEAD,ArmorValue=4} + +TypeID = 3375 +Name = "a soldier helmet" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=5} + +TypeID = 3376 +Name = "a studded helmet" +Flags = {Take,Armor} +Attributes = {Weight=2450,SlotType=HEAD,ArmorValue=2} + +TypeID = 3377 +Name = "a scale armor" +Flags = {Take,Armor} +Attributes = {Weight=10500,SlotType=BODY,ArmorValue=9} + +TypeID = 3378 +Name = "a studded armor" +Flags = {Take,Armor} +Attributes = {Weight=7100,SlotType=BODY,ArmorValue=5} + +TypeID = 3379 +Name = "a doublet" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=BODY,ArmorValue=2} + +TypeID = 3380 +Name = "a noble armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=11} + +TypeID = 3381 +Name = "a crown armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3382 +Name = "crown legs" +Flags = {Take,Armor} +Attributes = {Weight=6500,SlotType=LEGS,ArmorValue=8} + +TypeID = 3383 +Name = "a dark armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3384 +Name = "a dark helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3385 +Name = "a crown helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3386 +Name = "a dragon scale mail" +Flags = {Take,Armor} +Attributes = {Weight=11400,SlotType=BODY,ArmorValue=15} + +TypeID = 3387 +Name = "a demon helmet" +Description = "You hear an evil whispering from inside" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=10} + +TypeID = 3388 +Name = "a demon armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=16} + +TypeID = 3389 +Name = "demon legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=9} + +TypeID = 3390 +Name = "a horned helmet" +Flags = {Take,Armor} +Attributes = {Weight=5100,SlotType=HEAD,ArmorValue=11} + +TypeID = 3391 +Name = "a crusader helmet" +Flags = {Take,Armor} +Attributes = {Weight=5200,SlotType=HEAD,ArmorValue=8} + +TypeID = 3392 +Name = "a royal helmet" +Description = "An excellent masterpiece of a smith" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=HEAD,ArmorValue=9} + +TypeID = 3393 +Name = "an amazon helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3394 +Name = "an amazon armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3395 +Name = "a ceremonial mask" +Flags = {Take,Armor} +Attributes = {Weight=4000,SlotType=HEAD,Brightness=3,LightColor=215,ArmorValue=9} + +TypeID = 3396 +Name = "a dwarfen helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3397 +Name = "a dwarven armor" +Flags = {Take,Armor} +Attributes = {Weight=13000,SlotType=BODY,ArmorValue=10} + +TypeID = 3398 +Name = "dwarfen legs" +Flags = {Take,Armor} +Attributes = {Weight=4900,SlotType=LEGS,ArmorValue=6} + +TypeID = 3399 +Name = "an elven mail" +Flags = {Take,Armor} +Attributes = {Weight=9000,SlotType=BODY,ArmorValue=9} + +TypeID = 3400 +Name = "a dragon scale helmet" +Flags = {Take,Armor} +Attributes = {Weight=3250,SlotType=HEAD,ArmorValue=9} + +TypeID = 3401 +Name = "elven legs" +Flags = {Take,Armor} +Attributes = {Weight=3300,SlotType=LEGS,ArmorValue=4} + +TypeID = 3402 +Name = "a native armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=7} + +TypeID = 3403 +Name = "a tribal mask" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=HEAD,ArmorValue=2} + +TypeID = 3404 +Name = "a leopard armor" +Flags = {Take,Armor} +Attributes = {Weight=9500,SlotType=BODY,ArmorValue=9} + +TypeID = 3405 +Name = "a horseman helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3406 +Name = "a feather headdress" +Flags = {Take,Armor} +Attributes = {Weight=2100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3407 +Name = "a charmer's tiara" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3408 +Name = "a beholder helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=7} + +TypeID = 3409 +Name = "a steel shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=21} + +TypeID = 3410 +Name = "a plate shield" +Flags = {Take,Shield} +Attributes = {Weight=6500,Defense=17} + +TypeID = 3411 +Name = "a brass shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=16} + +TypeID = 3412 +Name = "a wooden shield" +Flags = {Take,Shield} +Attributes = {Weight=4000,Defense=14} + +TypeID = 3413 +Name = "a battle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=23} + +TypeID = 3414 +Name = "a mastermind shield" +Description = "It's an enchanted shield" +Flags = {Take,Shield} +Attributes = {Weight=5700,Defense=37} + +TypeID = 3415 +Name = "a guardian shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=30} + +TypeID = 3416 +Name = "a dragon shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=31} + +TypeID = 3417 +Name = "a shield of honour" +Description = "A mighty shield warded by the gods of Tibia" +Flags = {Take,Shield} +Attributes = {Weight=5400,Defense=33} + +TypeID = 3418 +Name = "a beholder shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=28} + +TypeID = 3419 +Name = "a crown shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3420 +Name = "a demon shield" +Description = "This powerful shield seems to be as light as air" +Flags = {Take,Shield} +Attributes = {Weight=2600,Defense=35} + +TypeID = 3421 +Name = "a dark shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=25} + +TypeID = 3422 +Name = "a great shield" +Description = "The shield is made of dragon scales" +Flags = {Take,Shield} +Attributes = {Weight=8400,Defense=38} + +TypeID = 3423 +Name = "a blessed shield" +Description = "The shield grants divine protection" +Flags = {Take,Shield} +Attributes = {Weight=6800,Defense=40} + +TypeID = 3424 +Name = "an ornamented shield" +Description = "Many gems sparkle on the shield" +Flags = {Take,Shield} +Attributes = {Weight=6700,Defense=22} + +TypeID = 3425 +Name = "a dwarven shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=26} + +TypeID = 3426 +Name = "a studded shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=15} + +TypeID = 3427 +Name = "a rose shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=27} + +TypeID = 3428 +Name = "a tower shield" +Flags = {Take,Shield} +Attributes = {Weight=8200,Defense=32} + +TypeID = 3429 +Name = "a black shield" +Description = "An unholy creature covers the shield" +Flags = {Take,Shield} +Attributes = {Weight=4200,Defense=18} + +TypeID = 3430 +Name = "a copper shield" +Flags = {Take,Shield} +Attributes = {Weight=6300,Defense=19} + +TypeID = 3431 +Name = "a viking shield" +Flags = {Take,Shield} +Attributes = {Weight=6600,Defense=22} + +TypeID = 3432 +Name = "an ancient shield" +Flags = {Take,Shield} +Attributes = {Weight=6100,Defense=27} + +TypeID = 3433 +Name = "a griffin shield" +Flags = {Take,Shield} +Attributes = {Weight=5000,Defense=29} + +TypeID = 3434 +Name = "a vampire shield" +Description = "Dark powers enchant this shield" +Flags = {Take,Shield} +Attributes = {Weight=3800,Defense=34} + +TypeID = 3435 +Name = "a castle shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=28} + +TypeID = 3436 +Name = "a medusa shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=33} + +TypeID = 3437 +Name = "an amazon shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3438 +Name = "an eagle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3439 +Name = "a phoenix shield" +Description = "This shield feels warm to the touch" +Flags = {Take,Shield} +Attributes = {Weight=3500,Defense=34} + +TypeID = 3440 +Name = "a scarab shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=25} + +TypeID = 3441 +Name = "a bone shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=20} + +TypeID = 3442 +Name = "a tempest shield" +Flags = {Take,Shield} +Attributes = {Weight=5100,Defense=36} + +TypeID = 3443 +Name = "a tusk shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=27} + +TypeID = 3444 +Name = "a sentinel shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=22} + +TypeID = 3445 +Name = "a salamander shield" +Flags = {Take,Shield} +Attributes = {Weight=5900,Defense=26} + +TypeID = 3446 +Name = "a bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=BOLT,Attack=30,MissileEffect=2,Fragility=100} + +TypeID = 3447 +Name = "an arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=70,AmmoType=ARROW,Attack=25,MissileEffect=3,Fragility=100} + +TypeID = 3448 +Name = "a poison arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=ARROW,Attack=10,MissileEffect=6,Fragility=100,WeaponSpecialEffect=1,AttackStrength=50} + +TypeID = 3449 +Name = "a burst arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=ARROW,Attack=0,MissileEffect=7,Fragility=100,WeaponSpecialEffect=2,AttackStrength=30} + +TypeID = 3450 +Name = "a power bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=BOLT,Attack=40,MissileEffect=14,Fragility=100} + +TypeID = 3451 +Name = "a pitchfork" +Flags = {MultiUse,Take} +Attributes = {Weight=2500} + +TypeID = 3452 +Name = "a rake" +Flags = {MultiUse,Take} +Attributes = {Weight=1500} + +TypeID = 3453 +Name = "a scythe" +Flags = {UseEvent,MultiUse,Take,Weapon} +Attributes = {Weight=3000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=8,Defense=3} + +TypeID = 3454 +Name = "a broom" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3455 +Name = "a hoe" +Flags = {MultiUse,Take} +Attributes = {Weight=2800} + +TypeID = 3456 +Name = "a pick" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=4500} + +TypeID = 3457 +Name = "a shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3500} + +TypeID = 3458 +Name = "an anvil" +Flags = {Unpass,Unmove,Height} + +TypeID = 3459 +Name = "a wooden hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 3460 +Name = "a hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=1150} + +TypeID = 3461 +Name = "a saw" +Flags = {MultiUse,Take} +Attributes = {Weight=1000} + +TypeID = 3462 +Name = "a small axe" +Flags = {MultiUse,Take} +Attributes = {Weight=2000} + +TypeID = 3463 +Name = "a mirror" +Flags = {MultiUse,Take} +Attributes = {Weight=950} + +TypeID = 3464 +Name = "a baking tray" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 3465 +Name = "a pot" +Flags = {MultiUse,FluidContainer,Unpass,Take,Destroy,Height} +Attributes = {Weight=5250,DestroyTarget=3142} + +TypeID = 3466 +Name = "a pan" +Flags = {Take} +Attributes = {Weight=1800} + +TypeID = 3467 +Name = "a fork" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3468 +Name = "a spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3469 +Name = "a knife" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 3470 +Name = "a wooden spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3471 +Name = "a cleaver" +Flags = {Take} +Attributes = {Weight=660} + +TypeID = 3472 +Name = "an oven spatula" +Flags = {Take} +Attributes = {Weight=1400} + +TypeID = 3473 +Name = "a rolling pin" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3474 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3475 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3476 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3477 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3478 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3479 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3480 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3481 +Name = "a closed trap" +Flags = {UseEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3482 +Name = "a trap" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3483 +Name = "a fishing rod" +Flags = {MultiUse,DistUse,UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 3484 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3487,DestroyTarget=3137} + +TypeID = 3485 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3486,DestroyTarget=3137} + +TypeID = 3486 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3484,DestroyTarget=3137} + +TypeID = 3487 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3485,DestroyTarget=3137} + +TypeID = 3488 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3491} + +TypeID = 3489 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3490} + +TypeID = 3490 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3488} + +TypeID = 3491 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3489} + +TypeID = 3492 +Name = "a worm" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3493 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3494 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3495 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3496 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3497 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3498 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3499 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3500 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3501 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3502 +Name = "a depot chest" +Flags = {Container,Unmove} +Attributes = {Capacity=30} + +TypeID = 3503 +Name = "a parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3504 +Name = "a stamped parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3505 +Name = "a letter" +Flags = {Text,Write,Take} +Attributes = {MaxLength=2000,Weight=50} + +TypeID = 3506 +Name = "a stamped letter" +Flags = {Text,Take} +Attributes = {Weight=50} + +TypeID = 3507 +Name = "a label" +Flags = {Text,Write,Take} +Attributes = {MaxLength=80,Weight=10} + +TypeID = 3508 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3509 +Name = "an inkwell" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3510 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=207} + +TypeID = 3511 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3512 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3513 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3514 +Name = "an empty coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=0,LightColor=215} + +TypeID = 3515 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3516 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3517 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3518 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3519 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3520 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3521 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3522 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3523 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3524 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3525 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3526 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3527 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3528 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3529 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3530 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3531 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3532 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3533 +Name = "a black token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3534 +Name = "a white token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3535 +Name = "a white pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3536 +Name = "a white castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3537 +Name = "a white knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3538 +Name = "a white bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3539 +Name = "the white queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3540 +Name = "the white king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3541 +Name = "a black pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3542 +Name = "a black castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3543 +Name = "a black knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3544 +Name = "a black bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3545 +Name = "the black queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3546 +Name = "the black king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3547 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3548 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3549 +Name = "soft boots" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=800,DeEquipTarget=6529,SlotType=FEET,ExpireTarget=6530,TotalExpireTime=14400,HealthGain=1,HealthTicks=2000,ManaGain=1,ManaTicks=2000} + +TypeID = 3550 +Name = "patched boots" +Flags = {Take,Armor} +Attributes = {Weight=1000,SlotType=FEET,ArmorValue=2} + +TypeID = 3551 +Name = "sandals" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET,SpeedBoost=5} + +TypeID = 3552 +Name = "leather boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3553 +Name = "bunnyslippers" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3554 +Name = "steel boots" +Flags = {Take,Armor} +Attributes = {Weight=2900,SlotType=FEET,ArmorValue=3} + +TypeID = 3555 +Name = "golden boots" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=FEET,ArmorValue=4} + +TypeID = 3556 +Name = "crocodile boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3557 +Name = "plate legs" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=LEGS,ArmorValue=7} + +TypeID = 3558 +Name = "chain legs" +Flags = {Take,Armor} +Attributes = {Weight=3500,SlotType=LEGS,ArmorValue=3} + +TypeID = 3559 +Name = "leather legs" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=LEGS,ArmorValue=1} + +TypeID = 3560 +Name = "a bast skirt" +Flags = {Take} +Attributes = {Weight=350,SlotType=LEGS} + +TypeID = 3561 +Name = "a jacket" +Flags = {Take,Armor} +Attributes = {Weight=2400,SlotType=BODY,ArmorValue=1} + +TypeID = 3562 +Name = "a coat" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=BODY,ArmorValue=1} + +TypeID = 3563 +Name = "a green tunic" +Flags = {Take,Armor} +Attributes = {Weight=930,SlotType=BODY,ArmorValue=1} + +TypeID = 3564 +Name = "a red tunic" +Flags = {Take,Armor} +Attributes = {Weight=1400,SlotType=BODY,ArmorValue=2} + +TypeID = 3565 +Name = "a cape" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3566 +Name = "a red robe" +Description = "The robe is artfully embroidered" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=BODY,ArmorValue=1} + +TypeID = 3567 +Name = "a blue robe" +Description = "It is a magic robe" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=BODY,ArmorValue=11} + +TypeID = 3568 +Name = "a simple dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3569 +Name = "a white dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3570 +Name = "a ball gown" +Flags = {Take} +Attributes = {Weight=2500,SlotType=BODY} + +TypeID = 3571 +Name = "a rangers cloak" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3572 +Name = "a scarf" +Flags = {Take,Armor} +Attributes = {Weight=200,SlotType=NECKLACE,ArmorValue=1} + +TypeID = 3573 +Name = "a magician hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 3574 +Name = "a mystic turban" +Description = "Something is strange about this turban" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 3575 +Name = "a wood cape" +Flags = {Take,Armor} +Attributes = {Weight=1100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3576 +Name = "a post officers hat" +Description = "This hat is the insignia of all tibian post officers" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=1} + +TypeID = 3577 +Name = "meat" +Flags = {Cumulative,Take} +Attributes = {Nutrition=15,Weight=1300} + +TypeID = 3578 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=520} + +TypeID = 3579 +Name = "salmon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=320} + +TypeID = 3580 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=17,Weight=830} + +TypeID = 3581 +Name = "shrimp" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3582 +Name = "ham" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=2000} + +TypeID = 3583 +Name = "dragon ham" +Description = "It still contains a small part of the power of a dragon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=60,Weight=3000} + +TypeID = 3584 +Name = "a pear" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=140} + +TypeID = 3585 +Name = "a red apple" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=150} + +TypeID = 3586 +Name = "an orange" +Flags = {Cumulative,Take} +Attributes = {Nutrition=13,Weight=110} + +TypeID = 3587 +Name = "a banana" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=180} + +TypeID = 3588 +Name = "a blueberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3589 +Name = "a coconut" +Flags = {Cumulative,Take} +Attributes = {Nutrition=18,Weight=480} + +TypeID = 3590 +Name = "a cherry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3591 +Name = "a strawberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=20} + +TypeID = 3592 +Name = "grapes" +Flags = {Take} +Attributes = {Nutrition=9,Weight=250} + +TypeID = 3593 +Name = "a melon" +Flags = {Take} +Attributes = {Nutrition=20,Weight=950} + +TypeID = 3594 +Name = "a pumpkin" +Flags = {Take} +Attributes = {Nutrition=17,Weight=1350} + +TypeID = 3595 +Name = "a carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3596 +Name = "a tomato" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=100} + +TypeID = 3597 +Name = "a corncob" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=350} + +TypeID = 3598 +Name = "a cookie" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=10} + +TypeID = 3599 +Name = "a candy cane" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=50} + +TypeID = 3600 +Name = "a bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 3601 +Name = "a roll" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=100} + +TypeID = 3602 +Name = "a brown bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=400} + +TypeID = 3603 +Name = "flour" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3604 +Name = "a lump of dough" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3605 +Name = "a bunch of wheat" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=1250} + +TypeID = 3606 +Name = "an egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 3607 +Name = "cheese" +Flags = {Take} +Attributes = {Nutrition=9,Weight=400} + +TypeID = 3608 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3609 +Name = "a snowy fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3610 +Name = "a plum tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3617} + +TypeID = 3611 +Name = "a firtree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3614} + +TypeID = 3612 +Name = "a dead tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3634} + +TypeID = 3613 +Name = "a holy tree" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=143} + +TypeID = 3614 +Name = "a fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3615 +Name = "a sycamore" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3616 +Name = "a willow" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3617 +Name = "a plum tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3618 +Name = "a red maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3619 +Name = "a pear tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3620 +Name = "a yellow maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3621 +Name = "a beech" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3622 +Name = "a poplar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3623 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3624 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3625 +Name = "a dwarf tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3626 +Name = "a pine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3627 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3628 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3629 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3630 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3631 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3632 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3633 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3634 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3635 +Name = "old rush wood" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3636 +Name = "an old tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3637 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3638 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3639 +Name = "a palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3640 +Name = "a coconut palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3641 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3642 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3643 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3644 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3645 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3646 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3647 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3648 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3649 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3650 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3651 +Name = "wheat" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3652,TotalExpireTime=43200} + +TypeID = 3652 +Name = "wheat" +Description = "It's not mature yet" +Flags = {Unmove,Avoid,Expire} +Attributes = {ExpireTarget=3653,TotalExpireTime=43200} + +TypeID = 3653 +Name = "wheat" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3654 +Name = "moon flowers" +Flags = {Unmove} + +TypeID = 3655 +Name = "a moon flower" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3656 +Name = "a white flower" +Flags = {Unmove} + +TypeID = 3657 +Name = "a heaven blossom" +Flags = {Unmove} + +TypeID = 3658 +Name = "a red rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3659 +Name = "a blue rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3660 +Name = "a yellow rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3661 +Name = "a grave flower" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3662 +Name = "a love flower" +Flags = {Unmove} + +TypeID = 3663 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3664 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3665 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3666 +Name = "some sunflowers" +Flags = {Unmove,Avoid} + +TypeID = 3667 +Name = "a sunflower" +Flags = {Unmove} + +TypeID = 3668 +Name = "a tulip" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3669 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3670 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3671 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3672 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3673 +Name = "an orange star" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3674 +Name = "a goat grass" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3675 +Name = "an orchid" +Flags = {Unmove} + +TypeID = 3676 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3677 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3678 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3679 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3680 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3681 +Name = "a bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3682 +Name = "a small fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3683 +Name = "a shadow plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3684 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3685 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3686 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3687 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3688 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3689 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3690 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3691 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3692 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3693 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3694 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3695 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3696,TotalExpireTime=300} + +TypeID = 3696 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3697 +Name = "an agave" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3698 +Name = "a dry bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3699 +Name = "a blueberry bush" +Flags = {UseEvent,Bottom,Unpass,Unmove,Unlay} + +TypeID = 3700 +Name = "a blueberry bush" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=3699,TotalExpireTime=3600} + +TypeID = 3701 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3702,TotalExpireTime=300} + +TypeID = 3702 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3703 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3704 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3705 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3706 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3707 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3708 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3709 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3710 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3711 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3712 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3713 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3714 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3715 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3716 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3717 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3718 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3719 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3720 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3721 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3722 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3723 +Name = "a white mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=40} + +TypeID = 3724 +Name = "a red mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3725 +Name = "a brown mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=22,Weight=20} + +TypeID = 3726 +Name = "an orange mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=30} + +TypeID = 3727 +Name = "a wood mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=30} + +TypeID = 3728 +Name = "a dark mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=10} + +TypeID = 3729 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=10} + +TypeID = 3730 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=10} + +TypeID = 3731 +Name = "a fire mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=36,Weight=10} + +TypeID = 3732 +Name = "a green mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 3733 +Name = "dark mushrooms" +Flags = {Unmove,Hang} + +TypeID = 3734 +Name = "a blood herb" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 3735 +Name = "a stone herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3736 +Name = "a star herb" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3737 +Name = "a fern" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3738 +Name = "a sling herb" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 3739 +Name = "a powder herb" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 3740 +Name = "a shadow herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3741 +Name = "a troll green" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 3742 +Name = "an orange tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3743 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3744 +Name = "a jungle dweller bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3745 +Name = "a tower fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3746 +Name = "a snake nest bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3747 +Name = "a green wig bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3748 +Name = "a lizards tongue bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3749 +Name = "a jungle crown plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3750 +Name = "a green fountain bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3751 +Name = "a big fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3752 +Name = "a dragons nest tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3753 +Name = "a purple kiss bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3754 +Name = "a small fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3755 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3756 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3757 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3758 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3759 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3760 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3761 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3762 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3763 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3764 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3765 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3766 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3767 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3768 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3769 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3770 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3771 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3772 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3773 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3774 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3775 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3776 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3777 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3778 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3779 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3780 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3781 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3782 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3783 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3784 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3785 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3786 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3787 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3788 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3789 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3790 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3791 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3792 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3793 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3794 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3795 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3796 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3797 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3798 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3799 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3800 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3801 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3802 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3803 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3804 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3805 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3806 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3807 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3808 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3809 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3810 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3811 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3812 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3813 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3814 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3815 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3816 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3817 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3818 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3819 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3820 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3821 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3822 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3823 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3824 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3825 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3826 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3827 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3828 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3829 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3830 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3831 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3832 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3833 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3834 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3835 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3836 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3837 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3838 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3839 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3840 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3841 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3842 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3843 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3844 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3845 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3846 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3847 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3848 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3849 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3850 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3851 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3852 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3853 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3854 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3855 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3856 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3857 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3858 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3859 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3860 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3861 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3862 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3863 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3864 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3865 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3866 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3867 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3868 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3869 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3870 +Name = "a chill nettle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3871 +Name = "a monkey tail" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3872 +Name = "a fairy queen" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3873 +Name = "a crane plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3874 +Name = "a jungle bells plant" +Flags = {Bottom,Unmove} + +TypeID = 3875 +Name = "a dawn singer" +Flags = {Bottom,Unmove} + +TypeID = 3876 +Name = "a turtle sprouter" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3877 +Name = "a bees ballroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3878 +Name = "a giant jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3879 +Name = "a jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3880 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3881 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3882 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3883 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3884 +Name = "a purple cardinal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3885 +Name = "a witches cauldron plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3886 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3887 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3888 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3889 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3890 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3891 +Name = "a sneeze blossom" +Flags = {Bottom,Unmove} + +TypeID = 3892 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3893 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3894 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3895 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3896 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3897 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3898 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3899 +Name = "a devil's tongue flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3900 +Name = "a small pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3901 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3902 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3903 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3904 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3905 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3906 +Name = "a dead man's saddle" +Flags = {Unmove} + +TypeID = 3907 +Name = "dead man's saddles" +Flags = {Unmove} + +TypeID = 3908 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3909 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3910 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3911 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 3912 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3913 +Name = "slime table mushrooms" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3914 +Name = "a giggle mushroom" +Flags = {Unmove} + +TypeID = 3915 +Name = "giggle mushrooms" +Flags = {Unmove} + +TypeID = 3916 +Name = "a cat's food mushroom" +Flags = {Unmove} + +TypeID = 3917 +Name = "cat's food mushrooms" +Flags = {Unmove} + +TypeID = 3918 +Name = "a glimmer cap mushroom" +Flags = {Unmove} + +TypeID = 3919 +Name = "glimmer cap mushrooms" +Flags = {Unmove} + +TypeID = 3920 +Name = "a giant glimmer cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3921 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3922 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3923 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3924 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3925 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3926 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3927 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3928 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3929 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3930 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3931 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3932 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3933 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3934 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3935 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3936 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3937 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3938 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3939 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3940 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3941 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3942 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3943 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3944 +Name = "a jungle maw" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 3945 +Name = "a jungle maw" +Flags = {Bottom,Unmove,Expire} +Attributes = {ExpireTarget=3944,TotalExpireTime=150} + +TypeID = 3946 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3947 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3948 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3949 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3950 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3951 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3952 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3953 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3954 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3955 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3956 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3957 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3958 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3959 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3960 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3961 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3962 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3963 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3964 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3965 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3966 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3967 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3968 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3969 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3970 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3971 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3972 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3973 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3974 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3975 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3976 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3977 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3978 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3979 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3980 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3981 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3982 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3983 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3984 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3985 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3986 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3987 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=60000,ExpireTarget=3991,TotalExpireTime=1800} + +TypeID = 3988 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=4600,ExpireTarget=4003,TotalExpireTime=1200} + +TypeID = 3989 +Name = "a dead cyclops" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4092,TotalExpireTime=1800} + +TypeID = 3990 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=60000,ExpireTarget=4103,TotalExpireTime=1200} + +TypeID = 3991 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 3992 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 3993 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3994 +Name = "a dead rat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=3995,TotalExpireTime=1200} + +TypeID = 3995 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=3996,TotalExpireTime=1200} + +TypeID = 3996 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 3997 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3998 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=3999,TotalExpireTime=1200} + +TypeID = 3999 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=840,ExpireTarget=4000,TotalExpireTime=1200} + +TypeID = 4000 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=620,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4001 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=82000,ExpireTarget=4002,TotalExpireTime=1800} + +TypeID = 4002 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=65000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4003 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=4004,TotalExpireTime=1200} + +TypeID = 4004 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4005 +Name = "a dead rotworm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4006,TotalExpireTime=1200} + +TypeID = 4006 +Name = "a dead rotworm" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4145,TotalExpireTime=1200} + +TypeID = 4007 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=26000,ExpireTarget=4008,TotalExpireTime=1800} + +TypeID = 4008 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=20000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4009 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=13000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4010 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4011 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=130000,ExpireTarget=4012,TotalExpireTime=1800} + +TypeID = 4012 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=105000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4013 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=85000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4014 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4015 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4016 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=68000,ExpireTarget=4017,TotalExpireTime=1800} + +TypeID = 4017 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=58000,ExpireTarget=4018,TotalExpireTime=1800} + +TypeID = 4018 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4019 +Name = "a dead deer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4020 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=21000,ExpireTarget=4021,TotalExpireTime=1200} + +TypeID = 4021 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=14000,ExpireTarget=4022,TotalExpireTime=1200} + +TypeID = 4022 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=9000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4023 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4024 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=4156,TotalExpireTime=1200} + +TypeID = 4025 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4026,TotalExpireTime=1800} + +TypeID = 4026 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4027 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4028 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4029 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4066,TotalExpireTime=1200} + +TypeID = 4030 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4031,TotalExpireTime=1800} + +TypeID = 4031 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4032,TotalExpireTime=1800} + +TypeID = 4032 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4033 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4034 +Name = "a slain ghoul" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=64000,ExpireTarget=4035,TotalExpireTime=1800} + +TypeID = 4035 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=57000,ExpireTarget=4036,TotalExpireTime=1800} + +TypeID = 4036 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4037 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4038 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4039,TotalExpireTime=1800} + +TypeID = 4039 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4040,TotalExpireTime=1800} + +TypeID = 4040 +Name = "a dead giant spider" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4041 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4042,TotalExpireTime=1800} + +TypeID = 4042 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4043 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4044,TotalExpireTime=1800} + +TypeID = 4044 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4045 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4046,TotalExpireTime=1800} + +TypeID = 4046 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4047 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4048,TotalExpireTime=1800} + +TypeID = 4048 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4049 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4050 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4051 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4052 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4053,TotalExpireTime=1800} + +TypeID = 4053 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4054 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4055 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4056 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4057 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=150000,ExpireTarget=4058,TotalExpireTime=1800} + +TypeID = 4058 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4059 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4060 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4061 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4062 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4063,TotalExpireTime=1800} + +TypeID = 4063 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4064 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4065 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4066 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7000,ExpireTarget=4125,TotalExpireTime=1200} + +TypeID = 4067 +Name = "a dead fire devil" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=80000,ExpireTarget=4068,TotalExpireTime=1800} + +TypeID = 4068 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=60000,ExpireTarget=4069,TotalExpireTime=1800} + +TypeID = 4069 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4070 +Name = "a dead lion" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4071,TotalExpireTime=1800} + +TypeID = 4071 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4072,TotalExpireTime=1800} + +TypeID = 4072 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4073 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4074 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4075,TotalExpireTime=1800} + +TypeID = 4075 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4076,TotalExpireTime=1800} + +TypeID = 4076 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4077 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4078 +Name = "a dead scorpion" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4079,TotalExpireTime=1200} + +TypeID = 4079 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4146,TotalExpireTime=1200} + +TypeID = 4080 +Name = "a dead wasp" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4081,TotalExpireTime=1200} + +TypeID = 4081 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4082,TotalExpireTime=1200} + +TypeID = 4082 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4083 +Name = "a dead bug" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000,ExpireTarget=4084,TotalExpireTime=1200} + +TypeID = 4084 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6500,ExpireTarget=4085,TotalExpireTime=1200} + +TypeID = 4085 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4086 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4087,TotalExpireTime=1800} + +TypeID = 4087 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4088 +Name = "a dead sheep" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=35000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4089 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4090,TotalExpireTime=1800} + +TypeID = 4090 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4091,TotalExpireTime=1800} + +TypeID = 4091 +Name = "a dead beholder" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4092 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4093,TotalExpireTime=1800} + +TypeID = 4093 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4094 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=2200,ExpireTarget=4158,TotalExpireTime=1200} + +TypeID = 4095 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4096,TotalExpireTime=1800} + +TypeID = 4096 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4097 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4098,TotalExpireTime=1800} + +TypeID = 4098 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4099,TotalExpireTime=1800} + +TypeID = 4099 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4100 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4101 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4102,TotalExpireTime=1800} + +TypeID = 4102 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4103 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=4104,TotalExpireTime=1200} + +TypeID = 4104 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4105 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=45000,ExpireTarget=4106,TotalExpireTime=1800} + +TypeID = 4106 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=32000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4107 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=18000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4108 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4109 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=92000,ExpireTarget=4110,TotalExpireTime=1800} + +TypeID = 4110 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=83000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 4111 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=69000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4112 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4113,TotalExpireTime=1800} + +TypeID = 4113 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4114,TotalExpireTime=1800} + +TypeID = 4114 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4115 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4116 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=75000,ExpireTarget=4117,TotalExpireTime=1800} + +TypeID = 4117 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4118,TotalExpireTime=1800} + +TypeID = 4118 +Name = "a dead pig" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4119 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4120,TotalExpireTime=1800} + +TypeID = 4120 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=90000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4121 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=68000,ExpireTarget=4122,TotalExpireTime=1800} + +TypeID = 4122 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=58000,ExpireTarget=4123,TotalExpireTime=1800} + +TypeID = 4123 +Name = "a dead goblin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=37000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4124 +Name = "a dead golin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4125 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4126 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4127,TotalExpireTime=1800} + +TypeID = 4127 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4128 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4129 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=27000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4130 +Name = "remains of a mummy" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=48000,ExpireTarget=4131,TotalExpireTime=1200} + +TypeID = 4131 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=31000,ExpireTarget=4132,TotalExpireTime=1200} + +TypeID = 4132 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4133 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4134,TotalExpireTime=1800} + +TypeID = 4134 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4135,TotalExpireTime=1800} + +TypeID = 4135 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4136 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4137 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=78000,ExpireTarget=4138,TotalExpireTime=1200} + +TypeID = 4138 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=69000,ExpireTarget=4139,TotalExpireTime=1200} + +TypeID = 4139 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=48000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4140 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=33000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4141 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4142,TotalExpireTime=1800} + +TypeID = 4142 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4143 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=52000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4144 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=34000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4145 +Name = "a dead rotworm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4146 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4147 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=51000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4148 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4149,TotalExpireTime=1800} + +TypeID = 4149 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=92000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4150 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4151,TotalExpireTime=1800} + +TypeID = 4151 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4152,TotalExpireTime=1800} + +TypeID = 4152 +Name = "a dead war wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4153 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4154,TotalExpireTime=1800} + +TypeID = 4154 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4155,TotalExpireTime=1800} + +TypeID = 4155 +Name = "a dead orc and wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4156 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=26000,ExpireTarget=4157,TotalExpireTime=1200} + +TypeID = 4157 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4158 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=1300,ExpireTarget=4159,TotalExpireTime=1200} + +TypeID = 4159 +Name = "remains of a ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4160 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4161,TotalExpireTime=1800} + +TypeID = 4161 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4162 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4163,TotalExpireTime=1800} + +TypeID = 4163 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4164 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4165,TotalExpireTime=1800} + +TypeID = 4165 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4166 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4167,TotalExpireTime=1800} + +TypeID = 4167 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4168 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4169,TotalExpireTime=1800} + +TypeID = 4169 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4170 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4171,TotalExpireTime=1800} + +TypeID = 4171 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4172,TotalExpireTime=1800} + +TypeID = 4172 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4173 +Name = "a dead rabbit" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4174,TotalExpireTime=1200} + +TypeID = 4174 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4175,TotalExpireTime=1200} + +TypeID = 4175 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4176 +Name = "a dead swamp troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=SLIME,Weight=60000,ExpireTarget=4177,TotalExpireTime=1800} + +TypeID = 4177 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=4178,TotalExpireTime=1800} + +TypeID = 4178 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4179 +Name = "a slain banshee" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,Weight=11000,ExpireTarget=4180,TotalExpireTime=1800} + +TypeID = 4180 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=4181,TotalExpireTime=1800} + +TypeID = 4181 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4182 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4183,TotalExpireTime=1800} + +TypeID = 4183 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4184,TotalExpireTime=1800} + +TypeID = 4184 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4185 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4186,TotalExpireTime=1800} + +TypeID = 4186 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4187,TotalExpireTime=1800} + +TypeID = 4187 +Name = "a dead scarab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4188 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {FluidSource=BLOOD,Weight=1320,ExpireTarget=4189,TotalExpireTime=1200} + +TypeID = 4189 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=920,ExpireTarget=4190,TotalExpireTime=1200} + +TypeID = 4190 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=680,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4191 +Name = "a dead larva" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=1050,ExpireTarget=4192,TotalExpireTime=1200} + +TypeID = 4192 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=820,ExpireTarget=4193,TotalExpireTime=1200} + +TypeID = 4193 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=530,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4194 +Name = "a dead scarab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=12000,ExpireTarget=4195,TotalExpireTime=1200} + +TypeID = 4195 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7800,ExpireTarget=4196,TotalExpireTime=1200} + +TypeID = 4196 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3600,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4197 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4198,TotalExpireTime=1800} + +TypeID = 4198 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4199,TotalExpireTime=1800} + +TypeID = 4199 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4200 +Name = "a dead hyaena" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4201,TotalExpireTime=1800} + +TypeID = 4201 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4202,TotalExpireTime=1800} + +TypeID = 4202 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4203 +Name = "a dead gargoyle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4204,TotalExpireTime=1800} + +TypeID = 4204 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4205,TotalExpireTime=1800} + +TypeID = 4205 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4206 +Name = "a slain lich" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4207,TotalExpireTime=1800} + +TypeID = 4207 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4208,TotalExpireTime=1800} + +TypeID = 4208 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4209 +Name = "a slain crypt shambler" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4210,TotalExpireTime=1800} + +TypeID = 4210 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4211,TotalExpireTime=1800} + +TypeID = 4211 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4212 +Name = "a slain bonebeast" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4213,TotalExpireTime=1800} + +TypeID = 4213 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4214,TotalExpireTime=1800} + +TypeID = 4214 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4215 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4216,TotalExpireTime=1800} + +TypeID = 4216 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4217,TotalExpireTime=1800} + +TypeID = 4217 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4218 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4219,TotalExpireTime=1800} + +TypeID = 4219 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4220,TotalExpireTime=1800} + +TypeID = 4220 +Name = "a dead efreet" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4221 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4222,TotalExpireTime=1800} + +TypeID = 4222 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4223,TotalExpireTime=1800} + +TypeID = 4223 +Name = "a dead marid" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4224 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5900,ExpireTarget=4225,TotalExpireTime=1800} + +TypeID = 4225 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5500,ExpireTarget=4226,TotalExpireTime=1800} + +TypeID = 4226 +Name = "a dead badger" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4227 +Name = "a dead skunk" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5600,ExpireTarget=4228,TotalExpireTime=1800} + +TypeID = 4228 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=4229,TotalExpireTime=1800} + +TypeID = 4229 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4230 +Name = "a dead gazer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4231,TotalExpireTime=1800} + +TypeID = 4231 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4232,TotalExpireTime=1800} + +TypeID = 4232 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4233 +Name = "a dead elder beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4234,TotalExpireTime=1800} + +TypeID = 4234 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4235,TotalExpireTime=1800} + +TypeID = 4235 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4236 +Name = "a dead yeti" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4237,TotalExpireTime=1800} + +TypeID = 4237 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4238,TotalExpireTime=1800} + +TypeID = 4238 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4239 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4240 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4241,TotalExpireTime=1800} + +TypeID = 4241 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4242 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4243 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4244 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4245 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4246 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4247 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4248,TotalExpireTime=1800} + +TypeID = 4248 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4249 +Name = "a dead troll" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000} + +TypeID = 4250 +Name = "a dead spider" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000} + +TypeID = 4251 +Name = "a dead cyclops" +Flags = {Container} +Attributes = {Capacity=12,FluidSource=BLOOD} + +TypeID = 4252 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4253 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4254 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4255 +Name = "a dead rat" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4256 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4257 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4258 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4259 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4260 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 4261 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4262 +Name = "a dead orc" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000} + +TypeID = 4263 +Name = "a dead orc" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4264 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4265 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4266 +Name = "a dead rotworm" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4267 +Name = "a dead rotworm" + +TypeID = 4268 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=6,FluidSource=BLOOD,Weight=21000} + +TypeID = 4269 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=15000} + +TypeID = 4270 +Name = "a dead wolf" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4271 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4272 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=12,FluidSource=BLOOD,Weight=150000} + +TypeID = 4273 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=7,Weight=110000} + +TypeID = 4274 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=2,Weight=80000} + +TypeID = 4275 +Name = "a dead minotaur" +Flags = {Take} +Attributes = {Weight=40000} + +TypeID = 4276 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=20000} + +TypeID = 4277 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000} + +TypeID = 4278 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=3,Weight=50000} + +TypeID = 4279 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=30000} + +TypeID = 4280 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4281 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=2,FluidSource=BLOOD,Weight=20000} + +TypeID = 4282 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=10000} + +TypeID = 4283 +Name = "a dead dog" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4284 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4285 +Name = "a pile of bones" +Flags = {Container,Take} +Attributes = {Capacity=10,Weight=10000} + +TypeID = 4286 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=16,FluidSource=BLOOD} + +TypeID = 4287 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4288 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4289 +Name = "a pile of bones" + +TypeID = 4290 +Name = "remains of a ghost" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=2200} + +TypeID = 4291 +Name = "a dead bear" +Flags = {Container} +Attributes = {Capacity=8,FluidSource=BLOOD} + +TypeID = 4292 +Name = "a dead bear" + +TypeID = 4293 +Name = "a dead bear" + +TypeID = 4294 +Name = "a pile of bones" + +TypeID = 4295 +Name = "a slain ghoul" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=40000} + +TypeID = 4296 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4297 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4298 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4299 +Name = "a dead cyclops" + +TypeID = 4300 +Name = "a pile of bones" + +TypeID = 4301 +Name = "a dead rabbit" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4302 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4303 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4304 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4305 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4306 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4307 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4308 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4309 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4310 +Name = "a dead elephant" +Flags = {Container} +Attributes = {Capacity=15} + +TypeID = 4311 +Name = "a dead human" +Flags = {Container,Corpse} +Attributes = {Capacity=10} + +TypeID = 4312 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4313 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=50000} + +TypeID = 4314 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4315 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4316 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4317 +Name = "some bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4318 +Name = "a dead crab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4319,TotalExpireTime=1200} + +TypeID = 4319 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4320,TotalExpireTime=1200} + +TypeID = 4320 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4321 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4322,TotalExpireTime=1200} + +TypeID = 4322 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4323,TotalExpireTime=1200} + +TypeID = 4323 +Name = "a dead lizard templar" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4324 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4325,TotalExpireTime=1200} + +TypeID = 4325 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4326,TotalExpireTime=1200} + +TypeID = 4326 +Name = "a dead lizard sentinel" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4327 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4328,TotalExpireTime=1200} + +TypeID = 4328 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4329,TotalExpireTime=1200} + +TypeID = 4329 +Name = "a dead lizard snakecharmer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4330 +Name = "a dead chicken" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4331,TotalExpireTime=1200} + +TypeID = 4331 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4332,TotalExpireTime=1200} + +TypeID = 4332 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4333 +Name = "a dead kongra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4334,TotalExpireTime=1200} + +TypeID = 4334 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4335,TotalExpireTime=1200} + +TypeID = 4335 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4336 +Name = "a dead merlkin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4337,TotalExpireTime=1200} + +TypeID = 4337 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4338,TotalExpireTime=1200} + +TypeID = 4338 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4339 +Name = "a dead sibang" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4340,TotalExpireTime=1200} + +TypeID = 4340 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4341,TotalExpireTime=1200} + +TypeID = 4341 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4342 +Name = "a dead crocodile" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4343,TotalExpireTime=1200} + +TypeID = 4343 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4344,TotalExpireTime=1200} + +TypeID = 4344 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4345 +Name = "a dead carniphila" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4346,TotalExpireTime=1200} + +TypeID = 4346 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4347,TotalExpireTime=1200} + +TypeID = 4347 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4348 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4349,TotalExpireTime=1800} + +TypeID = 4349 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4350,TotalExpireTime=1800} + +TypeID = 4350 +Name = "a dead hydra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4351 +Name = "a dead panda" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4352,TotalExpireTime=1200} + +TypeID = 4352 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4353,TotalExpireTime=1200} + +TypeID = 4353 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4354 +Name = "a dead centipede" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4355,TotalExpireTime=1200} + +TypeID = 4355 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4356,TotalExpireTime=1200} + +TypeID = 4356 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4357 +Name = "a dead tiger" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4358,TotalExpireTime=1200} + +TypeID = 4358 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4359,TotalExpireTime=1200} + +TypeID = 4359 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4360 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4361,TotalExpireTime=1800} + +TypeID = 4361 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4362,TotalExpireTime=1800} + +TypeID = 4362 +Name = "a dead elephant" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4363 +Name = "a dead bat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4364,TotalExpireTime=1200} + +TypeID = 4364 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4365,TotalExpireTime=1200} + +TypeID = 4365 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4366 +Name = "a dead flamingo" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4367,TotalExpireTime=1200} + +TypeID = 4367 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4368,TotalExpireTime=1200} + +TypeID = 4368 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4369 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4370,TotalExpireTime=1200} + +TypeID = 4370 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4371,TotalExpireTime=1200} + +TypeID = 4371 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4372 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4373,TotalExpireTime=1200} + +TypeID = 4373 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4374,TotalExpireTime=1200} + +TypeID = 4374 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4375 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4376,TotalExpireTime=1200} + +TypeID = 4376 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4377,TotalExpireTime=1200} + +TypeID = 4377 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4378 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4379 +Name = "a dead parrot" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4380,TotalExpireTime=1200} + +TypeID = 4380 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4381,TotalExpireTime=1200} + +TypeID = 4381 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4382 +Name = "a dead bird" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4383,TotalExpireTime=1200} + +TypeID = 4383 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4384,TotalExpireTime=1200} + +TypeID = 4384 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4385 +Name = "a dead tarantula" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4386,TotalExpireTime=1200} + +TypeID = 4386 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4387,TotalExpireTime=1200} + +TypeID = 4387 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4388 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4389,TotalExpireTime=1800} + +TypeID = 4389 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4390,TotalExpireTime=1800} + +TypeID = 4390 +Name = "a dead serpent spawn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4391 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4392,TotalExpireTime=1200} + +TypeID = 4392 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4393 +Name = "a drawbridge" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=90,DisguiseTarget=1771} + +TypeID = 4394 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4395 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4396 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4397 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4398 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4399 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4400 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4401 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4402 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4403 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4404 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4405 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4406 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4407 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4408 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4409 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4410 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4411 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4412 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4413 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4414 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4415 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4416 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4417 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4418 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4419 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4420 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4421 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4422 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4423 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4424 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4425 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4426 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4427 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4428 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4429 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4430 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4431 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4432 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4433 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4434 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4435 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4436 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4437 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4438 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4439 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4440 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4441 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4442 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4443 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4444 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4445 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4446 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4447 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4448 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4449 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4450 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4451 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4452 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4453 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4454 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4455 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4456 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4457 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4458 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4459 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4460 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4461 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4462 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4463 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4464 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4465 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4466 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4467 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4468 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4469 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4470 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4471 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4472 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4473 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4474 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4475 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4476 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4477 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4478 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4479 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4480 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4481 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4482 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4483 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4484 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4485 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4486 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4487 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4488 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4489 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4490 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4491 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4492 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4493 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4494 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4495 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4496 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4497 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4498 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4499 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4500 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4501 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4502 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4503 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4504 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4505 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4506 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4507 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4508 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4509 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4510 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4511 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4512 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4513 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4514 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4515 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4516 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4517 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4518 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4519 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4520 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4521 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4522 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4523 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4524 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4525 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4526 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4527 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4528 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4529 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4530 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4531 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4532 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4533 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4534 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4535 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4536 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4537 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4538 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4539 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4540 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4541 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4542 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4543 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4544 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4545 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4546 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4547 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4548 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4549 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4550 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4551 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4552 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4553 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4554 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4555 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4556 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4557 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4558 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4559 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4560 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4561 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4562 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4563 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4564 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4565 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4566 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4567 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4568 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4569 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4570 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4571 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4572 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4573 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4574 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4575 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4576 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4577 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4578 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4579 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4580 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4581 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4582 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4583 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4584 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4585 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4586 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4587 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4588 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4589 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4590 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4591 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4592 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4593 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4594 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4595 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4596 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4597 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4598 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4599 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4600 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4601 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4602 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4603 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4604 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4605 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4606 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4607 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4608 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4609 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4597,TotalExpireTime=2200} + +TypeID = 4610 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4598,TotalExpireTime=2200} + +TypeID = 4611 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4599,TotalExpireTime=2200} + +TypeID = 4612 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4600,TotalExpireTime=2200} + +TypeID = 4613 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4601,TotalExpireTime=2200} + +TypeID = 4614 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4602,TotalExpireTime=2200} + +TypeID = 4615 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4609} + +TypeID = 4616 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4610} + +TypeID = 4617 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4611} + +TypeID = 4618 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4612} + +TypeID = 4619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4613} + +TypeID = 4620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4614} + +TypeID = 4621 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4622 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4623 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4624 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4625 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4626 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4627 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4628 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4629 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4630 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4631 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4632 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4633 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4634 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4635 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4636 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4637 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4638 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4639 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4640 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4641 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4642 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4643 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4644 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4645 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4646 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4647 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4648 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4649 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4650 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4651 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4652 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4653 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4654 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4655 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4656 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4657 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4658 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4659 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4660 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4661 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4662 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4663 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4664 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4665 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4666 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4667 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4668 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4669 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4670 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4671 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4672 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4673 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4674 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4675 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4676 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4677 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4678 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4679 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4680 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4681 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4682 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4683 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4684 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4685 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4686 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4687 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4688 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4689 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4690 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4691 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4692 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4693 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4694 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4695 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4696 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4697 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4698 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4699 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4700 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4701 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4702 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4703 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4704 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4705 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4706 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4707 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4708 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4709 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4710 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4711 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4712 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4713 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4714 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4715 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4716 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4717 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4718 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4719 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4720 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4721 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4722 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4723 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4724 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4725 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4726 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4727 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4728 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4729 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4730 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4731 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4732 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4733 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4734 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4735 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4736 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4737 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4738 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4739 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4740 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4741 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4742 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4743 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4744 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4745 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4746 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4747 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4748 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4749 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4750 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4751 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4752 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4753 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4754 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4755 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4756 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4757 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4758 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4759 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4760 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4761 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4762 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4763 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4764 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4765 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4766 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4767 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4768 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4769 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4770 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4771 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4772 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4773 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4774 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4775 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4776 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4777 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4778 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4779 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4780 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4781 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4782 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4783 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4784 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4785 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4786 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4787 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4788 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4789 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4790 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4791 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4792 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4793 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4794 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4795 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4796 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4797 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4798 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4799 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4800 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4801 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4802 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4803 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4804 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4805 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4806 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4807 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4808 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4809 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4810 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4811 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4812 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4813 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4814 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4815 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4816 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4817 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4818 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4819 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4820 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4821 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4822 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4823 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4824 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4825 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4826 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4827 +Name = "whisper moss" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 4828 +Name = "a flask of cough syrup" +Description = "It smells like herbs" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4829 +Name = "a witches cap mushroom" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4830 +Name = "witches mushrooms" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3919} + +TypeID = 4831 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4832 +Name = "a giant ape's hair" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4833 +Name = "a giant footprint" +Flags = {Bottom,Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=2753} + +TypeID = 4834 +Name = "a family brooch" +Description = "The emblem of a dwarven family is engraved on it" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 4835 +Name = "a snake destroyer" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=6600} + +TypeID = 4836 +Name = "a spectral dress" +Flags = {Take} +Attributes = {Weight=1000,SlotType=BODY} + +TypeID = 4837 +Name = "an icicle" +Description = "It is melting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=1900,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 4838 +Name = "strange powder" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 4839 +Name = "a hydra egg" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4840 +Name = "a spectral stone" +Description = "It is pulsating with spectral energy" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 4841 +Name = "a memory stone" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 4842 +Name = "a sheet of tracing paper" +Description = "It is blank" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=100} + +TypeID = 4843 +Name = "a sheet of tracing paper" +Description = "It contains some strange symbols of the lizard language" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4844 +Name = "an elven poetry book" +Description = "It contains a collection of beautiful elven poems" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 4845 +Name = "a dwarven pickaxe" +Description = "It is a masterpiece of dwarvish smithery and made of especially hard steel" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4846 +Name = "a wrinkled parchment" +Description = "It is covered with strange numbers" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4847 +Name = "a funeral urn" +Description = "It contains the ashes of a lizard high priest" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4848 +Name = "a small cask" +Description = "It is filled with the blood of the snake god" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BLOOD} + +TypeID = 4849 +Name = "wooden trash" +Description = "The blood of the snake god is pouring out" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4848,TotalExpireTime=120} + +TypeID = 4850 +Name = "the statue of the snake god" +Description = "It is emitting an eerie light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=102} + +TypeID = 4851 +Name = "a smashed stone head" +Description = "It seems to repair itself rapidly" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4850,TotalExpireTime=60} + +TypeID = 4852 +Name = "an ectoplasm container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 4853 +Name = "an ectoplasm container" +Description = "It is filled with ectoplasm" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 4854 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4855 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4856 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4857 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4858 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4859 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4860 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4861 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4862 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4863 +Name = "a butterfly conservation kit" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=700} + +TypeID = 4864 +Name = "a butterfly conservation kit" +Description = "It contains a red butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4865 +Name = "a butterfly conservation kit" +Description = "It contains a purple butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4866 +Name = "a butterfly conservation kit" +Description = "It contains a blue butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4867 +Name = "a botanist's container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=1800} + +TypeID = 4868 +Name = "a botanist's container" +Description = "It holds a sample of the jungle bells plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4869 +Name = "a botanist's container" +Description = "It holds a sample of the giant jungle rose" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4870 +Name = "a botanist's container" +Description = "It holds a sample of the witches cauldron plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4871 +Name = "an explorer brooch" +Description = "It is the official badge of the explorer society" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 4872 +Name = "an ice pick" +Description = "It might come in handy in cold regions" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=7000} + +TypeID = 4873 +Name = "a hydra's nest" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=5676} + +TypeID = 4874 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4875 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4876 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4877 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4878 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4879 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4880 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4881 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 4882 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4883 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4884 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 4885 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4886 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4887 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4888 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4889 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4890 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4891 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4892 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4893 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4894 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4895 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4896 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4897 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4898 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4899 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4900 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4901 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4902 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4903 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4904 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4905 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4906 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4907 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4908 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4909 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4910 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4911 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4912 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4913 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4914 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4915 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4916 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4917 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4918 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4919 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4920 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4921 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4922 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4923 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4924 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4925 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4926 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4927 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4928 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4929 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4930 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4931 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4932 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4933 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4934 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4935 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4936 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4937 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4938 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4939 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4940 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4941 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4942 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4943 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4944 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4945 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4946 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4947 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4948 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4949 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4950 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4951 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4952 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4953 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4954 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4955 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4956 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4957 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4958 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4959 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4960 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4961 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4962 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4963 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4964 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4965 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4966 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4967 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4968 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4969 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4970 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4971 +Name = "a ventilation grille" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 4972 +Name = "a bollard" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4973 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4974 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4975 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4976 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4977 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4978 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4979 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4980 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4981 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4982 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4983 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4984 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4985 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4986 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4987 +Name = "a cleat" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4988 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4989 +Name = "a white flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4990 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4991 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4992 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4993 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4994 +Name = "some sharp icicles" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4995 +Name = "a canopic jar" +Description = "You feel an eerie presence" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=4996} + +TypeID = 4996 +Name = "the remains of a canopic jar" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4995,TotalExpireTime=300} + +TypeID = 4997 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4998 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4999 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5000 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 5001 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5002 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5003 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 5004 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 5005 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5006 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5007 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5008 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5009 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5010 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5011 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5012 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5013 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 5014 +Name = "a mandrake" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 5015 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5016 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5017 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5018 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5019 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5020 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5021 +Name = "an orichalcum pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 5022 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5023 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5024 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=215} + +TypeID = 5025 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=208} + +TypeID = 5026 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=201} + +TypeID = 5027 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=194} + +TypeID = 5028 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=187} + +TypeID = 5029 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=180} + +TypeID = 5030 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=214} + +TypeID = 5031 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 5032 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=200} + +TypeID = 5033 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=193} + +TypeID = 5034 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=186} + +TypeID = 5035 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=213} + +TypeID = 5036 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=206} + +TypeID = 5037 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=199} + +TypeID = 5038 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=192} + +TypeID = 5039 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=212} + +TypeID = 5040 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=205} + +TypeID = 5041 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=198} + +TypeID = 5042 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=211} + +TypeID = 5043 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=204} + +TypeID = 5044 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=210} + +TypeID = 5045 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5046 +Name = "a monkey statue" +Description = "The words 'See no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5047 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5048 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5049 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5050 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5051 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5052 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5053 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5054 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5055 +Name = "a monkey statue" +Description = "The words 'Hear no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5056 +Name = "a monkey statue" +Description = "The words 'Speak no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5057 +Name = "a snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5058 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5059 +Name = "a small snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5060 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5061 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5062 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5063 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5064 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5065 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5066 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5067 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5068 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5069 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5070 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5071 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5072 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5073 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5074 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5075 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5076 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5077 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5078 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5079 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5080 +Name = "a panda teddy" +Flags = {UseEvent,Take} +Attributes = {Weight=600} + +TypeID = 5081 +Name = "a ladder" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 5082 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5083 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5084 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5085 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5086 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5087 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5088 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5089 +Name = "a butterfly conservation kit" +Description = "It contains a rare yellow butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 5090 +Name = "a treasure map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 5091 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5092 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5093 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5094 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5095 +Name = "a banana tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5096 +Name = "a mango" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=180} + +TypeID = 5097 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5098 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5099 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5100 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5101 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5102 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5103 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5104 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5105 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5106 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5107 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5108 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5109 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5110 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5111 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5112 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5113 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5114 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5115 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5116 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5117 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5118 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5119 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5120 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5121 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5122 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5123 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5124 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5125 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5126 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5127 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5128 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5129 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5130 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5131 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5132 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5133 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5134 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5135 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5136 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5137 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5138 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5139 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5140 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5141 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5142 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5143 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5144 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5145 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5146 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5147 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5148 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5149 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5150 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5151 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5152 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5153 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5154 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5155 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5156 +Name = "a mango tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5157 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5158 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5159 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5160 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5161 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5162 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5163 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5164 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5165 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5166 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5167 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5168 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5169 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5170 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5171 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5172 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5173 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5174 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5175 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5176 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5177 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5178 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5179 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5180 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5181 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5182 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5183 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5184 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5185 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5186 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5187 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5188 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5189 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5190 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5191 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5192 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5193 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5194 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5195 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5196 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5197 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5198 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5199 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5200 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5201 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5202 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5203 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5204 +Name = "a striped marquee" +Flags = {Unpass,Unmove} + +TypeID = 5205 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5206 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5207 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5208 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5209 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5210 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5211 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5212 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5213 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5214 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5215 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5216 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5217 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5218 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5219 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5220 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5221 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5222 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5223 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5224 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5225 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5226 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5227 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5228 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5230 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5231 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5232 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5233 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5234 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5235 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5236 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5237 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5238 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5239 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5240 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5241 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5242 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5243 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5244 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5245 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5246 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5247 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5248 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5249 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5250 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5251 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5252 +Name = "a marquee" +Flags = {Unpass,Unmove} + +TypeID = 5253 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5254 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5255 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5256 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5257 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5258 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5259 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 5260 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5261 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5262 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5263 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5264 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5265 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5266 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5267 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5268 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5269 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5270 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5271 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5272 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5273 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5274 +Name = "a wooden wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5275 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5276 +Name = "a wooden window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5277 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5278 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5279 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5280 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5281 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5282 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5283 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5284 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5285 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5286 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5287 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5288 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5289 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5290 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5291 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5292 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5293 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5294 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 5295 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5296 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5297 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5298 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5299 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5300 +Name = "a wooden plank" +Flags = {Unmove} + +TypeID = 5301 +Name = "a wooden planks" +Flags = {Unmove} + +TypeID = 5302 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5303 +Name = "a white stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5304 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5305 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5306 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5307 +Name = "a white stone pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5308 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5309 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5310 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5311 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5312 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5313 +Name = "a white stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5314 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5315 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5316 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5317 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5318 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5319 +Name = "a wooden railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5320 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5321 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5322 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5323 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5324 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5325 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5326 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5327 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5328 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5329 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5330 +Name = "a wooden column" +Flags = {Top,Unmove,Unlay} + +TypeID = 5331 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5332 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5333 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5334 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5335 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5336 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5337 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5338 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5339 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5340 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5341 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5342 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5343 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5344 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5345 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5346 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5347 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5348 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5349 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5350 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5351 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5352 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5353 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5354 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5355 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5356 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5357 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5358 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5359 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5360 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 5361 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5362 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5363 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5364 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5365 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5366 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5367 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5368 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5369 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5370 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5371 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5372 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5373 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5374 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5375 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5376 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5377 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5378 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5379 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5380 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5381 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5382 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5383 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5384 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5385 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5386 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5387 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5388 +Name = "a sign" +Flags = {Unmove,Unlay,AllowDistRead} + +TypeID = 5389 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5390 +Name = "a pawpaw tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5391 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5392 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5393 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5394 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5395 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5396 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5397 +Name = "a dry mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5398 +Name = "a mangrove" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5399 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5400 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5401 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5402 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5403 +Name = "a bamboo lamp" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=7,LightColor=207} + +TypeID = 5404 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5405 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5406 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5407 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5408 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5409 +Name = "ocean floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5410 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5411 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5412 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5413 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5414 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5415 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5416 +Name = "a starfish" +Flags = {Unmove} + +TypeID = 5417 +Name = "a sea anemone" +Flags = {Unmove} + +TypeID = 5418 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5419 +Name = "bubbles" +Flags = {Top,Unmove} + +TypeID = 5420 +Name = "kelp" +Flags = {Unpass,Unmove} + +TypeID = 5421 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5422 +Name = "a coral" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5423 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5424 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5425 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5426 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5427 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5428 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5429 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5430 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5431 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5432 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5433 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5434 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5435 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5436 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5437 +Name = "ocean floor" +Flags = {Clip,Unmove} + +TypeID = 5438 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5439 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5440 +Name = "an old steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5441 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5442 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5443 +Name = "a rusty anchor" +Flags = {Unpass,Unmove} + +TypeID = 5444 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5445 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5446 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5447 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5448 +Name = "the remains of a mast" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 5449 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5450 +Name = "a wrecked figurehead" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5451 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5452 +Name = "a wrecked ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5453 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5454 +Name = "a wrecked ship cabin railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5455 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5456 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5457 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5458 +Name = "a wrecked ship hull" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5459 +Name = "a wrecked ventilation grill" +Flags = {Unmove} + +TypeID = 5460 +Name = "a helmet of the deep" +Description = "Enables underwater exploration" +Flags = {Take,Armor} +Attributes = {Weight=21000,SlotType=HEAD,ArmorValue=2,AbsorbDrown=100} + +TypeID = 5461 +Name = "pirate boots" +Flags = {Take,Armor} +Attributes = {Weight=800,SlotType=FEET,ArmorValue=2} + +TypeID = 5462 +Name = "a sugar cane" +Description = "It has just been harvested" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5470,TotalExpireTime=240} + +TypeID = 5463 +Name = "a sugar cane" +Description = "It can be harvested" +Flags = {Unmove,Avoid} + +TypeID = 5464 +Name = "a burning sugar cane" +Flags = {Unpass,Unmove,Expire} +Attributes = {ExpireTarget=5463,TotalExpireTime=10} + +TypeID = 5465 +Name = "a sugar cane" +Flags = {Unpass,Unmove} + +TypeID = 5466 +Name = "a bunch of sugar cane" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=2250} + +TypeID = 5467 +Name = "a fire bug" +Description = "This strange creature has the tendency to set certain things on fire" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=3050,Brightness=3,LightColor=206} + +TypeID = 5468 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5469 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5470 +Name = "a sugar cane" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=5465,TotalExpireTime=240} + +TypeID = 5471 +Name = "a ball on chains" + +TypeID = 5472 +Name = "a ball on chains" + +TypeID = 5473 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5474 +Name = "an iron maiden" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5475 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5476 +Name = "a pillory" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5477 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5478 +Name = "a barred window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5479 +Name = "a cat's paw" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 5480 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5481 +Name = "a shackles" +Flags = {Unpass,Unmove} + +TypeID = 5482 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5483 +Name = "wall chains" +Flags = {Unmove} + +TypeID = 5484 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5485 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5486 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5487 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5488 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5489 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5490 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5491 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5492 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5493 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5494 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5495 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=5501} + +TypeID = 5496 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=5502} + +TypeID = 5497 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=5499} + +TypeID = 5498 +Name = "a straw mat" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=5500} + +TypeID = 5499 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} # TODO: Every straw mat should be a bed to lay in house +Attributes = {BedDirection=EAST,BedTarget=5497} + +TypeID = 5500 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=5498} + +TypeID = 5501 +Name = "a straw mat" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=5495} + +TypeID = 5502 +Name = "a straw mat" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=5496} + +TypeID = 5503 +Name = "a wooden column" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5504 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5505 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5506 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5507 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5508 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5509 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5510 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5511 +Name = "a pulley" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5512 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5468,TotalExpireTime=10} + +TypeID = 5513 +Name = "a distilling machine" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {FluidSource=RUM,ExpireTarget=5469,TotalExpireTime=10} + +TypeID = 5514 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5515 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5516 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5517 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5518 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5519 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5520 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5521 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5536,TotalExpireTime=900} + +TypeID = 5522 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5532,TotalExpireTime=900} + +TypeID = 5523 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5530,TotalExpireTime=900} + +TypeID = 5524 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5528,TotalExpireTime=900} + +TypeID = 5525 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5534,TotalExpireTime=900} + +TypeID = 5526 +Name = "a demon dust" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5527,TotalExpireTime=900} + +TypeID = 5527 +Name = "a demon dust" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5528 +Name = "a dead quara hydromancer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5529,TotalExpireTime=900} + +TypeID = 5529 +Name = "a dead quara hydromancer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5530 +Name = "a dead quara constrictor" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5531,TotalExpireTime=900} + +TypeID = 5531 +Name = "a dead quara constrictor" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5532 +Name = "a dead quara mantassin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5533,TotalExpireTime=900} + +TypeID = 5533 +Name = "a dead quara mantassin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5534 +Name = "a dead quara predator" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5535,TotalExpireTime=900} + +TypeID = 5535 +Name = "a dead quara predator" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5536 +Name = "a dead quara pincher" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5537,TotalExpireTime=900} + +TypeID = 5537 +Name = "a dead quara pincher" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5538 +Name = "a rum cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=RUM} + +TypeID = 5539 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5540,TotalExpireTime=900} + +TypeID = 5540 +Name = "a dead carrion worm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5541,TotalExpireTime=600} + +TypeID = 5541 +Name = "a dead carrion worm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5542 +Name = "a rope-ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 5543 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5544 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 5545 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5546 +Name = "a cart" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5547 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5548 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5549 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5550 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5551 +Name = "a pile of sugar cane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5552 +Name = "a rum flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=280} + +TypeID = 5553 +Name = "a small fish" +Flags = {Unmove} + +TypeID = 5554 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5555 +Name = "straw" +Flags = {Clip,Unmove} + +TypeID = 5556 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5557 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5558 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5559 +Name = "a mossy wall" +Flags = {Unmove} + +TypeID = 5560 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5561 +Name = "a small branches" +Flags = {Unmove} + +TypeID = 5562 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5563 +Name = "a slimy wall" +Flags = {Unmove} + +TypeID = 5564 +Name = "a slain pirate skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=0,TotalExpireTime=900} + +TypeID = 5565 +Name = "a slain pirate ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=20000,ExpireTarget=5566,TotalExpireTime=900} + +TypeID = 5566 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=5567,TotalExpireTime=600} + +TypeID = 5567 +Name = "a slain pirate ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5568 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5569 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5570 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5571 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5572 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5573 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5574 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5575 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5576 +Name = "a blue carpet" +Flags = {Clip,Unmove} + +TypeID = 5577 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5578 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5579 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5580 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5581 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5582 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5583 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5584 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5585 +Name = "a green carpet" +Flags = {Clip,Unmove} + +TypeID = 5586 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5587 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5588 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5589 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5590 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5591 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5592 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5593 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5594 +Name = "a brown carpet" +Flags = {Clip,Unmove} + +TypeID = 5595 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5596 +Name = "a crossed weapons" +Flags = {Unmove,Hang} + +TypeID = 5597 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5598 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5599 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5600 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5601 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5602 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5603 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5604 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5605 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5606 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5607 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5608 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5609 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5610 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5611 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5612 +Name = "a catapult" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5613 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5614 +Name = "a pirate flag" +Flags = {Unmove} + +TypeID = 5615 +Name = "a pirate tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 5616 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5617 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5618 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5619 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5620 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5621 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5622 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5623 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5624 +Name = "a dead tortoise" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=6,Weight=1000,FluidSource=BLOOD,ExpireTarget=5625,TotalExpireTime=900} + +TypeID = 5625 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=5626,TotalExpireTime=600} + +TypeID = 5626 +Name = "a dead tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5627 +Name = "a dead thornback tortoise" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5628,TotalExpireTime=900} + +TypeID = 5628 +Name = "a dead thornback tortoise" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5629,TotalExpireTime=600} + +TypeID = 5629 +Name = "a dead thornback tortoise" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5630 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5631 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5632 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5633 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5634 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5635 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5636 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5637 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5638 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5639 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5640 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5641 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5642 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5643 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5644 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5645 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5646 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5647 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5648 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5649 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5650 +Name = "a dirt wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5651 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5652 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5653 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5654 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5655 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5656 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5657 +Name = "a map" +Flags = {Unmove,Unlay} + +TypeID = 5658 +Name = "a blooming griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5659 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5660 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5661 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5662 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5663 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5664 +Name = "a water trickle" +Flags = {Unmove} + +TypeID = 5665 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5666,TotalExpireTime=900} + +TypeID = 5666 +Name = "a dead mammoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=5667,TotalExpireTime=600} + +TypeID = 5667 +Name = "a dead mammoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5668 +Name = "a mysterious voodoo skull" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 5669 +Name = "a enigmatic voodoo skull" +Description = "It is not time yet" +Flags = {Take,Expire} +Attributes = {Weight=1400,ExpireTarget=5668,TotalExpireTime=72000} + +TypeID = 5670 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5671 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5672 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5673 +Name = "a pirate statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5674 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5675 +Name = "a treasure chest" +Flags = {Unmove,Avoid} + +TypeID = 5676 +Name = "a hydra's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5677 +Name = "a tortoise's nest" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5678 +Name = "a tortoise egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 5679 +Name = "a shell" +Flags = {Unmove} + +TypeID = 5680 +Name = "a spiral shell" +Flags = {Unmove} + +TypeID = 5681 +Name = "some shells" +Flags = {Unmove} + +TypeID = 5682 +Name = "a piece of a shell" +Flags = {Unmove} + +TypeID = 5683 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5684 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5685 +Name = "a dirt wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5686 +Name = "a stone wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5687 +Name = "a dry griffinclaw" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5688 +Name = "a dead blood crab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=5689,TotalExpireTime=900} + +TypeID = 5689 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=5690,TotalExpireTime=600} + +TypeID = 5690 +Name = "a dead blood crab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5691 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5692 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5693 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5694 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5695 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5696 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5697 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5698 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5699 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5700 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5701 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5702 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5703 +Name = "a ballista" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5704 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5705 +Name = "a shark fin" +Flags = {Unmove,Unlay} + +TypeID = 5706 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Take} +Attributes = {Weight=830} + +TypeID = 5707 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5708 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5709 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5710 +Name = "a light shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 5711 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5712 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5713 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5714 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5715 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5716 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5717 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5718 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5719 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5720 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5721 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5722 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5723 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5724 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5725 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5726 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 5727 +Name = "a dead seagull" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5728,TotalExpireTime=600} + +TypeID = 5728 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5729,TotalExpireTime=600} + +TypeID = 5729 +Name = "a dead seagull" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5730 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2480} + +TypeID = 5731 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 5732 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5733 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5734 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5735 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5736 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5737 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5738 +Name = "a shark fin" +Flags = {Unmove} + +TypeID = 5739 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5740 +Name = "an antic well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5741 +Name = "a skull helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=9} + +TypeID = 5742 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2482} + +TypeID = 5743 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5744 +Name = "wooden floor" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5745 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5746 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5747 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5748 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 5749 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5750 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5751 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5752 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5753 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5754 +Name = "an earth pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5755 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5756 +Name = "a turtle" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 5757 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5758 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5759 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5760 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5761 +Name = "a turtle" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5762 +Name = "a dead quara constrictor" +Flags = {Container,Corpse} +Attributes = {Capacity=10,FluidSource=BLOOD} + +TypeID = 5763 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 5764 +Name = "a ventilation grille" +Flags = {Bank,Unmove,CollisionEvent} +Attributes = {Waypoints=500} + +TypeID = 5765 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5766,TotalExpireTime=600} + +TypeID = 5766 +Name = "a dead toad" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5767,TotalExpireTime=600} + +TypeID = 5767 +Name = "a dead toad" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5768 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5769 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5770 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 5771 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5772 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5773 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5774 +Name = "metal fittings" +Flags = {Clip,Unmove} + +TypeID = 5775 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2481} + +TypeID = 5776 +Name = "a Tibianus talon" +Description = "Rumours say that the Gods enchanted these talons for the greatest good, or the greatest evil achievements" +Flags = {Cumulative,Take,Disguise} +Attributes = {DisguiseTarget=3034,Weight=20} + +TypeID = 5777 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5778 +Name = "a target board" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5779 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5780 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5781 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5782 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5783 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5784 +Name = "a stuck arrow" +Flags = {Unmove} + +TypeID = 5785 +Name = "a medal of honour" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1000} + +TypeID = 5786 +Name = "a wooden whistle" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5787 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5788 +Name = "a training dummy" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5789 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5790 +Name = "a stuck axe" +Flags = {Unmove} + +TypeID = 5791 +Name = "a stuffed dragon" +Flags = {UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 5792 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5793 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5794 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5795 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5796 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5797 +Name = "a dice" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 5798 +Name = "an abacus" +Description = "If you use it wisely, you may write yourself into the history books" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5799 +Name = "a golden figurine" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5800 +Name = "a grappling hook" +Flags = {Unmove} + +TypeID = 5801 +Name = "a key ring" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=50} + +TypeID = 5802 +Name = "a message in a bottle" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 5803 +Name = "an arbalest" +Description = "It is a bit heavy due to the iron mounting, but very precise" +Flags = {Take,Distance} +Attributes = {Weight=9500,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 5804 +Name = "a nose ring" +Description = "It was the favourite trinket of the famous Horned Fox" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5805 +Name = "a golden goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5806 +Name = "a silver goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5807 +Name = "a bronze goblet" +Flags = {Text,WriteOnce,Take} +Attributes = {MaxLength=99,Weight=1500} + +TypeID = 5808 +Name = "Orshabaal's brain" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5809 +Name = "a soul stone" +Description = "It contains the essence of countless tormented souls" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 5810 +Name = "a voodoo doll" +Description = "It looks like a small pirate" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 5811 +Name = "a mermaid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5812 +Name = "a skull candle" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=5813,Weight=2200,Brightness=3,LightColor=206,ExpireTarget=3114,TotalExpireTime=3000} + +TypeID = 5813 +Name = "a skull candle" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=5812,Weight=2200,Brightness=0,LightColor=215} + +TypeID = 5814 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unpass,Unmove} + +TypeID = 5815 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 5816 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5817 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5818 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5819 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5820 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5821 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5822 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5823 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5824 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5825 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5826 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5827 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Clip,Unmove} + +TypeID = 5828 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5829 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5830 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5831 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5832 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5833 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5834 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5835 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5836 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5837 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5838 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5839 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 5840 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5841 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5842 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5843 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5844 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5845 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5846 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5847 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5848 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5849 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5850 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5851 +Name = "small lava cracks" +Description = "The ground is steaming hot" +Flags = {Unmove} + +TypeID = 5852 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5853 +Name = "a weapon rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5854 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5855 +Name = "a lance" +Flags = {Unmove} + +TypeID = 5856 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5857 +Name = "a sword" +Flags = {Unmove} + +TypeID = 5858 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5859 +Name = "a scimitar" +Flags = {Unmove} + +TypeID = 5860 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5861 +Name = "an armour rack" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5862 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5863 +Name = "an armour rack" +Flags = {Unmove} + +TypeID = 5864 +Name = "" # this is nothing in client + +TypeID = 5865 +Name = "a juice squeezer" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 5866 +Name = "rubble" +Flags = {Top,Unpass,Unmove,Unlay} + +TypeID = 5867 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5868 +Name = "rubble" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5869 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5870 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5871 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5872 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5873 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5874 +Name = "a poison trickle" +Flags = {Unmove} + +TypeID = 5875 +Name = "sniper gloves" +Description = "They are the pride of the paladin guild" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 5876 +Name = "a lizard leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5877 +Name = "a green dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5878 +Name = "a minotaur leather" +Flags = {Cumulative,Take} +Attributes = {Weight=40} + +TypeID = 5879 +Name = "a spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5880 +Name = "an iron ore" +Flags = {Cumulative,Take} +Attributes = {Weight=200} + +TypeID = 5881 +Name = "a lizard scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5882 +Name = "a red dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 5883 +Name = "an ape fur" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5884 +Name = "a spirit container" +Description = "It contains pure fighting spirit" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 5885 +Name = "a flask of warrior's sweat" +Description = "It contains the sweat spilled in many battles and is said to be used for certain perfumes too" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 5886 +Name = "a spool of yarn" +Description = "It is made from fine spider silk" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5887 +Name = "a piece of royal steel" +Description = "Even the king would be proud to wear an armour made of this refined steel" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5888 +Name = "a piece of hell steel" +Description = "This rare metal must have been refined in the depths" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5889 +Name = "a piece of draconian steel" +Description = "An armour made of this steel is said to protect against fiery dragon breath" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5890 +Name = "a chicken feather" +Description = "Some thousands of these would probably make an extremely comfortable pillow" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 5891 +Name = "an enchanted chicken wing" +Description = "It is said to make your feet fly" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5892 +Name = "a huge chunk of crude iron" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 5893 +Name = "a perfect behemoth fang" +Description = "Collectors all around the world crave for this item" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5894 +Name = "a bat wing" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5895 +Name = "a fish fin" +Description = "It once belonged to a mighty creature of the deep" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5896 +Name = "a bear paw" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 5897 +Name = "a wolf paw" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5898 +Name = "a beholder's eye" +Description = "You could swear it just winked at you" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5899 +Name = "a turtle shell" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5900 +Name = "a dwarven beard" +Description = "It was once worn proudly by a dwarfish warrior - or maiden" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 5901 +Name = "wood" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5902 +Name = "a honeycomb" +Description = "Some people swear it makes an excellent glue" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5903 +Name = "Ferumbras' hat" +Description = "It is the proof that Ferumbras has fallen. For now. The Edron Academy should be interested in this" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5904 +Name = "a magic sulphur" +Description = "It smells rather badly but is said to be an important catalyst for magical rituals" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5905 +Name = "a vampire dust" +Description = "Sun can be a merciless killer, but so can you" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5906 +Name = "a demon dust" +Description = "It reeks of hatred and malice" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5907 +Name = "a slingshot" +Description = "A hermit near Carlin might be able to tell you more about it" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5908 +Name = "an obsidian knife" +Description = "Sharp and light, this is a useful tool for tanners, doctors and assassins" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 5909 +Name = "a white piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5910 +Name = "a green piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5911 +Name = "a red piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5912 +Name = "a blue piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5913 +Name = "a brown piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5914 +Name = "a yellow piece of cloth" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 5915 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5916 +Name = "a large throne" +Flags = {Unmove,Avoid} + +TypeID = 5917 +Name = "a bandana" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 5918 +Name = "pirate knee breeches" +Description = "It is quite fashionable as well as useful for anyone spending huge amounts of time in the sun" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=LEGS,ArmorValue=1} + +TypeID = 5919 +Name = "a dragon claw" +Description = "It is the claw of Demodras" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 5920 +Name = "a green dragon scale" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 5921 +Name = "a heaven blossom" +Flags = {Cumulative,Take} +Attributes = {Weight=210} + +TypeID = 5922 +Name = "a holy orchid" +Flags = {Cumulative,Take} +Attributes = {Weight=250} + +TypeID = 5923 +Name = "" # this is nothing in client + +TypeID = 5924 +Name = "a damaged steel helmet" +Description = "The words 'Ramsay the Reckless' are engraved inside. It appears to be cracked and broken" +Flags = {Take} +Attributes = {Weight=4600} + +TypeID = 5925 +Name = "a hardened bone" +Flags = {Cumulative,Take} +Attributes = {Weight=600} + +TypeID = 5926 +Name = "a pirate backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5927 +Name = "a pirate bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5928 +Name = "an empty goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5929 +Name = "a goldfish bowl" +Flags = {Take} +Attributes = {Weight=3200} + +TypeID = 5930 +Name = "a behemoth claw" +Flags = {Cumulative,Take} +Attributes = {Weight=1250} + +TypeID = 5931 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5932,TotalExpireTime=900} + +TypeID = 5932 +Name = "the remains of ferumbras" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5933,TotalExpireTime=600} + +TypeID = 5933 +Name = "the remains of ferumbras" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5934 +Name = "a dead frog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=1000,FluidSource=BLOOD,ExpireTarget=5935,TotalExpireTime=600} + +TypeID = 5935 +Name = "a dead frog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=800,ExpireTarget=5936,TotalExpireTime=600} + +TypeID = 5936 +Name = "a dead frog" +Flags = {Corpse,Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 5937 +Name = "a botanist's container" +Description = "It holds a sample of the rare griffinclaw flower" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 5938 +Name = "Ceiron's waterskin" +Description = "It is empty" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5939 +Name = "Ceiron's waterskin" +Description = "It contains a special sample of water from a hydra cave" +Flags = {Take} +Attributes = {Weight=700} + +TypeID = 5940 +Name = "Ceiron's wolf tooth chain" +Description = "It has the letter 'C' carved into one of the teeth" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 5941 +Name = "a wooden stake" +Description = "It is a simple wooden stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5942 +Name = "a blessed wooden stake" +Description = "Many mighty priests have blessed this stake" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 5943 +Name = "Morgaroth's heart" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 5944 +Name = "a soul orb" +Description = "This strange object seems to be made of half spirit, half metal. It is unknown to most smiths" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 5945 +Name = "a coral comb" +Description = "It once belonged to a mermaid" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5946 +Name = "a comb" +Flags = {Take} +Attributes = {Weight=450} + +TypeID = 5947 +Name = "Elane's crossbow" +Description = "'For Elane, with Love' is engraved on it" +Flags = {Take} +Attributes = {Weight=4000} + +TypeID = 5948 +Name = "a red dragon leather" +Flags = {Cumulative,Take} +Attributes = {Weight=60} + +TypeID = 5949 +Name = "a beach backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 5950 +Name = "a beach bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 5951 +Name = "a fish tail" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 5952 +Name = "a poem scroll" +Description = "It contains a love poem, written by an unknown elven poet" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5953 +Name = "a raven herb" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 5954 +Name = "a demon horn" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 5955 +Name = "" # this is nothing in client + +TypeID = 5956 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 5957 +Name = "a lottery ticket" +Description = "It has not been used yet" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5958 +Name = "a winning lottery ticket" +Description = "You were lucky! Go claim your prize at the potion store in Edron" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 5959 +Name = "" # this is nothing in client + +TypeID = 5960 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=3987,TotalExpireTime=10} + +TypeID = 5961 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=SLIME,ExpireTarget=3988,TotalExpireTime=10} + +TypeID = 5962 +Name = "a dead cyclops" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=3989,TotalExpireTime=10} + +TypeID = 5963 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=3990,TotalExpireTime=10} + +TypeID = 5964 +Name = "a dead rat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=3994,TotalExpireTime=10} + +TypeID = 5965 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 5966 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4001,TotalExpireTime=10} + +TypeID = 5967 +Name = "a dead rotworm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4005,TotalExpireTime=10} + +TypeID = 5968 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4007,TotalExpireTime=10} + +TypeID = 5969 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4011,TotalExpireTime=10} + +TypeID = 5970 +Name = "a dead deer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4016,TotalExpireTime=10} + +TypeID = 5971 +Name = "a dead dog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4020,TotalExpireTime=10} + +TypeID = 5972 +Name = "a slain skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4024,TotalExpireTime=10} + +TypeID = 5973 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4025,TotalExpireTime=10} + +TypeID = 5974 +Name = "a dead spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4029,TotalExpireTime=10} + +TypeID = 5975 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4030,TotalExpireTime=10} + +TypeID = 5976 +Name = "a slain ghoul" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4034,TotalExpireTime=10} + +TypeID = 5977 +Name = "a dead giant spider" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4038,TotalExpireTime=10} + +TypeID = 5978 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4041,TotalExpireTime=10} + +TypeID = 5979 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4043,TotalExpireTime=10} + +TypeID = 5980 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4045,TotalExpireTime=10} + +TypeID = 5981 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4047,TotalExpireTime=10} + +TypeID = 5982 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4052,TotalExpireTime=10} + +TypeID = 5983 +Name = "a dead minotaur" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4057,TotalExpireTime=10} + +TypeID = 5984 +Name = "a dead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4062,TotalExpireTime=10} + +TypeID = 5985 +Name = "a dead fire devil" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4067,TotalExpireTime=10} + +TypeID = 5986 +Name = "a dead lion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4070,TotalExpireTime=10} + +TypeID = 5987 +Name = "a dead bear" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4074,TotalExpireTime=10} + +TypeID = 5988 +Name = "a dead scorpion" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4078,TotalExpireTime=10} + +TypeID = 5989 +Name = "a dead wasp" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4080,TotalExpireTime=10} + +TypeID = 5990 +Name = "a dead bug" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4083,TotalExpireTime=10} + +TypeID = 5991 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4086,TotalExpireTime=10} + +TypeID = 5992 +Name = "a dead beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4089,TotalExpireTime=10} + +TypeID = 5993 +Name = "remains of a ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4094,TotalExpireTime=10} + +TypeID = 5994 +Name = "a dead sheep" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4095,TotalExpireTime=10} + +TypeID = 5995 +Name = "a slain demon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4097,TotalExpireTime=10} + +TypeID = 5996 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4101,TotalExpireTime=10} + +TypeID = 5997 +Name = "a dead wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4105,TotalExpireTime=10} + +TypeID = 5998 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4109,TotalExpireTime=10} + +TypeID = 5999 +Name = "a dead behemoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4112,TotalExpireTime=10} + +TypeID = 6000 +Name = "a dead pig" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4116,TotalExpireTime=10} + +TypeID = 6001 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4119,TotalExpireTime=10} + +TypeID = 6002 +Name = "a dead goblin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4121,TotalExpireTime=10} + +TypeID = 6003 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4126,TotalExpireTime=10} + +TypeID = 6004 +Name = "remains of a mummy" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4130,TotalExpireTime=10} + +TypeID = 6005 +Name = "a split stone golem" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4133,TotalExpireTime=10} + +TypeID = 6006 +Name = "a slain vampire" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4137,TotalExpireTime=10} + +TypeID = 6007 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4141,TotalExpireTime=10} + +TypeID = 6008 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4148,TotalExpireTime=10} + +TypeID = 6009 +Name = "a dead war wolf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4150,TotalExpireTime=10} + +TypeID = 6010 +Name = "a dead orc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4153,TotalExpireTime=10} + +TypeID = 6011 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4160,TotalExpireTime=10} + +TypeID = 6012 +Name = "a dead elf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4162,TotalExpireTime=10} + +TypeID = 6013 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4164,TotalExpireTime=10} + +TypeID = 6014 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4166,TotalExpireTime=10} + +TypeID = 6015 +Name = "a dead dwarf" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4168,TotalExpireTime=10} + +TypeID = 6016 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4170,TotalExpireTime=10} + +TypeID = 6017 +Name = "a dead rabbit" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4173,TotalExpireTime=10} + +TypeID = 6018 +Name = "a dead troll" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4176,TotalExpireTime=10} + +TypeID = 6019 +Name = "a slain banshee" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4179,TotalExpireTime=10} + +TypeID = 6020 +Name = "a dead djinn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4182,TotalExpireTime=10} + +TypeID = 6021 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4185,TotalExpireTime=10} + +TypeID = 6022 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6023 +Name = "a dead larva" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4191,TotalExpireTime=10} + +TypeID = 6024 +Name = "a dead scarab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4194,TotalExpireTime=10} + +TypeID = 6025 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4197,TotalExpireTime=10} + +TypeID = 6026 +Name = "a dead hyaena" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4200,TotalExpireTime=10} + +TypeID = 6027 +Name = "a dead gargoyle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4203,TotalExpireTime=10} + +TypeID = 6028 +Name = "a slain lich" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4206,TotalExpireTime=10} + +TypeID = 6029 +Name = "a slain crypt shambler" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4209,TotalExpireTime=10} + +TypeID = 6030 +Name = "a slain bonebeast" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4212,TotalExpireTime=10} + +TypeID = 6031 +Name = "a dead pharaoh" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4215,TotalExpireTime=10} + +TypeID = 6032 +Name = "a dead efreet" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4218,TotalExpireTime=10} + +TypeID = 6033 +Name = "a dead marid" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4221,TotalExpireTime=10} + +TypeID = 6034 +Name = "a dead badger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4224,TotalExpireTime=10} + +TypeID = 6035 +Name = "a dead skunk" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4227,TotalExpireTime=10} + +TypeID = 6036 +Name = "a dead gazer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4230,TotalExpireTime=10} + +TypeID = 6037 +Name = "a dead elder beholder" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4233,TotalExpireTime=10} + +TypeID = 6038 +Name = "a dead yeti" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4236,TotalExpireTime=10} + +TypeID = 6039 +Name = "a dead crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4318,TotalExpireTime=10} + +TypeID = 6040 +Name = "a dead lizard sentinel" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4324,TotalExpireTime=10} + +TypeID = 6041 +Name = "a dead lizard snakecharmer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4327,TotalExpireTime=10} + +TypeID = 6042 +Name = "a dead chicken" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4330,TotalExpireTime=10} + +TypeID = 6043 +Name = "a dead kongra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4333,TotalExpireTime=10} + +TypeID = 6044 +Name = "a dead merlkin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4336,TotalExpireTime=10} + +TypeID = 6045 +Name = "a dead sibang" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4339,TotalExpireTime=10} + +TypeID = 6046 +Name = "a dead crocodile" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4342,TotalExpireTime=10} + +TypeID = 6047 +Name = "a dead carniphila" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4345,TotalExpireTime=10} + +TypeID = 6048 +Name = "a dead hydra" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4348,TotalExpireTime=10} + +TypeID = 6049 +Name = "a dead panda" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4351,TotalExpireTime=10} + +TypeID = 6050 +Name = "a dead centipede" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4354,TotalExpireTime=10} + +TypeID = 6051 +Name = "a dead tiger" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4357,TotalExpireTime=10} + +TypeID = 6052 +Name = "a dead elephant" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4360,TotalExpireTime=10} + +TypeID = 6053 +Name = "a dead bat" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4363,TotalExpireTime=10} + +TypeID = 6054 +Name = "a dead flamingo" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4366,TotalExpireTime=10} + +TypeID = 6055 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4369,TotalExpireTime=10} + +TypeID = 6056 +Name = "a dead parrot" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4379,TotalExpireTime=10} + +TypeID = 6057 +Name = "a dead bird" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4382,TotalExpireTime=10} + +TypeID = 6058 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4372,TotalExpireTime=10} + +TypeID = 6059 +Name = "a dead dworc" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4375,TotalExpireTime=10} + +TypeID = 6060 +Name = "a dead tarantula" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4385,TotalExpireTime=10} + +TypeID = 6061 +Name = "a dead serpent spawn" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4388,TotalExpireTime=10} + +TypeID = 6062 +Name = "a lifeless nettle" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4391,TotalExpireTime=10} + +TypeID = 6063 +Name = "a dead quara pincher" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5521,TotalExpireTime=10} + +TypeID = 6064 +Name = "a dead quara mantassin" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5522,TotalExpireTime=10} + +TypeID = 6065 +Name = "a dead quara constrictor" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5523,TotalExpireTime=10} + +TypeID = 6066 +Name = "a dead quara hydromancer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5524,TotalExpireTime=10} + +TypeID = 6067 +Name = "a dead quara predator" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5525,TotalExpireTime=10} + +TypeID = 6068 +Name = "demon dust" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=24,FluidSource=BLOOD,ExpireTarget=5526,TotalExpireTime=10} + +TypeID = 6069 +Name = "a dead carrion worm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5539,TotalExpireTime=10} + +TypeID = 6070 +Name = "a slain pirate skeleton" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5564,TotalExpireTime=10} + +TypeID = 6071 +Name = "a slain pirate ghost" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=5565,TotalExpireTime=10} + +TypeID = 6072 +Name = "a dead tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5624,TotalExpireTime=10} + +TypeID = 6073 +Name = "a dead thornback tortoise" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=6,FluidSource=BLOOD,ExpireTarget=5627,TotalExpireTime=10} + +TypeID = 6074 +Name = "a dead mammoth" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=5665,TotalExpireTime=10} + +TypeID = 6075 +Name = "a dead blood crab" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5688,TotalExpireTime=10} + +TypeID = 6076 +Name = "a dead seagull" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5727,TotalExpireTime=10} + +TypeID = 6077 +Name = "a dead toad" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5765,TotalExpireTime=10} + +TypeID = 6078 +Name = "remains of Ferumbras" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=5931,TotalExpireTime=10} + +TypeID = 6079 +Name = "a dead frog" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=5934,TotalExpireTime=10} + +TypeID = 6080 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4240,TotalExpireTime=10} + +TypeID = 6081 +Name = "a dead human" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4247,TotalExpireTime=10} + +TypeID = 6082 +Name = "a dead human" + +TypeID = 6083 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 6084 +Name = "a dead human" +Flags = {Container,Expire} +Attributes = {Capacity=10,ExpireTarget=6082,TotalExpireTime=120} + +TypeID = 6085 +Name = "a large trunk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6086 +Name = "a faked label" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 6087 +Name = "a music sheet" +Description = "It contains the first verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6088 +Name = "a music sheet" +Description = "It contains the second verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6089 +Name = "a music sheet" +Description = "It contains the third verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6090 +Name = "a music sheet" +Description = "It contains the fourth and last verse of a hymn" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6091 +Name = "a very noble-looking watch" +Description = "Unfortunately it seems to be broken" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6092 +Name = "a very noble-looking watch" +Flags = {Take,Expire} +Attributes = {Weight=50,ExpireTarget=6091,TotalExpireTime=259200} + +TypeID = 6093 +Name = "a crystal ring" +Description = "The initials E.S. are engraved on it" +Flags = {Take} +Attributes = {Weight=90,SlotType=RING} + +TypeID = 6094 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6095 +Name = "a pirate shirt" +Flags = {Take,Armor} +Attributes = {Weight=2000,SlotType=BODY,ArmorValue=3} + +TypeID = 6096 +Name = "a pirate hat" +Flags = {Take,Armor} +Attributes = {Weight=1250,SlotType=HEAD,ArmorValue=3} + +TypeID = 6097 +Name = "a hook" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6098 +Name = "an eye patch" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=150} + +TypeID = 6099 +Name = "Brutus Bloodbeard's hat" +Flags = {Take} +Attributes = {Weight=1250} + +TypeID = 6100 +Name = "the Lethal Lissy's shirt" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 6101 +Name = "Ron the Ripper's sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 6102 +Name = "Deadeye Devious' eye patch" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6103 +Name = "an unholy boo" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 6104 +Name = "a jewel case" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=170} + +TypeID = 6105 +Name = "Striker's favourite pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 6106 +Name = "a bottle of whisper beer" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 6107 +Name = "a staff" +Description = "It must be the one which Simon the Beggar talked about" +Flags = {Take} +Attributes = {Weight=3800} + +TypeID = 6108 +Name = "an atlas" +Description = "It is filled with detailed maps" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 6109 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6110} + +TypeID = 6110 +Name = "a weapon rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6109} + +TypeID = 6111 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6112} + +TypeID = 6112 +Name = "an armor rack" +Flags = {Container,Unpass,Unlay,Rotate} +Attributes = {Capacity=20,RotateTarget=6111} + +TypeID = 6113 +Name = "a letter to Eremo" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 6114 +Name = "an armor rack kit" +Description = "Use it in your house to construct an armor rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6115 +Name = "a weapon rack kit" +Description = "Use it in your house to construct a weapon rack" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 6116 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6117 +Name = "electric sparks" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=137} + +TypeID = 6118 +Name = "a treasure map" +Description = "It obviously shows Treasure Island including a big, red cross" +Flags = {Unmove} + +TypeID = 6119 +Name = "a scroll" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 6120 +Name = "Dragha's spellbook" +Description = "It apparently belonged to someone called Dragha, the apprentice of a voodoomaster" +Flags = {Take} +Attributes = {Weight=5800} + +TypeID = 6121 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6122 +Name = "a note pinned on the wall" +Flags = {Unmove} + +TypeID = 6123 +Name = "a piano" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6124 +Name = "a damaged logbook" +Description = "It must be the one which the explorer society requested" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 6125 +Name = "a tortoise egg from Nargor" +Description = "Handle with care and don't try to eat it" +Flags = {Take} +Attributes = {Nutrition=8,Weight=30} + +TypeID = 6126 +Name = "a peg leg" +Description = "It belonged once to a pirate" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 6127 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6128 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6129 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6130 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 6131 +Name = "a tortoise shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=26} + +TypeID = 6132 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6133 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6134 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6135 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6136 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6137 +Name = "a grass roof" +Flags = {Unmove} + +TypeID = 6138 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6139 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6140 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6141 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6142 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6143 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6144 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6145 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6146 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6147 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6148 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6149 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6150 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6151 +Name = "a tree wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6152 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6153 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6154 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6155 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6156 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6157 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6158 +Name = "a tree wall" +Flags = {Clip,Unmove} + +TypeID = 6159 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6160 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6161 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6162 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6163 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6164 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6165 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6166 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6167 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6168 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6169 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6170 +Name = "a grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6171 +Name = "a grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 6172 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 6173 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 6174 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6175 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6176 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6177 +Name = "a tree hole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6178 +Name = "a tree hole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6179 +Name = "a tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6180 +Name = "a tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 6181 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6182 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6183 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6184 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6185 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6186 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6187 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6188 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6189 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6190 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6191 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6192 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6193 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6194 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6195 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6196 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6197 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6198 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6199 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6200 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6201 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6202 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6203 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6204 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6205 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6206 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6207 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6208 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6209 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6210 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6211 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6212 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6213 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6214 +Name = "a rope bridge" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6215 +Name = "grass" +Flags = {Unmove} + +TypeID = 6216 +Name = "grass" +Flags = {Unmove} + +TypeID = 6217 +Name = "grass" +Flags = {Unmove} + +TypeID = 6218 +Name = "grass" +Flags = {Unmove} + +TypeID = 6219 +Name = "twines" +Flags = {Unmove} + +TypeID = 6220 +Name = "twines" +Flags = {Unmove} + +TypeID = 6221 +Name = "twines" +Flags = {Unmove} + +TypeID = 6222 +Name = "twines" +Flags = {Unmove} + +TypeID = 6223 +Name = "twines" +Flags = {Unmove} + +TypeID = 6224 +Name = "twines" +Flags = {Unmove} + +TypeID = 6225 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6226 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6227 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6228 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6229 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6230 +Name = "flowers" +Flags = {Unmove} + +TypeID = 6231 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6232 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6233 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6234 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6235 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6236 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6237 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6238 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6239 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6240 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6241 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6242 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6243 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6244 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6245 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6246 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6247 +Name = "a tendril wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6248 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6249 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6250 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6251 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6252 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6253 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6254 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6255 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6256 +Name = "a closed door" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6257 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 6258 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6259 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6260 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6261 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 6262 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6263 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6264 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6265 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 6266 +Name = "metal trash" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6267 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6268 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6269 +Name = "tendrils" +Flags = {Unpass,Unmove} + +TypeID = 6270 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6271 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6272 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6273 +Name = "some leaves" +Flags = {Unmove} + +TypeID = 6274 +Name = "a tree archway" +Flags = {Top,Unmove} + +TypeID = 6275 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 6276 +Name = "a lump of cake dough" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6277 +Name = "a cake" +Flags = {UseEvent,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 6278 +Name = "a cake" +Description = "It is nicely decorated with fruits and icing" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 6279 +Name = "a party cake" +Description = "It is nicely decorated with fruits, icing and a candle. Someone is caring about you" +Flags = {UseEvent,Take} +Attributes = {Weight=500} + +TypeID = 6280 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6281 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6282 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6283 +Name = "a broken brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6284 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6285 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6286 +Name = "" # this is nothing in client + +TypeID = 6287 +Name = "" # this is nothing in client + +TypeID = 6288 +Name = "a burning wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6289 +Name = "a burning wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6290 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6291 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6292 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6293 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6294 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6295 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6296 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6297 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6298 +Name = "some cracks" +Flags = {Clip,Unmove} + +TypeID = 6299 +Name = "a death ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=6300} + +TypeID = 6300 +Name = "a death ring" +Description = "Wearing it makes you feel a little weaker than usual" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ShieldBoost=-10,ExpireTarget=0,TotalExpireTime=480,DeEquipTarget=6299} + +TypeID = 6301 +Name = "a dead wyvern" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6302,TotalExpireTime=10} + +TypeID = 6302 +Name = "a dead wyvern" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6303,TotalExpireTime=900} + +TypeID = 6303 +Name = "a dead wyvern" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6304,TotalExpireTime=600} + +TypeID = 6304 +Name = "a dead wyvern" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6305 +Name = "a slain undead dragon" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6306,TotalExpireTime=10} + +TypeID = 6306 +Name = "a slain undead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6307,TotalExpireTime=900} + +TypeID = 6307 +Name = "a slain undead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=24,ExpireTarget=6308,TotalExpireTime=600} + +TypeID = 6308 +Name = "a slain undead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6309 +Name = "a slain lost soul" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6318,TotalExpireTime=900} + +TypeID = 6310 +Name = "a slain lost soul" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6309,TotalExpireTime=10} + +TypeID = 6311 +Name = "a slain hand of cursed fate" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6312,TotalExpireTime=10} + +TypeID = 6312 +Name = "a slain hand of cursed fate" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6313,TotalExpireTime=900} + +TypeID = 6313 +Name = "a slain hand of cursed fate" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6314,TotalExpireTime=600} + +TypeID = 6314 +Name = "a slain hand of cursed fate" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6315 +Name = "a slain betrayed wraith" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6316,TotalExpireTime=10} + +TypeID = 6316 +Name = "a slain betrayed wraith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6317,TotalExpireTime=900} + +TypeID = 6317 +Name = "a slain betrayed wraith" +Flags = {Corpse,Expire} +Attributes = {FluidSource=BLOOD,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6318 +Name = "a slain skeleton" +Flags = {Corpse,Expire} +Attributes = {FluidSource=BLOOD,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6319 +Name = "a dead destroyer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6320,TotalExpireTime=10} + +TypeID = 6320 +Name = "a slain destroyer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6321,TotalExpireTime=900} + +TypeID = 6321 +Name = "a dead destroyer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6322,TotalExpireTime=600} + +TypeID = 6322 +Name = "a dead destroyer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6323 +Name = "elemental ashes" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6324,TotalExpireTime=10} + +TypeID = 6324 +Name = "elemental ashes" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6325,TotalExpireTime=900} + +TypeID = 6325 +Name = "elemental ashes" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6326,TotalExpireTime=600} + +TypeID = 6326 +Name = "elemental ashes" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6327 +Name = "a dead torturer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6328,TotalExpireTime=900} + +TypeID = 6328 +Name = "a dead torturer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6329,TotalExpireTime=600} + +TypeID = 6329 +Name = "a dead torturer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6330 +Name = "a dead torturer" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6327,TotalExpireTime=10} + +TypeID = 6331 +Name = "a dead hellhound" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=20,FluidSource=BLOOD,ExpireTarget=6332,TotalExpireTime=10} + +TypeID = 6332 +Name = "a dead hellhound" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=20,ExpireTarget=6333,TotalExpireTime=900} + +TypeID = 6333 +Name = "a dead hellhound" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=20,ExpireTarget=6334,TotalExpireTime=600} + +TypeID = 6334 +Name = "a dead hellhound" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6335 +Name = "a dead juggernaut" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=25,FluidSource=BLOOD,ExpireTarget=6336,TotalExpireTime=10} + +TypeID = 6336 +Name = "a dead juggernaut" +Flags = {Container,Expire} +Attributes = {Capacity=25,ExpireTarget=6337,TotalExpireTime=900} + +TypeID = 6337 +Name = "a dead juggernaut" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=25,ExpireTarget=6338,TotalExpireTime=600} + +TypeID = 6338 +Name = "a dead juggernaut" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6339 +Name = "a dead nightmare" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6340,TotalExpireTime=10} + +TypeID = 6340 +Name = "a dead nightmare" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6341,TotalExpireTime=900} + +TypeID = 6341 +Name = "a dead nightmare" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6342,TotalExpireTime=600} + +TypeID = 6342 +Name = "a dead nightmare" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6343 +Name = "remains of a phantasm" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6344,TotalExpireTime=10} + +TypeID = 6344 +Name = "remains of a phantasm" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=10000,ExpireTarget=6345,TotalExpireTime=600} + +TypeID = 6345 +Name = "remains of a phantasm" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=8000,ExpireTarget=6346,TotalExpireTime=600} + +TypeID = 6346 +Name = "remains of a phantasm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6347 +Name = "remains of a spectre" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=6348,TotalExpireTime=10} + +TypeID = 6348 +Name = "remains of a spectre" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=10000,ExpireTarget=6349,TotalExpireTime=900} + +TypeID = 6349 +Name = "remains of a spectre" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=8000,ExpireTarget=6350,TotalExpireTime=600} + +TypeID = 6350 +Name = "remains of a spectre" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6351 +Name = "" # this is nothing in client + +TypeID = 6352 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 6353 +Name = "a slain blightwalker" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=6354,TotalExpireTime=10} + +TypeID = 6354 +Name = "a slain blightwalker" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6355 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6356,RotateTarget=6357,Brightness=3,LightColor=199} + +TypeID = 6356 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6355,RotateTarget=6358} + +TypeID = 6357 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6358,RotateTarget=6359,Brightness=3,LightColor=193} + +TypeID = 6358 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6357,RotateTarget=6360} + +TypeID = 6359 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6360,RotateTarget=6361,Brightness=3,LightColor=193} + +TypeID = 6360 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6359,RotateTarget=6362} + +TypeID = 6361 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6362,RotateTarget=6355,Brightness=3,LightColor=193} + +TypeID = 6362 +Name = "an oven" +Flags = {ChangeUse,Rotate,Height} +Attributes = {ChangeTarget=6361,RotateTarget=6356} + +TypeID = 6363 +Name = "a dead diabolic imp" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6364,TotalExpireTime=10} + +TypeID = 6364 +Name = "a dead diabolic imp" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=6365,TotalExpireTime=900} + +TypeID = 6365 +Name = "a dead diabolic imp" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=6366,TotalExpireTime=600} + +TypeID = 6366 +Name = "a dead diabolic imp" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6367 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6368} + +TypeID = 6368 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6369} + +TypeID = 6369 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6370} + +TypeID = 6370 +Name = "a bookcase" +Flags = {Container,Unpass,Rotate} +Attributes = {Capacity=6,RotateTarget=6367} + +TypeID = 6371 +Name = "an oven kit" +Description = "Use it in your house to construct an oven" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 6372 +Name = "a bookcase kit" +Description = "Use it in your house to construct a bookcase" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 6373 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6374 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6375 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6376 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6377 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6378 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6379 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6380 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6381 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6382 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6383 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6384 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6385 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6386 +Name = "a greeting card" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=100} + +TypeID = 6387 +Name = "a christmas card" +Flags = {UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 6388 +Name = "stony floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 6389 +Name = "an ornamented fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 6390 +Name = "a nightmare shield" +Description = "It was crafted by the ancient order of the nightmare knights" +Flags = {Take,Shield} +Attributes = {Weight=3200,Defense=37,Brightness=4,LightColor=186} + +TypeID = 6391 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 6392 +Name = "a valentine's cake" +Flags = {Take} +Attributes = {Nutrition=15,Weight=500} + +TypeID = 6393 +Name = "a cream cake" +Flags = {Take} +Attributes = {Nutrition=15,Weight=500} + +TypeID = 6394 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6395 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6396 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6397 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6398 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6399 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6400 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6401 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6402 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6403 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6404 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6405 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6406 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6407 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6408 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6409 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6410 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6411 +Name = "bones" +Flags = {Top,Unmove} + +TypeID = 6412 +Name = "a bone pillar" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6413 +Name = "bones" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6414 +Name = "bones" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 6415 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6416 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6417 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6418 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6419 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6420 +Name = "bones" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 6421 +Name = "bones" +Flags = {Unpass,Unmove} + +TypeID = 6422 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6423 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6424 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6425 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6426 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6427 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6428 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6429 +Name = "a large branch" +Flags = {Top,Unmove} + +TypeID = 6430 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6431 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6432 +Name = "a necromancer shield" +Description = "It is enchanted with unholy, necromantic powers" +Flags = {Take,Shield} +Attributes = {Weight=3200,Defense=37,Brightness=4,LightColor=103} + +TypeID = 6433 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 6434 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 6435 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6437} + +TypeID = 6436 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6438} + +TypeID = 6437 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6435} + +TypeID = 6438 +Name = "a framework window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6436} + +TypeID = 6439 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6441} + +TypeID = 6440 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6442} + +TypeID = 6441 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6439} + +TypeID = 6442 +Name = "a brick window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6440} + +TypeID = 6443 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6445} + +TypeID = 6444 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6446} + +TypeID = 6445 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6443} + +TypeID = 6446 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6444} + +TypeID = 6447 +Name = "a marble window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6448 +Name = "a marble window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 6449 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6451} + +TypeID = 6450 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6452} + +TypeID = 6451 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6449} + +TypeID = 6452 +Name = "a tree window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6450} + +TypeID = 6453 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6455} + +TypeID = 6454 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6456} + +TypeID = 6455 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6453} + +TypeID = 6456 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6454} + +TypeID = 6457 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6459} + +TypeID = 6458 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6460} + +TypeID = 6459 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6457} + +TypeID = 6460 +Name = "a bamboo window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6458} + +TypeID = 6461 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6463} + +TypeID = 6462 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6464} + +TypeID = 6463 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6461} + +TypeID = 6464 +Name = "a sandstone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6462} + +TypeID = 6465 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6467} + +TypeID = 6466 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6468} + +TypeID = 6467 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6465} + +TypeID = 6468 +Name = "a stone window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6466} + +TypeID = 6469 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6471} + +TypeID = 6470 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unthrow,Unlay} +Attributes = {ChangeTarget=6472} + +TypeID = 6471 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6469} + +TypeID = 6472 +Name = "a wooden window" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=6470} + +TypeID = 6473 +Name = "wooden planks" +Flags = {Unmove} + +TypeID = 6474 +Name = "wooden planks" +Flags = {Unmove} + +TypeID = 6475 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6476 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6477 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6478 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6479 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6480 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6481 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6482 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6483 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6484 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6485 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6486 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6487 +Name = "debris" +Flags = {Clip,Unmove} + +TypeID = 6488 +Name = "a christmas branch" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=6489,Weight=1800,Brightness=2,LightColor=206} + +TypeID = 6489 +Name = "a christmas branch" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=6488,Weight=1800,Brightness=0,LightColor=0} + +TypeID = 6490 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6493,TotalExpireTime=8} + +TypeID = 6491 +Name = "a bat decoration" +Flags = {Take,Hang} +Attributes = {Weight=200} + +TypeID = 6492 +Name = "a bat decoration" +Flags = {Unmove} + +TypeID = 6493 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6490,TotalExpireTime=8} + +TypeID = 6494 +Name = "a firefly" +Flags = {Top,Unmove,Expire} +Attributes = {ExpireTarget=6497,TotalExpireTime=8} + +TypeID = 6495 +Name = "a bat decoration" +Flags = {Unmove} + +TypeID = 6496 +Name = "a christmas present bag" +Description = "It contains presents which were stolen from Santa. Bring them back to Ruprecht on Vega" +Flags = {Take} +Attributes = {Weight=8000} + +TypeID = 6497 +Name = "a firefly" +Flags = {Top,Unmove} + +TypeID = 6498 +Name = "a certificate" +Description = "You have mastered the Dream Challenge and may apply for joining the order of the Nightmare Knights" +Flags = {Take,Hang} +Attributes = {Weight=150} + +TypeID = 6499 +Name = "a demonic essence" +Description = "Someone might be interested in trading this" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6500 +Name = "a gingerbreadman" +Flags = {Take} +Attributes = {Nutrition=20,Weight=500} + +TypeID = 6501 +Name = "a christmas wreath" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 6502 +Name = "a christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6503 +Name = "a red christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6504 +Name = "a blue christmas garland" +Flags = {Take,Hang} +Attributes = {Weight=500} + +TypeID = 6505 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6506 +Name = "a red christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6507 +Name = "a blue christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6508 +Name = "a green christmas bundle" +Description = "It contains random christmas decoration" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6509 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6510 +Name = "a christmas present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 6511 +Name = "a santa doll" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 6512 +Name = "a christmas wreath" +Flags = {Unmove} + +TypeID = 6513 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6514 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6515 +Name = "a dead plaguesmith" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=18,FluidSource=BLOOD,ExpireTarget=6519,TotalExpireTime=10} + +TypeID = 6516 +Name = "a christmas wreath" +Flags = {Unmove} + +TypeID = 6517 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6518 +Name = "a christmas garland" +Flags = {Unmove} + +TypeID = 6519 +Name = "a dead plaguesmith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,FluidSource=BLOOD,ExpireTarget=6520,TotalExpireTime=900} + +TypeID = 6520 +Name = "a dead plaguesmith" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=18,ExpireTarget=6521,TotalExpireTime=600} + +TypeID = 6521 +Name = "a dead plaguesmith" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=600} + +TypeID = 6522 +Name = "a parchment" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 6523 +Name = "a skeleton" +Flags = {Unmove} + +TypeID = 6524 +Name = "a skeleton" +Flags = {Unmove} + +TypeID = 6525 +Name = "a skeleton decoration" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 6526 +Name = "a christmas token" +Description = "Collect enough of these to trade them for valuable prizes" +Flags = {Cumulative,Take} +Attributes = {Weight=5} + +TypeID = 6527 +Name = "the avenger" +Description = "This holy blade was forged of shattered dreams" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=50,Defense=38} + +TypeID = 6528 +Name = "an infernal bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {MinimumLevel=70,Weight=90,AmmoType=BOLT,Attack=43,MissileEffect=16,Fragility=100} + +TypeID = 6529 +Name = "pair of soft boots" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=800,EquipTarget=3549,SlotType=FEET} + +TypeID = 6530 +Name = "a worn soft boots" +Description = "Someone specialised in shoes might be able to repair them for you" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 6531 +Name = "a santa hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 6532 +Name = "a dead defiler" +Flags = {Container,Unmove,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=6552,TotalExpireTime=10} + +TypeID = 6533 +Name = "a book" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 6534 +Name = "the Imperor's trident" +Flags = {Take} +Attributes = {Weight=8000} + +TypeID = 6535 +Name = "the Plasmother's remains" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 6536 +Name = "Countess Sorrow's frozen tear" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6537 +Name = "Mr. Punish's handcuffs" +Flags = {Take} +Attributes = {Weight=2500} + +TypeID = 6538 +Name = "a valentine's card" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=100} + +TypeID = 6539 +Name = "the Handmaiden's protector" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 6540 +Name = "a piece of Massacre's shell" +Flags = {Take} +Attributes = {Weight=6500} + +TypeID = 6541 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6542 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6543 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6544 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6545 +Name = "a coloured egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 6546 +Name = "Dracola's eye" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 6547 +Name = "a yellow powder" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 6548 +Name = "a purple powder" +Flags = {Cumulative,Take} +Attributes = {Weight=750} + +TypeID = 6549 +Name = "a green djinn powder" +Description = "The magical powder will bless you with the power to convince the green djinns" +Flags = {Cumulative,Take} +Attributes = {Weight=750} + +TypeID = 6550 +Name = "a red powder" +Description = "It reeks of hatred and malice" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6551 +Name = "a blue djinn powder" +Description = "The magical powder will bless you with the power to convince the blue djinns" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 6552 +Name = "a dead defiler" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=360} + +TypeID = 6553 +Name = "a ruthless axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=15} + +TypeID = 6554 +Name = "a 100 points scroll" +Description = "It grants you 100 shop points" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 6555 +Name = "a 50 points scroll" +Description = "It grants you 50 shop points" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 6556 +Name = "a tic-tac-toe token" +Description = "It seems to be rather fragile" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6557 +Name = "a tic-tac-toe token" +Description = "It seems to be rather fragile" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6558 +Name = "concentrated demonic blood" +Description = "Shake it to create a potion" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 6559 +Name = "" # this is nothing in client + +TypeID = 6560 +Name = "a dead human" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4247,TotalExpireTime=180} + +TypeID = 6561 +Name = "a ceremonial ankh" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 6562 +Name = "a flat roof" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6563 +Name = "a flat roof" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 6564 +Name = "a flat roof" +Flags = {Clip,Unmove} + +TypeID = 6565 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 6566 +Name = "a stuffed dragon" +Flags = {Take,Expire} +Attributes = {Weight=850,ExpireTarget=5791,TotalExpireTime=3} + +TypeID = 6567 +Name = "a santa doll" +Flags = {Take,Expire} +Attributes = {Weight=750,ExpireTarget=6511,TotalExpireTime=3} + +TypeID = 6568 +Name = "a panda teddy" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=5080,TotalExpireTime=3} + +TypeID = 6569 +Name = "a candy" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=5} + +TypeID = 6570 +Name = "a surprise bag" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6571 +Name = "a surprise bag" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 6572 +Name = "a party trumpet" +Flags = {UseEvent,Take} +Attributes = {Weight=50} + +TypeID = 6573 +Name = "a party trumpet" +Flags = {Take,Expire} +Attributes = {Weight=50,ExpireTarget=6572,TotalExpireTime=3} + +TypeID = 6574 +Name = "a bar of chocolate" +Flags = {Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 6575 +Name = "red balloons" +Flags = {Take,Hang} +Attributes = {Weight=10} + +TypeID = 6576 +Name = "a fireworks rocket" +Description = "Do not use in your backpack or while asleep. Keep away from animals or children" +Flags = {UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 6577 +Name = "green balloons" +Flags = {Take,Hang} +Attributes = {Weight=10} + +TypeID = 6578 +Name = "a party hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 6579 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=750} \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/compat/compat.lua b/app/SabrehavenServer/data/lib/compat/compat.lua new file mode 100644 index 0000000..789451e --- /dev/null +++ b/app/SabrehavenServer/data/lib/compat/compat.lua @@ -0,0 +1,1000 @@ +TRUE = true +FALSE = false + +result.getDataInt = result.getNumber +result.getDataLong = result.getNumber +result.getDataString = result.getString +result.getDataStream = result.getStream + +LUA_ERROR = false +LUA_NO_ERROR = true + +STACKPOS_GROUND = 0 +STACKPOS_FIRST_ITEM_ABOVE_GROUNDTILE = 1 +STACKPOS_SECOND_ITEM_ABOVE_GROUNDTILE = 2 +STACKPOS_THIRD_ITEM_ABOVE_GROUNDTILE = 3 +STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 +STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 +STACKPOS_TOP_CREATURE = 253 +STACKPOS_TOP_FIELD = 254 +STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 + +THING_TYPE_PLAYER = 1 + 1 +THING_TYPE_MONSTER = 2 + 1 +THING_TYPE_NPC = 3 + 1 + +COMBAT_POISONDAMAGE = COMBAT_EARTHDAMAGE +TALKTYPE_ORANGE_1 = TALKTYPE_MONSTER_SAY +TALKTYPE_ORANGE_2 = TALKTYPE_MONSTER_YELL + +NORTH = DIRECTION_NORTH +EAST = DIRECTION_EAST +SOUTH = DIRECTION_SOUTH +WEST = DIRECTION_WEST +SOUTHWEST = DIRECTION_SOUTHWEST +SOUTHEAST = DIRECTION_SOUTHEAST +NORTHWEST = DIRECTION_NORTHWEST +NORTHEAST = DIRECTION_NORTHEAST + +do + local function CreatureIndex(self, key) + local methods = getmetatable(self) + if key == "uid" then + return methods.getId(self) + elseif key == "type" then + local creatureType = 0 + if methods.isPlayer(self) then + creatureType = THING_TYPE_PLAYER + elseif methods.isMonster(self) then + creatureType = THING_TYPE_MONSTER + elseif methods.isNpc(self) then + creatureType = THING_TYPE_NPC + end + return creatureType + elseif key == "itemid" then + return 1 + elseif key == "actionid" then + return 0 + end + return methods[key] + end + rawgetmetatable("Player").__index = CreatureIndex + rawgetmetatable("Monster").__index = CreatureIndex + rawgetmetatable("Npc").__index = CreatureIndex +end + +do + local function ItemIndex(self, key) + local methods = getmetatable(self) + if key == "itemid" then + return methods.getId(self) + elseif key == "actionid" then + return methods.getActionId(self) + elseif key == "uid" then + return methods.getUniqueId(self) + elseif key == "type" then + return methods.getSubType(self) + end + return methods[key] + end + rawgetmetatable("Item").__index = ItemIndex + rawgetmetatable("Container").__index = ItemIndex + rawgetmetatable("Teleport").__index = ItemIndex +end + +function pushThing(thing) + local t = {uid = 0, itemid = 0, type = 0, actionid = 0} + if thing ~= nil then + if thing:isItem() then + t.uid = thing:getUniqueId() + t.itemid = thing:getId() + if ItemType(t.itemid):hasSubType() then + t.type = thing:getSubType() + end + t.actionid = thing:getActionId() + elseif thing:isCreature() then + t.uid = thing:getId() + t.itemid = 1 + if thing:isPlayer() then + t.type = THING_TYPE_PLAYER + elseif thing:isMonster() then + t.type = THING_TYPE_MONSTER + else + t.type = THING_TYPE_NPC + end + end + end + return t +end + +createCombatObject = Combat +setCombatArea = Combat.setArea +setCombatCallback = Combat.setCallback +setCombatCondition = Combat.setCondition +setCombatFormula = Combat.setFormula +setCombatParam = Combat.setParameter + +createConditionObject = Condition +setConditionParam = Condition.setParameter +setConditionFormula = Condition.setFormula +addDamageCondition = Condition.addDamage +addOutfitCondition = Condition.setOutfit + +function doCombat(cid, combat, var) return combat:execute(cid, var) end + +function isCreature(cid) return Creature(cid) ~= nil end +function isPlayer(cid) return Player(cid) ~= nil end +function isMonster(cid) return Monster(cid) ~= nil end +function isSummon(cid) return Creature(cid):getMaster() ~= nil end +function isNpc(cid) return Npc(cid) ~= nil end +function isItem(uid) return Item(uid) ~= nil end +function isContainer(uid) return Container(uid) ~= nil end + +function getCreatureName(cid) local c = Creature(cid) return c ~= nil and c:getName() or false end +function getCreatureHealth(cid) local c = Creature(cid) return c ~= nil and c:getHealth() or false end +function getCreatureMaxHealth(cid) local c = Creature(cid) return c ~= nil and c:getMaxHealth() or false end +function getCreaturePosition(cid) local c = Creature(cid) return c ~= nil and c:getPosition() or false end +function getCreatureOutfit(cid) local c = Creature(cid) return c ~= nil and c:getOutfit() or false end +function getCreatureSpeed(cid) local c = Creature(cid) return c ~= nil and c:getSpeed() or false end +function getCreatureBaseSpeed(cid) local c = Creature(cid) return c ~= nil and c:getBaseSpeed() or false end + +function getCreatureTarget(cid) + local c = Creature(cid) + if c ~= nil then + local target = c:getTarget() + return target ~= nil and target:getId() or 0 + end + return false +end + +function getCreatureMaster(cid) + local c = Creature(cid) + if c ~= nil then + local master = c:getMaster() + return master ~= nil and master:getId() or c:getId() + end + return false +end + +function getCreatureSummons(cid) + local c = Creature(cid) + if c == nil then + return false + end + + local result = {} + for _, summon in ipairs(c:getSummons()) do + result[#result + 1] = summon:getId() + end + return result +end + +getCreaturePos = getCreaturePosition + +function doCreatureAddHealth(cid, health) local c = Creature(cid) return c ~= nil and c:addHealth(health) or false end +function doRemoveCreature(cid) local c = Creature(cid) return c ~= nil and c:remove() or false end +function doCreatureSetLookDir(cid, direction) local c = Creature(cid) return c ~= nil and c:setDirection(direction) or false end +function doCreatureSay(cid, text, type, ...) local c = Creature(cid) return c ~= nil and c:say(text, type, ...) or false end +function doCreatureChangeOutfit(cid, outfit) local c = Creature(cid) return c ~= nil and c:setOutfit(outfit) or false end +function doSetCreatureDropLoot(cid, doDrop) local c = Creature(cid) return c ~= nil and c:setDropLoot(doDrop) or false end +function doChangeSpeed(cid, delta) local c = Creature(cid) return c ~= nil and c:changeSpeed(delta) or false end +function doAddCondition(cid, conditionId) local c = Creature(cid) return c ~= nil and c:addCondition(conditionId) or false end +function doRemoveCondition(cid, conditionType, subId) local c = Creature(cid) return c ~= nil and (c:removeCondition(conditionType, CONDITIONID_COMBAT, subId) or c:removeCondition(conditionType, CONDITIONID_DEFAULT, subId) or true) end + +doSetCreatureDirection = doCreatureSetLookDir + +function registerCreatureEvent(cid, name) local c = Creature(cid) return c ~= nil and c:registerEvent(name) or false end +function unregisterCreatureEvent(cid, name) local c = Creature(cid) return c ~= nil and c:unregisterEvent(name) or false end + +function getPlayerByName(name) local p = Player(name) return p ~= nil and p:getId() or false end +function getIPByPlayerName(name) local p = Player(name) return p ~= nil and p:getIp() or false end +function getPlayerGUID(cid) local p = Player(cid) return p ~= nil and p:getGuid() or false end +function getPlayerIp(cid) local p = Player(cid) return p ~= nil and p:getIp() or false end +function getPlayerAccountType(cid) local p = Player(cid) return p ~= nil and p:getAccountType() or false end +function getPlayerLastLoginSaved(cid) local p = Player(cid) return p ~= nil and p:getLastLoginSaved() or false end +function getPlayerName(cid) local p = Player(cid) return p ~= nil and p:getName() or false end +function getPlayerFreeCap(cid) local p = Player(cid) return p ~= nil and (p:getFreeCapacity() / 100) or false end +function getPlayerPosition(cid) local p = Player(cid) return p ~= nil and p:getPosition() or false end +function getPlayerMagLevel(cid) local p = Player(cid) return p ~= nil and p:getMagicLevel() or false end +function getPlayerAccess(cid) + local player = Player(cid) + if player == nil then + return false + end + return player:getGroup():getAccess() and 1 or 0 +end +function getPlayerSkill(cid, skillId) local p = Player(cid) return p ~= nil and p:getSkillLevel(skillId) or false end +function getPlayerMana(cid) local p = Player(cid) return p ~= nil and p:getMana() or false end +function getPlayerMaxMana(cid) local p = Player(cid) return p ~= nil and p:getMaxMana() or false end +function getPlayerLevel(cid) local p = Player(cid) return p ~= nil and p:getLevel() or false end +function getPlayerTown(cid) local p = Player(cid) return p ~= nil and p:getTown():getId() or false end +function getPlayerVocation(cid) local p = Player(cid) return p ~= nil and p:getVocation():getId() or false end +function getPlayerSoul(cid) local p = Player(cid) return p ~= nil and p:getSoul() or false end +function getPlayerSex(cid) local p = Player(cid) return p ~= nil and p:getSex() or false end +function getPlayerStorageValue(cid, key) local p = Player(cid) return p ~= nil and p:getStorageValue(key) or false end +function getPlayerBalance(cid) local p = Player(cid) return p ~= nil and p:getBankBalance() or false end +function getPlayerMoney(cid) local p = Player(cid) return p ~= nil and p:getMoney() or false end +function getPlayerGroupId(cid) local p = Player(cid) return p ~= nil and p:getGroup():getId() or false end +function getPlayerLookDir(cid) local p = Player(cid) return p ~= nil and p:getDirection() or false end +function getPlayerLight(cid) local p = Player(cid) return p ~= nil and p:getLight() or false end +function getPlayerDepotItems(cid, depotId) local p = Player(cid) return p ~= nil and p:getDepotItems(depotId) or false end +function getPlayerSkullType(cid) local p = Player(cid) return p ~= nil and p:getSkull() or false end +function getPlayerLossPercent(cid) local p = Player(cid) return p ~= nil and p:getDeathPenalty() or false end +function getPlayerPremiumDays(cid) local p = Player(cid) return p ~= nil and p:getPremiumDays() or false end +function getPlayerBlessing(cid, blessing) local p = Player(cid) return p ~= nil and p:hasBlessing(blessing) or false end +function getPlayerParty(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return nil + end + return party:getLeader():getId() +end +function getPlayerGuildId(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getId() +end +function getPlayerGuildLevel(cid) local p = Player(cid) return p ~= nil and p:getGuildLevel() or false end +function getPlayerGuildName(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getName() +end +function getPlayerGuildRank(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + + local rank = guild:getRankByLevel(player:getGuildLevel()) + return rank ~= nil and rank.name or false +end +function getPlayerGuildNick(cid) local p = Player(cid) return p ~= nil and p:getGuildNick() or false end +function getPlayerMasterPos(cid) local p = Player(cid) return p ~= nil and p:getTown():getTemplePosition() or false end +function getPlayerItemCount(cid, itemId, ...) local p = Player(cid) return p ~= nil and p:getItemCount(itemId, ...) or false end +function getPlayerSlotItem(cid, slot) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getSlotItem(slot)) +end +function getPlayerItemById(cid, deepSearch, itemId, ...) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getItemById(itemId, deepSearch, ...)) +end +function getPlayerFood(cid) + local player = Player(cid) + if player == nil then + return false + end + local c = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) return c ~= nil and math.floor(c:getTicks() / 1000) or 0 +end +function canPlayerLearnInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:canLearnSpell(name) or false end +function getPlayerLearnedInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:hasLearnedSpell(name) or false end +function isPlayerGhost(cid) local p = Player(cid) return p ~= nil and p:isInGhostMode() or false end +function isPlayerPzLocked(cid) local p = Player(cid) return p ~= nil and p:isPzLocked() or false end +function isPremium(cid) local p = Player(cid) return p ~= nil and p:isPremium() or false end +function getPlayersByIPAddress(ip, mask) + if mask == nil then mask = 0xFFFFFFFF end + local masked = bit.band(ip, mask) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if bit.band(player:getIp(), mask) == masked then + result[#result + 1] = player:getId() + end + end + return result +end +function getOnlinePlayers() + local result = {} + for _, player in ipairs(Game.getPlayers()) do + result[#result + 1] = player:getName() + end + return result +end +function getPlayersByAccountNumber(accountNumber) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if player:getAccountId() == accountNumber then + result[#result + 1] = player:getId() + end + end + return result +end +function getPlayerGUIDByName(name) + local player = Player(name) + if player ~= nil then + return player:getGuid() + end + + local resultId = db.storeQuery("SELECT `id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local guid = result.getDataInt(resultId, "id") + result.free(resultId) + return guid + end + return 0 +end +function getAccountNumberByPlayerName(name) + local player = Player(name) + if player ~= nil then + return player:getAccountId() + end + + local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local accountId = result.getDataInt(resultId, "account_id") + result.free(resultId) + return accountId + end + return 0 +end + +getPlayerAccountBalance = getPlayerBalance +getIpByName = getIPByPlayerName + +function setPlayerStorageValue(cid, key, value) local p = Player(cid) return p ~= nil and p:setStorageValue(key, value) or false end +function doPlayerSetBalance(cid, balance) local p = Player(cid) return p ~= nil and p:setBankBalance(balance) or false end +function doPlayerAddMoney(cid, money) local p = Player(cid) return p ~= nil and p:addMoney(money) or false end +function doPlayerRemoveMoney(cid, money) local p = Player(cid) return p ~= nil and p:removeMoney(money) or false end +function doPlayerAddSoul(cid, soul) local p = Player(cid) return p ~= nil and p:addSoul(soul) or false end +function doPlayerSetVocation(cid, vocation) local p = Player(cid) return p ~= nil and p:setVocation(Vocation(vocation)) or false end +function doPlayerSetTown(cid, town) local p = Player(cid) return p ~= nil and p:setTown(Town(town)) or false end +function setPlayerGroupId(cid, groupId) local p = Player(cid) return p ~= nil and p:setGroup(Group(groupId)) or false end +function doPlayerSetSex(cid, sex) local p = Player(cid) return p ~= nil and p:setSex(sex) or false end +function doPlayerSetGuildLevel(cid, level) local p = Player(cid) return p ~= nil and p:setGuildLevel(level) or false end +function doPlayerSetGuildNick(cid, nick) local p = Player(cid) return p ~= nil and p:setGuildNick(nick) or false end +function 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 ~= nil and p:showTextDialog(itemId, text) or false end +function doPlayerAddItemEx(cid, uid, ...) local p = Player(cid) return p ~= nil and p:addItemEx(Item(uid), ...) or false end +function doPlayerRemoveItem(cid, itemid, count, ...) local p = Player(cid) return p ~= nil and p:removeItem(itemid, count, ...) or false end +function doPlayerAddPremiumDays(cid, days) local p = Player(cid) return p ~= nil and p:addPremiumDays(days) or false end +function doPlayerRemovePremiumDays(cid, days) local p = Player(cid) return p ~= nil and p:removePremiumDays(days) or false end +function doPlayerAddBlessing(cid, blessing) local p = Player(cid) return p ~= nil and p:addBlessing(blessing) or false end +function doPlayerSendCancel(cid, text) local p = Player(cid) return p ~= nil and p:sendCancelMessage(text) or false end +function doPlayerFeed(cid, food) local p = Player(cid) return p ~= nil and p:feed(food) or false end +function playerLearnInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:learnSpell(name) or false end +function doPlayerPopupFYI(cid, message) local p = Player(cid) return p ~= nil and p:popupFYI(message) or false end +function doPlayerSendTextMessage(cid, type, text, ...) local p = Player(cid) return p ~= nil and p:sendTextMessage(type, text, ...) or false end +function doSendAnimatedText() debugPrint("Deprecated function.") return true end +function doPlayerAddExp(cid, exp, useMult, ...) + local player = Player(cid) + if player == nil then + return false + end + + if useMult then + exp = exp * Game.getExperienceStage(player:getLevel()) + end + return player:addExperience(exp, ...) +end +function doPlayerAddManaSpent(cid, mana) local p = Player(cid) return p ~= nil and p:addManaSpent(mana * configManager.getNumber(configKeys.RATE_MAGIC)) or false end +function doPlayerAddSkillTry(cid, skillid, n) local p = Player(cid) return p ~= nil and p:addSkillTries(skillid, n * configManager.getNumber(configKeys.RATE_SKILL)) or false end +function doPlayerAddMana(cid, mana, ...) local p = Player(cid) return p ~= nil and p:addMana(mana, ...) or false end +function doPlayerJoinParty(cid, leaderId) + local player = Player(cid) + if player == nil then + return false + end + + if player:getParty() ~= nil then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party.") + return true + end + + local leader = Player(leaderId) + if leader == nil then + return false + end + + local party = leader:getParty() + if party == nil or party:getLeader() ~= leader then + return true + end + + for _, invitee in ipairs(party:getInvitees()) do + if player ~= invitee then + return true + end + end + + party:addMember(player) + return true +end +function getPartyMembers(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return false + end + + local result = {party:getLeader():getId()} + for _, member in ipairs(party:getMembers()) do + result[#result + 1] = member:getId() + end + return result +end + +doPlayerSendDefaultCancel = doPlayerSendCancel + +function getMonsterTargetList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local result = {} + for _, creature in ipairs(monster:getTargetList()) do + if monster:isTarget(creature) then + result[#result + 1] = creature:getId() + end + end + return result +end +function getMonsterFriendList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local z = monster:getPosition().z + + local result = {} + for _, creature in ipairs(monster:getFriendList()) do + if not creature:isRemoved() and creature:getPosition().z == z then + result[#result + 1] = creature:getId() + end + end + return result +end +function doSetMonsterTarget(cid, target) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() ~= nil then + return true + end + + local target = Creature(cid) + if target == nil then + return false + end + + monster:selectTarget(target) + return true +end +function doMonsterChangeTarget(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() ~= nil then + return true + end + + monster:searchTarget(1) + return true +end +function doCreateNpc(name, pos, ...) + local npc = Game.createNpc(name, pos, ...) return npc ~= nil and npc:setMasterPos(pos) or false +end +function doSummonCreature(name, pos, ...) + local m = Game.createMonster(name, pos, ...) return m ~= nil and m:getId() or false +end +function doConvinceCreature(cid, target) + local creature = Creature(cid) + if creature == nil then + return false + end + + local targetCreature = Creature(target) + if targetCreature == nil then + return false + end + + targetCreature:setMaster(creature) + return true +end + +function getTownId(townName) local t = Town(townName) return t ~= nil and t:getId() or false end +function getTownName(townId) local t = Town(townId) return t ~= nil and t:getName() or false end +function getTownTemplePosition(townId) local t = Town(townId) return t ~= nil and t:getTemplePosition() or false end + +function doSetItemActionId(uid, actionId) local i = Item(uid) return i ~= nil and i:setActionId(actionId) or false end +function doTransformItem(uid, newItemId, ...) local i = Item(uid) return i ~= nil and i:transform(newItemId, ...) or false end +function doChangeTypeItem(uid, newType) local i = Item(uid) return i ~= nil and i:transform(i:getId(), newType) or false end +function doRemoveItem(uid, ...) local i = Item(uid) return i ~= nil and i:remove(...) or false end + +function getContainerSize(uid) local c = Container(uid) return c ~= nil and c:getSize() or false end +function getContainerCap(uid) local c = Container(uid) return c ~= nil and c:getCapacity() or false end +function getContainerItem(uid, slot) + local container = Container(uid) + if container == nil then + return pushThing(nil) + end + return pushThing(container:getItem(slot)) +end + +function doAddContainerItemEx(uid, virtualId) + local container = Container(uid) + if container == nil then + return false + end + + local res = container:addItemEx(Item(virtualId)) + if res == nil then + return false + end + return res +end + +function doSendMagicEffect(pos, magicEffect, ...) return Position(pos):sendMagicEffect(magicEffect, ...) end +function doSendDistanceShoot(fromPos, toPos, distanceEffect, ...) return Position(fromPos):sendDistanceEffect(toPos, distanceEffect, ...) end +function isSightClear(fromPos, toPos, floorCheck) return Position(fromPos):isSightClear(toPos, floorCheck) end + +function getPromotedVocation(vocationId) + local vocation = Vocation(vocationId) + if vocation == nil then + return 0 + end + + local promotedVocation = vocation:getPromotion() + if promotedVocation == nil then + return 0 + end + return promotedVocation:getId() +end + +function getGuildId(guildName) + local resultId = db.storeQuery("SELECT `id` FROM `guilds` WHERE `name` = " .. db.escapeString(guildName)) + if resultId == false then + return false + end + + local guildId = result.getDataInt(resultId, "id") + result.free(resultId) + return guildId +end + +function getHouseName(houseId) local h = House(houseId) return h ~= nil and h:getName() or false end +function getHouseOwner(houseId) local h = House(houseId) return h ~= nil and h:getOwnerGuid() or false end +function getHouseEntry(houseId) local h = House(houseId) return h ~= nil and h:getExitPosition() or false end +function getHouseTown(houseId) local h = House(houseId) if h == nil then return false end local t = h:getTown() return t ~= nil and t:getId() or false end +function getHouseTilesSize(houseId) local h = House(houseId) return h ~= nil and h:getTileCount() or false end + +function isItemStackable(itemId) return ItemType(itemId):isStackable() end +function isItemRune(itemId) return ItemType(itemId):isRune() end +function isItemDoor(itemId) return ItemType(itemId):isDoor() end +function isItemContainer(itemId) return ItemType(itemId):isContainer() end +function isItemFluidContainer(itemId) return ItemType(itemId):isFluidContainer() end +function isItemMovable(itemId) return ItemType(itemId):isMovable() end +function isCorpse(uid) local i = Item(uid) return i ~= nil and ItemType(i:getId()):isCorpse() or false end + +isItemMoveable = isItemMovable +isMoveable = isMovable + +function getItemName(itemId) return ItemType(itemId):getName() end +function getItemWeight(itemId, ...) return ItemType(itemId):getWeight(...) / 100 end +function getItemDescriptions(itemId) + local itemType = ItemType(itemId) + return { + name = itemType:getName(), + plural = itemType:getPluralName(), + article = itemType:getArticle(), + description = itemType:getDescription() + } +end +function getItemIdByName(name) + local id = ItemType(name):getId() + if id == 0 then + return false + end + return id +end +function getItemWeightByUID(uid, ...) + local item = Item(uid) + if item == nil then + return false + end + + local itemType = ItemType(item:getId()) + return itemType:isStackable() and (itemType:getWeight(item:getCount(), ...) / 100) or (itemType:getWeight(1, ...) / 100) +end +function getItemRWInfo(uid) + local item = Item(uid) + if item == nil then + return false + end + + local rwFlags = 0 + local itemType = ItemType(item:getId()) + if itemType:isReadable() then + rwFlags = bit.bor(rwFlags, 1) + end + + if itemType:isWritable() then + rwFlags = bit.bor(rwFlags, 2) + end + return rwFlags +end +function getContainerCapById(itemId) return ItemType(itemId):getCapacity() end +function getFluidSourceType(itemId) local it = ItemType(itemId) return it.id ~= 0 and it:getFluidSource() or false end +function hasProperty(uid, prop) + local item = Item(uid) + if item == nil then + return false + end + + local parent = item:getParent() + if parent:isTile() and item == parent:getGround() then + return parent:hasProperty(prop) + else + return item:hasProperty(prop) + end +end + +function doSetItemText(uid, text) + local item = Item(uid) + if item == nil then + return false + end + + if text ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, text) + else + item:removeAttribute(ITEM_ATTRIBUTE_TEXT) + end + return true +end +function doSetItemSpecialDescription(uid, desc) + local item = Item(uid) + if item == nil then + return false + end + + if desc ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, desc) + else + item:removeAttribute(ITEM_ATTRIBUTE_DESCRIPTION) + end + return true +end +function doDecayItem(uid) local i = Item(uid) return i ~= nil and i:decay() or false end + +function setHouseOwner(id, guid) local h = House(id) return h ~= nil and h:setOwnerGuid(guid) or false end +function getHouseRent(id) local h = House(id) return h ~= nil and h:getRent() or nil end +function getHouseAccessList(id, listId) local h = House(id) return h ~= nil and h:getAccessList(listId) or nil end +function setHouseAccessList(id, listId, listText) local h = House(id) return h ~= nil and h:setAccessList(listId, listText) or false end + +function getHouseByPlayerGUID(playerGUID) + for _, house in ipairs(Game.getHouses()) do + if house:getOwnerGuid() == playerGUID then + return house:getId() + end + end + return nil +end + +function getTileHouseInfo(pos) + local t = Tile(pos) + if t == nil then + return false + end + local h = t:getHouse() + return h ~= nil and h:getId() or false +end + +function getTilePzInfo(position) + local t = Tile(position) + if t == nil then + return false + end + return t:hasFlag(TILESTATE_PROTECTIONZONE) +end + +function getTileInfo(position) + local t = Tile(position) + if t == nil then + return false + end + + local ret = pushThing(t:getGround()) + ret.protection = t:hasFlag(TILESTATE_PROTECTIONZONE) + ret.nopz = ret.protection + ret.nologout = t:hasFlag(TILESTATE_NOLOGOUT) + ret.refresh = t:hasFlag(TILESTATE_REFRESH) + ret.house = t:getHouse() ~= nil + ret.bed = t:hasFlag(TILESTATE_BED) + ret.depot = t:hasFlag(TILESTATE_DEPOT) + + ret.things = t:getThingCount() + ret.creatures = t:getCreatureCount() + ret.items = t:getItemCount() + ret.topItems = t:getTopItemCount() + ret.downItems = t:getDownItemCount() + return ret +end + +function getTileItemByType(position, itemType) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByType(itemType)) +end + +function getTileItemById(position, itemId, ...) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemById(itemId, ...)) +end + +function getTileThingByPos(position) + local t = Tile(position) + if t == nil then + if position.stackpos == -1 then + return -1 + end + return pushThing(nil) + end + + if position.stackpos == -1 then + return t:getThingCount() + end + return pushThing(t:getThing(position.stackpos)) +end + +function getTileThingByTopOrder(position, topOrder) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByTopOrder(topOrder)) +end + +function getTopCreature(position) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getTopCreature()) +end + +function queryTileAddThing(thing, position, ...) local t = Tile(position) return t ~= nil and t:queryAdd(thing, ...) or false end + +function doTeleportThing(uid, dest, pushMovement) + if type(uid) == "userdata" then + if uid:isCreature() then + return uid:teleportTo(dest, pushMovement or false) + else + return uid:moveTo(dest) + end + else + if uid >= 0x10000000 then + local creature = Creature(uid) + if creature ~= nil then + return creature:teleportTo(dest, pushMovement or false) + end + else + local item = Item(uid) + if item ~= nil then + return item:moveTo(dest) + end + end + end + return false +end + +function getThingPos(uid) + local thing + if type(uid) ~= "userdata" then + if uid >= 0x10000000 then + thing = Creature(uid) + else + thing = Item(uid) + end + else + thing = uid + end + + if thing == nil then + return false + end + + local stackpos = 0 + local tile = thing:getTile() + if tile ~= nil then + stackpos = tile:getThingIndex(thing) + end + + local position = thing:getPosition() + position.stackpos = stackpos + return position +end + +function getThingfromPos(pos) + local tile = Tile(pos) + if tile == nil then + return pushThing(nil) + end + + local thing + local stackpos = pos.stackpos or 0 + if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then + thing = tile:getTopCreature() + if thing == nil then + local item = tile:getTopDownItem() + if item ~= nil and item:getType():isMovable() then + thing = item + end + end + elseif stackpos == STACKPOS_TOP_FIELD then + thing = tile:getFieldItem() + elseif stackpos == STACKPOS_TOP_CREATURE then + thing = tile:getTopCreature() + else + thing = tile:getThing(stackpos) + end + return pushThing(thing) +end + +function doRelocate(fromPos, toPos, unmovables) + if fromPos == toPos then + return false + end + + local fromTile = Tile(fromPos) + if fromTile == nil then + return false + end + + if Tile(toPos) == nil then + return false + end + + local ignoreUnmovables = false + if unmovables ~= nil then + ignoreUnmovables = unmovables + end + + for i = fromTile:getThingCount() - 1, 0, -1 do + local thing = fromTile:getThing(i) + if thing ~= nil then + if thing:isItem() and not ItemType(thing:getId()):isGroundTile() then + if ignoreUnmovables and not ItemType(thing:getId()):isMovable() then + thing:moveTo(toPos) + elseif ItemType(thing:getId()):isMovable() then + thing:moveTo(toPos) + elseif ItemType(thing:getId()):isMagicField() then + thing:remove() + elseif ItemType(thing:getId()):isSplash() then + thing:remove() + end + elseif thing:isCreature() then + thing:teleportTo(toPos) + end + end + end + return true +end + +function getThing(uid) + return uid >= 0x10000000 and pushThing(Creature(uid)) or pushThing(Item(uid)) +end + +function getConfigInfo(info) + if type(info) ~= "string" then + return nil + end + dofile('config.lua') + return _G[info] +end + +function getWorldCreatures(type) + if type == 0 then + return Game.getPlayerCount() + elseif type == 1 then + return Game.getMonsterCount() + elseif type == 2 then + return Game.getNpcCount() + end + return Game.getPlayerCount() + Game.getMonsterCount() + Game.getNpcCount() +end + +saveData = saveServer + +function getGlobalStorageValue(key) + return Game.getStorageValue(key) or -1 +end + +function setGlobalStorageValue(key, value) + Game.setStorageValue(key, value) + return true +end + +getWorldType = Game.getWorldType + +numberToVariant = Variant +stringToVariant = Variant +positionToVariant = Variant + +function targetPositionToVariant(position) + local variant = Variant(position) + variant.type = VARIANT_TARGETPOSITION + return variant +end + +variantToNumber = Variant.getNumber +variantToString = Variant.getString +variantToPosition = Variant.getPosition + +function doCreateTeleport(itemId, destination, position) + local item = Game.createItem(itemId, 1, position) + if not item:isTeleport() then + item:remove() + return false + end + item:setDestination(destination) + return item:getUniqueId() +end + +function getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers) + local result = Game.getSpectators(centerPos, multifloor, onlyPlayers or false, rangex, rangex, rangey, rangey) + if #result == 0 then + return nil + end + + for index, spectator in ipairs(result) do + result[index] = spectator:getId() + end + return result +end + +function broadcastMessage(message, messageType) + Game.broadcastMessage(message, messageType) + print("> Broadcasted message: \"" .. message .. "\".") +end + +function Guild.addMember(self, player) + return player:setGuild(guild) +end +function Guild.removeMember(self, player) + return player:getGuild() == self and player:setGuild(nil) +end diff --git a/app/SabrehavenServer/data/lib/core/constants.lua b/app/SabrehavenServer/data/lib/core/constants.lua new file mode 100644 index 0000000..9d1f90b --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/constants.lua @@ -0,0 +1 @@ +CONTAINER_POSITION = 0xFFFF diff --git a/app/SabrehavenServer/data/lib/core/container.lua b/app/SabrehavenServer/data/lib/core/container.lua new file mode 100644 index 0000000..84e3ea1 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/container.lua @@ -0,0 +1,49 @@ +function Container.isContainer(self) + return true +end + +function Container.isItem(self) + return true +end + +function Container.isMonster(self) + return false +end + +function Container.isCreature(self) + return false +end + +function Container.isPlayer(self) + return false +end + +function Container.isTeleport(self) + return false +end + +function Container.isTile(self) + return false +end + +function Container.getItemsById(self, itemId) + local list = {} + for index = 0, (self:getSize() - 1) do + local item = self:getItem(index) + if item then + if item:isContainer() then + local rlist = item:getItemsById(itemId) + if type(rlist) == 'table' then + for _, v in pairs(rlist) do + table.insert(list, v) + end + end + else + if item:getId() == itemId then + table.insert(list, item) + end + end + end + end + return list +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/core.lua b/app/SabrehavenServer/data/lib/core/core.lua new file mode 100644 index 0000000..457e61e --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/core.lua @@ -0,0 +1,13 @@ +dofile('data/lib/core/constants.lua') +dofile('data/lib/core/container.lua') +dofile('data/lib/core/creature.lua') +dofile('data/lib/core/monster.lua') +dofile('data/lib/core/game.lua') +dofile('data/lib/core/item.lua') +dofile('data/lib/core/itemtype.lua') +dofile('data/lib/core/player.lua') +dofile('data/lib/core/position.lua') +dofile('data/lib/core/teleport.lua') +dofile('data/lib/core/tile.lua') +dofile('data/lib/core/vocation.lua') +dofile('data/lib/core/guildwars.lua') \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/creature.lua b/app/SabrehavenServer/data/lib/core/creature.lua new file mode 100644 index 0000000..3393fdb --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/creature.lua @@ -0,0 +1,116 @@ +function Creature.getClosestFreePosition(self, position, extended) + local usePosition = Position(position) + local tiles = { Tile(usePosition) } + local length = extended and 2 or 1 + + local tile + for y = -length, length do + for x = -length, length do + if x ~= 0 or y ~= 0 then + usePosition.x = position.x + x + usePosition.y = position.y + y + + tile = Tile(usePosition) + if tile then + tiles[#tiles + 1] = tile + end + end + end + end + + for i = 1, #tiles do + tile = tiles[i] + if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) then + return tile:getPosition() + end + end + return Position() +end + +function Creature:setMonsterOutfit(monster, time) + local monsterType = MonsterType(monster) + if not monsterType then + return false + end + + if self:isPlayer() and not (getPlayerFlagValue(self, PlayerFlag_CanIllusionAll) or monsterType:isIllusionable()) then + return false + end + + local condition = Condition(CONDITION_OUTFIT) + condition:setOutfit(monsterType:getOutfit()) + condition:setTicks(time) + self:addCondition(condition) + + return true +end + +function Creature:setItemOutfit(item, time) + local itemType = ItemType(item) + if not itemType then + return false + end + + local condition = Condition(CONDITION_OUTFIT) + condition:setOutfit({ + lookTypeEx = itemType:getId() + }) + condition:setTicks(time) + self:addCondition(condition) + + return true +end + +function Creature:addSummon(monster) + local summon = Monster(monster) + if not summon then + return false + end + + summon:setTarget(nil) + summon:setFollowCreature(nil) + summon:setDropLoot(false) + summon:setMaster(self) + return true +end + +function Creature:removeSummon(monster) + local summon = Monster(monster) + if not summon or summon:getMaster() ~= self then + return false + end + + summon:setTarget(nil) + summon:setFollowCreature(nil) + summon:setDropLoot(true) + summon:setMaster(nil) + return true +end + +function Creature.getPlayer(self) + return self:isPlayer() and self or nil +end + +function Creature.isItem(self) + return false +end + +function Creature.isMonster(self) + return false +end + +function Creature.isNpc(self) + return false +end + +function Creature.isPlayer(self) + return false +end + +function Creature.isTile(self) + return false +end + +function Creature.isContainer(self) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/game.lua b/app/SabrehavenServer/data/lib/core/game.lua new file mode 100644 index 0000000..fc900f7 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/game.lua @@ -0,0 +1,159 @@ +function Game.sendMagicEffect(position, effect) + local pos = Position(position) + pos:sendMagicEffect(effect) +end + +function Game.removeItemsOnMap(position) + local tile = Tile(position) + local tileCount = tile:getThingCount() + local i = 0 + while i < tileCount do + local tileItem = tile:getThing(i) + if tileItem and tileItem:getType():isMovable() then + tileItem:remove() + else + i = i + 1 + end + end +end + +function Game.transformItemOnMap(position, itemId, toItemId, subtype) + if not subtype then + subtype = -1 + end + + local tile = Tile(position) + local item = tile:getItemById(itemId) + item:transform(toItemId, subtype) + item:decay() + return item +end + +function Game.removeItemOnMap(position, itemId, subtype) + if not subtype then + subtype = -1 + end + + local tile = Tile(position) + local item = tile:getItemById(itemId, subtype) + item:remove() +end + +function Game.isItemThere(position, itemId) + local tile = Tile(position) + return tile:getItemById(itemId) ~= nil +end + +function Game.isPlayerThere(position) + local tile = Tile(position) + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + return false +end + +function Game.isMonsterThere(position, monsterName) + local tile = Tile(position) + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isMonster() and creature:getName():lower() == monsterName:lower() then + return creature + end + end + return nil +end + +function Game.broadcastMessage(message, messageType) + if messageType == nil then + messageType = MESSAGE_STATUS_WARNING + end + + for _, player in ipairs(Game.getPlayers()) do + player:sendTextMessage(messageType, message) + end +end + +function Game.convertIpToString(ip) + local band = bit.band + local rshift = bit.rshift + return string.format("%d.%d.%d.%d", + band(ip, 0xFF), + band(rshift(ip, 8), 0xFF), + band(rshift(ip, 16), 0xFF), + rshift(ip, 24) + ) +end + +function Game.getReverseDirection(direction) + if direction == WEST then + return EAST + elseif direction == EAST then + return WEST + elseif direction == NORTH then + return SOUTH + elseif direction == SOUTH then + return NORTH + elseif direction == NORTHWEST then + return SOUTHEAST + elseif direction == NORTHEAST then + return SOUTHWEST + elseif direction == SOUTHWEST then + return NORTHEAST + elseif direction == SOUTHEAST then + return NORTHWEST + end + return NORTH +end + +function Game.getSkillType(weaponType) + if weaponType == WEAPON_CLUB then + return SKILL_CLUB + elseif weaponType == WEAPON_SWORD then + return SKILL_SWORD + elseif weaponType == WEAPON_AXE then + return SKILL_AXE + elseif weaponType == WEAPON_DISTANCE then + return SKILL_DISTANCE + elseif weaponType == WEAPON_SHIELD then + return SKILL_SHIELD + end + return SKILL_FIST +end + +if not globalStorageTable then + globalStorageTable = {} +end + +function Game.getStorageValue(key) + -- Return from local table if possible + if globalStorageTable[key] ~= nil then + return globalStorageTable[key] + end + + -- Else look for it on the DB + local dbData = db.storeQuery("SELECT `value` FROM `global_storage` WHERE `key` = " .. key .. " LIMIT 1;") + if dbData ~= false then + local value = result.getNumber(dbData, "value") + if value ~= nil then + -- Save it to globalStorageTable + globalStorageTable[key] = value + return value + end + end + + return nil +end + +function Game.setStorageValue(key, value) + globalStorageTable[key] = value + + local dbData = db.storeQuery("SELECT `value` FROM `global_storage` WHERE `key` = " .. key .. " LIMIT 1;") + if dbData ~= false then + db.query("UPDATE `global_storage` SET `value`='".. value .."' WHERE `key` = " .. key .. " LIMIT 1;") + else + db.query("INSERT INTO `global_storage` (`key`, `value`) VALUES (" .. key .. ", " .. value .. ");") + end +end diff --git a/app/SabrehavenServer/data/lib/core/guildwars.lua b/app/SabrehavenServer/data/lib/core/guildwars.lua new file mode 100644 index 0000000..0deb969 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/guildwars.lua @@ -0,0 +1,199 @@ +guildwars = {} + +guildwars.__index = guildwars + +function guildwars:isInWar(player1, player2) + if not player1:getGuild() or not player2:getGuild() then + return 0 + end + + if player1:getGuild():getId() == 0 or player2:getGuild():getId() == 0 then + return 0 + end + + if player1:getGuild():getId() == player2:getGuild():getId() then + return 0 + end + + return isInWar(player1:getId(), player2:getId()) +end + +function guildwars:processKill(warId, killer, player) + local fragLimit = self:getFragLimit(warId) + + local killerFrags = self:getKills(warId, killer:getGuild():getId()) + 1 + local deadFrags = self:getKills(warId, player:getGuild():getId()) + + local killerMsg = "Opponent " .. player:getName() .. " of the " .. player:getGuild():getName() .. " was killed by " .. killer:getName() .. ". The new score is " .. killerFrags .. ":" .. deadFrags .. " frags (limit " .. fragLimit .. ")." + sendGuildChannelMessage(killer:getGuild():getId(), TALKTYPE_CHANNEL_O, killerMsg) + + local deadMsg = "Guild member " .. player:getName() .. " was killed by " .. killer:getName() .. " of the " .. killer:getGuild():getName() .. ". The new score is " .. deadFrags .. ":" .. killerFrags .. " frags (limit " .. fragLimit .. ")." + sendGuildChannelMessage(player:getGuild():getId(), TALKTYPE_CHANNEL_O, deadMsg) + + self:insertKill(warId, killer, player) + + if killerFrags >= fragLimit then + self:endWar(warId, killer, player, killerFrags) + end +end + +function guildwars:getFragLimit(warId) + local resultId = db.storeQuery("SELECT `frag_limit` FROM `guild_wars` WHERE `id` = " .. warId) + if resultId ~= false then + local frag_limit = result.getDataInt(resultId, "frag_limit") + result.free(resultId) + return frag_limit + end + return 0 +end + +function guildwars:getBounty(warId) + local resultId = db.storeQuery("SELECT `bounty` FROM `guild_wars` WHERE `id` = " .. warId) + if resultId ~= false then + local bounty = result.getDataInt(resultId, "bounty") + result.free(resultId) + return bounty + end + return 0 +end + +function guildwars:getKills(warId, guildId) + local resultId = db.storeQuery("SELECT COUNT(*) as frags FROM `guildwar_kills` WHERE `warid` = " .. warId .. " and `killerguild` = " .. guildId) + if resultId ~= false then + local frags = result.getDataInt(resultId, "frags") + result.free(resultId) + return frags + end + return 0 +end + +function guildwars:insertKill(warId, killer, target) + db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `warid`, `time`) VALUES (" .. db.escapeString(killer:getName()) .. ", " .. db.escapeString(target:getName()) .. ", " .. killer:getGuild():getId() .. ", " .. target:getGuild():getId() .. ", " .. warId .. ", " .. os.time() .. ")") +end + +function guildwars:endWar(warId, killer, player, frags) + local winGuildInternalMessage = "Congratulations! You have won the war against " .. player:getGuild():getName() .. " with " .. frags .. " frags." + sendGuildChannelMessage(killer:getGuild():getId(), TALKTYPE_CHANNEL_O, winGuildInternalMessage) + + local loseGuildInternalMessage = "You have lost the war against " .. killer:getGuild():getName() .. ". They have reached the limit of " .. frags .. " frags." + sendGuildChannelMessage(player:getGuild():getId(), TALKTYPE_CHANNEL_O, loseGuildInternalMessage) + + broadcastMessage(killer:getGuild():getName() .. " have won the war against " .. player:getGuild():getName() .. " with " .. frags .. " frags.", MESSAGE_EVENT_ADVANCE) + + self:updateState(warId, 5) + + self:setWarEmblem(killer:getGuild(), player:getGuild()) + + local bounty = self:getBounty(warId) + if bounty > 0 then + killer:getGuild():increaseBankBalance(bounty * 2) + end +end + +function guildwars:setWarEmblem(guild1, guild2) + guild1:setGuildWarEmblem(guild2) +end + +function guildwars:updateState(warId, status) + db.query("UPDATE `guild_wars` SET `status` = " .. status .. " WHERE `id` = " .. warId) +end + +function guildwars:getPendingInvitation(guild1, guild2) + local resultId = db.storeQuery("SELECT `id`, `bounty` FROM `guild_wars` WHERE `guild1` = " .. guild1 .. " AND `guild2` = " .. guild2 .. " AND `status` = 0") + + if resultId then + local id = result.getDataInt(resultId, "id") + local bounty = result.getDataInt(resultId, "bounty") + result.free(resultId) + + return id, bounty + end + + return 0 +end + +function guildwars:startWar(player, warId, guild1, guild2, bounty) + if bounty > 0 then + local guildBalance = guild1:getBankBalance() + if guildBalance < bounty then + player:sendCancelMessage("Your guild does not have that much money in the bank account balance to accept this war with the bounty of " .. bounty .. " gold.") + return true + end + + if not guild1:decreaseBankBalance(bounty) then + player:sendCancelMessage("Your guild does not have that much money in the bank account balance to accept this war with the bounty of " .. bounty .. " gold.") + return true + end + end + + self:updateState(warId, 1) + + self:setWarEmblem(guild1, guild2) + + broadcastMessage(guild1:getName() .. " has accepted " .. guild2:getName() .. " invitation to war.", MESSAGE_EVENT_ADVANCE) +end + +function guildwars:rejectWar(warId, guild1, guild2, bounty) + self:updateState(warId, 2) + broadcastMessage(guild1:getName() .. " has rejected " .. guild2:getName() .. " invitation to war.", MESSAGE_EVENT_ADVANCE) + + if bounty > 0 then + guild2:increaseBankBalance(bounty) + end +end + +function guildwars:cancelWar(warId, guild1, guild2, bounty) + self:updateState(warId, 3) + broadcastMessage(guild1:getName() .. " has canceled invitation to a war with " .. guild2:getName() .. ".", MESSAGE_EVENT_ADVANCE) + + if bounty > 0 then + guild1:increaseBankBalance(bounty) + end +end + +function guildwars:invite(player, guild1, guild2, frags, bounty) + local str = "" + local tmpQuery = db.storeQuery("SELECT `guild1`, `status` FROM `guild_wars` WHERE `guild1` IN (" .. guild1:getId() .. "," .. guild2:getId() .. ") AND `guild2` IN (" .. guild2:getId() .. "," .. guild1:getId() .. ") AND `status` IN (0, 1)") + if tmpQuery then + if result.getDataInt(tmpQuery, "status") == 0 then + if result.getDataInt(tmpQuery, "guild1") == guild1:getId() then + str = "You have already invited " .. guild2:getName() .. " to war." + else + str = guild2:getName() .. " have already invited you to war." + end + else + str = "You are already on a war with " .. guild2:getName() .. "." + end + + result.free(tmpQuery) + end + + if str ~= "" then + player:sendCancelMessage(str) + return true + end + + frags = math.max(10, math.min(500, frags)) + bounty = math.max(0, math.min(10000000, bounty)) + + if bounty > 0 then + local guildBalance = guild1:getBankBalance() + if guildBalance < bounty then + player:sendCancelMessage("Your guild does not have that much money in the bank account balance to set this bounty.") + return true + end + + if not guild1:decreaseBankBalance(bounty) then + player:sendCancelMessage("Your guild does not have that much money in the bank account balance to set this bounty.") + return true + end + end + + db.asyncQuery("INSERT INTO `guild_wars` (`guild1`, `guild2`, `frag_limit`, `bounty`) VALUES (" .. guild1:getId() .. ", " .. guild2:getId() .. ", " .. frags .. ", " .. bounty .. ");") + + local message = guild1:getName() .. " has invited " .. guild2:getName() .. " to war for " .. frags .. " frags." + if bounty > 0 then + message = message .. " The bounty reward is set to " .. bounty .. " gold." + end + broadcastMessage(message, MESSAGE_EVENT_ADVANCE) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/item.lua b/app/SabrehavenServer/data/lib/core/item.lua new file mode 100644 index 0000000..8aa0972 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/item.lua @@ -0,0 +1,31 @@ +function Item.getType(self) + return ItemType(self:getId()) +end + +function Item.isItem(self) + return true +end + +function Item.isContainer(self) + return false +end + +function Item.isCreature(self) + return false +end + +function Item.isMonster(self) + return false +end + +function Item.isPlayer(self) + return false +end + +function Item.isTeleport(self) + return false +end + +function Item.isTile(self) + return false +end diff --git a/app/SabrehavenServer/data/lib/core/itemtype.lua b/app/SabrehavenServer/data/lib/core/itemtype.lua new file mode 100644 index 0000000..c94ba7f --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/itemtype.lua @@ -0,0 +1,16 @@ +local slotBits = { + [CONST_SLOT_HEAD] = SLOTP_HEAD, + [CONST_SLOT_NECKLACE] = SLOTP_NECKLACE, + [CONST_SLOT_BACKPACK] = SLOTP_BACKPACK, + [CONST_SLOT_ARMOR] = SLOTP_ARMOR, + [CONST_SLOT_RIGHT] = SLOTP_RIGHT, + [CONST_SLOT_LEFT] = SLOTP_LEFT, + [CONST_SLOT_LEGS] = SLOTP_LEGS, + [CONST_SLOT_FEET] = SLOTP_FEET, + [CONST_SLOT_RING] = SLOTP_RING, + [CONST_SLOT_AMMO] = SLOTP_AMMO +} + +function ItemType.usesSlot(self, slot) + return bit.band(self:getSlotPosition(), slotBits[slot] or 0) ~= 0 +end diff --git a/app/SabrehavenServer/data/lib/core/monster.lua b/app/SabrehavenServer/data/lib/core/monster.lua new file mode 100644 index 0000000..7624009 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/monster.lua @@ -0,0 +1,27 @@ +function Monster.getMonster(self) + return self:isMonster() and self or nil +end + +function Monster.isItem(self) + return false +end + +function Monster.isMonster(self) + return true +end + +function Monster.isNpc(self) + return false +end + +function Monster.isPlayer(self) + return false +end + +function Monster.isTile(self) + return false +end + +function Monster.isContainer(self) + return false +end diff --git a/app/SabrehavenServer/data/lib/core/player.lua b/app/SabrehavenServer/data/lib/core/player.lua new file mode 100644 index 0000000..2e6d20a --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/player.lua @@ -0,0 +1,99 @@ +local foodCondition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + +function Player.feed(self, food) + local condition = self:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + if condition then + condition:setTicks(condition:getTicks() + (food * 1000)) + else + local vocation = self:getVocation() + if not vocation then + return nil + end + + foodCondition:setTicks(food * 1000) + foodCondition:setParameter(CONDITION_PARAM_HEALTHGAIN, vocation:getHealthGainAmount()) + foodCondition:setParameter(CONDITION_PARAM_HEALTHTICKS, vocation:getHealthGainTicks() * 1000) + foodCondition:setParameter(CONDITION_PARAM_MANAGAIN, vocation:getManaGainAmount()) + foodCondition:setParameter(CONDITION_PARAM_MANATICKS, vocation:getManaGainTicks() * 1000) + + self:addCondition(foodCondition) + end + return true +end + +function Player.getClosestFreePosition(self, position, extended) + if self:getAccountType() >= ACCOUNT_TYPE_GOD then + return position + end + return Creature.getClosestFreePosition(self, position, extended) +end + +function Player.getDepotItems(self, depotId) + return self:getDepotChest(depotId, true):getItemHoldingCount() +end + +function Player.isNoVocation(self) + return self:getVocation():getId() == 0 +end + +function Player.isSorcerer(self) + return self:getVocation():getId() == 1 or self:getVocation():getId() == 5 +end + +function Player.isDruid(self) + return self:getVocation():getId() == 2 or self:getVocation():getId() == 6 +end + +function Player.isPaladin(self) + return self:getVocation():getId() == 3 or self:getVocation():getId() == 7 +end + +function Player.isKnight(self) + return self:getVocation():getId() == 4 or self:getVocation():getId() == 8 +end + +function Player.isPremium(self) + return self:getPremiumDays() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) +end + +function Player.sendCancelMessage(self, message) + if type(message) == "number" then + message = Game.getReturnMessage(message) + end + return self:sendTextMessage(MESSAGE_STATUS_SMALL, message) +end + +function Player.isUsingOtClient(self) + return self:getClient().os >= CLIENTOS_OTCLIENT_LINUX +end + +function Player.sendExtendedOpcode(self, opcode, buffer) + if not self:isUsingOtClient() then + return false + end + + local networkMessage = NetworkMessage() + networkMessage:addByte(0x32) + networkMessage:addByte(opcode) + networkMessage:addString(buffer) + networkMessage:sendToPlayer(self) + networkMessage:delete() + return true +end + +APPLY_SKILL_MULTIPLIER = true +local addSkillTriesFunc = Player.addSkillTries +function Player.addSkillTries(...) + APPLY_SKILL_MULTIPLIER = false + local ret = addSkillTriesFunc(...) + APPLY_SKILL_MULTIPLIER = true + return ret +end + +local addManaSpentFunc = Player.addManaSpent +function Player.addManaSpent(...) + APPLY_SKILL_MULTIPLIER = false + local ret = addManaSpentFunc(...) + APPLY_SKILL_MULTIPLIER = true + return ret +end diff --git a/app/SabrehavenServer/data/lib/core/position.lua b/app/SabrehavenServer/data/lib/core/position.lua new file mode 100644 index 0000000..d5d3a92 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/position.lua @@ -0,0 +1,99 @@ +Position.directionOffset = { + [DIRECTION_NORTH] = {x = 0, y = -1}, + [DIRECTION_EAST] = {x = 1, y = 0}, + [DIRECTION_SOUTH] = {x = 0, y = 1}, + [DIRECTION_WEST] = {x = -1, y = 0}, + [DIRECTION_SOUTHWEST] = {x = -1, y = 1}, + [DIRECTION_SOUTHEAST] = {x = 1, y = 1}, + [DIRECTION_NORTHWEST] = {x = -1, y = -1}, + [DIRECTION_NORTHEAST] = {x = 1, y = -1} +} + +function Position:getNextPosition(direction, steps) + local offset = Position.directionOffset[direction] + if offset then + steps = steps or 1 + self.x = self.x + offset.x * steps + self.y = self.y + offset.y * steps + end +end + +function Position:moveUpstairs() + local isWalkable = function (position) + local tile = Tile(position) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then + return false + end + + local items = tile:getItems() + for i = 1, tile:getItemCount() do + local item = items[i] + local itemType = item:getType() + if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKSOLID) then + return false + end + end + return true + end + + local swap = function (lhs, rhs) + lhs.x, rhs.x = rhs.x, lhs.x + lhs.y, rhs.y = rhs.y, lhs.y + lhs.z, rhs.z = rhs.z, lhs.z + end + + self.z = self.z - 1 + + local defaultPosition = self + Position.directionOffset[DIRECTION_SOUTH] + if not isWalkable(defaultPosition) then + for direction = DIRECTION_NORTH, DIRECTION_NORTHEAST do + if direction == DIRECTION_SOUTH then + direction = DIRECTION_WEST + end + + local position = self + Position.directionOffset[direction] + if isWalkable(position) then + swap(self, position) + return self + end + end + end + swap(self, defaultPosition) + return self +end + +function Position:moveRel(x, y, z) + self.x = self.x + x + self.y = self.y + y + self.z = self.z + z + return self +end + +function Position:isInRange(from, to) + -- No matter what corner from and to is, we want to make + -- life easier by calculating north-west and south-east + local zone = { + nW = { + x = (from.x < to.x and from.x or to.x), + y = (from.y < to.y and from.y or to.y), + z = (from.z < to.z and from.z or to.z) + }, + sE = { + x = (to.x > from.x and to.x or from.x), + y = (to.y > from.y and to.y or from.y), + z = (to.z > from.z and to.z or from.z) + } + } + + 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.z >= zone.nW.z and self.z <= zone.sE.z then + return true + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/teleport.lua b/app/SabrehavenServer/data/lib/core/teleport.lua new file mode 100644 index 0000000..8930268 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/teleport.lua @@ -0,0 +1,3 @@ +function Teleport.isTeleport(self) + return true +end diff --git a/app/SabrehavenServer/data/lib/core/tile.lua b/app/SabrehavenServer/data/lib/core/tile.lua new file mode 100644 index 0000000..de3072f --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/tile.lua @@ -0,0 +1,65 @@ +function Tile.isItem(self) + return false +end + +function Tile.isContainer(self) + return false +end + +function Tile.isCreature(self) + return false +end + +function Tile.isPlayer(self) + return false +end + +function Tile.isTeleport(self) + return false +end + +function Tile.isTile(self) + return true +end + +function Tile.relocateTo(self, toPosition, pushMove, monsterPosition) + if self:getPosition() == toPosition then + return false + end + + if not Tile(toPosition) then + return false + end + + for i = self:getThingCount() - 1, 0, -1 do + local thing = self:getThing(i) + if thing then + if thing:isItem() then + if ItemType(thing.itemid):isMovable() then + thing:moveTo(toPosition) + end + elseif thing:isCreature() then + if monsterPosition and thing:isMonster() then + thing:teleportTo(monsterPosition, pushMove) + else + thing:teleportTo(toPosition, pushMove) + end + end + end + end + return true +end + +function Tile:getPlayers() + local players = {} + local creatures = self:getCreatures() + if (creatures) then + for i = 1, #creatures do + if (creatures[i]:isPlayer()) then + table.insert(players, creatures[i]) + end + end + end + + return players +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/core/vocation.lua b/app/SabrehavenServer/data/lib/core/vocation.lua new file mode 100644 index 0000000..33772a5 --- /dev/null +++ b/app/SabrehavenServer/data/lib/core/vocation.lua @@ -0,0 +1,7 @@ +function Vocation.getBase(self) + local base = self + while base:getDemotion() do + base = base:getDemotion() + end + return base +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/lib/lib.lua b/app/SabrehavenServer/data/lib/lib.lua new file mode 100644 index 0000000..5a0e2b5 --- /dev/null +++ b/app/SabrehavenServer/data/lib/lib.lua @@ -0,0 +1,5 @@ +-- Core API functions implemented in Lua +dofile('data/lib/core/core.lua') + +-- Compatibility library for our old Lua API +dofile('data/lib/compat/compat.lua') diff --git a/app/SabrehavenServer/data/monster/781/acolyte of the cult.xml b/app/SabrehavenServer/data/monster/781/acolyte of the cult.xml new file mode 100644 index 0000000..65e7387 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/acolyte of the cult.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/adept of the cult.xml b/app/SabrehavenServer/data/monster/781/adept of the cult.xml new file mode 100644 index 0000000..f7bd43f --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/adept of the cult.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/azure frog.xml b/app/SabrehavenServer/data/monster/781/azure frog.xml new file mode 100644 index 0000000..12fbe88 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/azure frog.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/blood crab.xml b/app/SabrehavenServer/data/monster/781/blood crab.xml new file mode 100644 index 0000000..87e40e1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/blood crab.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/brutus bloodbeard.xml b/app/SabrehavenServer/data/monster/781/brutus bloodbeard.xml new file mode 100644 index 0000000..62c3b89 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/brutus bloodbeard.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/carrion worm.xml b/app/SabrehavenServer/data/monster/781/carrion worm.xml new file mode 100644 index 0000000..faecb29 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/carrion worm.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/coral frog.xml b/app/SabrehavenServer/data/monster/781/coral frog.xml new file mode 100644 index 0000000..13680eb --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/coral frog.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/crimson frog.xml b/app/SabrehavenServer/data/monster/781/crimson frog.xml new file mode 100644 index 0000000..6322572 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/crimson frog.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/deadeye devious.xml b/app/SabrehavenServer/data/monster/781/deadeye devious.xml new file mode 100644 index 0000000..ac4df84 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/deadeye devious.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/enlightened of the cult.xml b/app/SabrehavenServer/data/monster/781/enlightened of the cult.xml new file mode 100644 index 0000000..d0a343d --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/enlightened of the cult.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/green frog.xml b/app/SabrehavenServer/data/monster/781/green frog.xml new file mode 100644 index 0000000..1685b29 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/green frog.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/island troll.xml b/app/SabrehavenServer/data/monster/781/island troll.xml new file mode 100644 index 0000000..fc0ca97 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/island troll.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/lavahole.xml b/app/SabrehavenServer/data/monster/781/lavahole.xml new file mode 100644 index 0000000..57ba857 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/lavahole.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/lethal lissy.xml b/app/SabrehavenServer/data/monster/781/lethal lissy.xml new file mode 100644 index 0000000..c387f63 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/lethal lissy.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/magic pillar.xml b/app/SabrehavenServer/data/monster/781/magic pillar.xml new file mode 100644 index 0000000..e3b7a56 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/magic pillar.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/mammoth.xml b/app/SabrehavenServer/data/monster/781/mammoth.xml new file mode 100644 index 0000000..e2ca883 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/mammoth.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/massive water elemental.xml b/app/SabrehavenServer/data/monster/781/massive water elemental.xml new file mode 100644 index 0000000..9bb6fa8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/massive water elemental.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/morgaroth.xml b/app/SabrehavenServer/data/monster/781/morgaroth.xml new file mode 100644 index 0000000..fbd301e --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/morgaroth.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/novice of the cult.xml b/app/SabrehavenServer/data/monster/781/novice of the cult.xml new file mode 100644 index 0000000..a7a8172 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/novice of the cult.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/orchid frog.xml b/app/SabrehavenServer/data/monster/781/orchid frog.xml new file mode 100644 index 0000000..4f2e9ac --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/orchid frog.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate buccaneer.xml b/app/SabrehavenServer/data/monster/781/pirate buccaneer.xml new file mode 100644 index 0000000..c1123fe --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate buccaneer.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate corsair.xml b/app/SabrehavenServer/data/monster/781/pirate corsair.xml new file mode 100644 index 0000000..c1ea8d8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate corsair.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate cutthroat.xml b/app/SabrehavenServer/data/monster/781/pirate cutthroat.xml new file mode 100644 index 0000000..54df4c3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate cutthroat.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate ghost.xml b/app/SabrehavenServer/data/monster/781/pirate ghost.xml new file mode 100644 index 0000000..609cdea --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate ghost.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate marauder.xml b/app/SabrehavenServer/data/monster/781/pirate marauder.xml new file mode 100644 index 0000000..f4453c1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate marauder.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/pirate skeleton.xml b/app/SabrehavenServer/data/monster/781/pirate skeleton.xml new file mode 100644 index 0000000..cdbac7f --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/pirate skeleton.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara constrictor scout.xml b/app/SabrehavenServer/data/monster/781/quara constrictor scout.xml new file mode 100644 index 0000000..e6cf6d7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara constrictor scout.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara constrictor.xml b/app/SabrehavenServer/data/monster/781/quara constrictor.xml new file mode 100644 index 0000000..97ccb44 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara constrictor.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara hydromancer scout.xml b/app/SabrehavenServer/data/monster/781/quara hydromancer scout.xml new file mode 100644 index 0000000..c7ceefb --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara hydromancer scout.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara hydromancer.xml b/app/SabrehavenServer/data/monster/781/quara hydromancer.xml new file mode 100644 index 0000000..19fa6db --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara hydromancer.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara mantassin scout.xml b/app/SabrehavenServer/data/monster/781/quara mantassin scout.xml new file mode 100644 index 0000000..4234ad5 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara mantassin scout.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara mantassin.xml b/app/SabrehavenServer/data/monster/781/quara mantassin.xml new file mode 100644 index 0000000..9937329 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara mantassin.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara pincher scout.xml b/app/SabrehavenServer/data/monster/781/quara pincher scout.xml new file mode 100644 index 0000000..e2653c6 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara pincher scout.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara pincher.xml b/app/SabrehavenServer/data/monster/781/quara pincher.xml new file mode 100644 index 0000000..62e3782 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara pincher.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara predator scout.xml b/app/SabrehavenServer/data/monster/781/quara predator scout.xml new file mode 100644 index 0000000..7be5c1e --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara predator scout.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/quara predator.xml b/app/SabrehavenServer/data/monster/781/quara predator.xml new file mode 100644 index 0000000..c2afb14 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/quara predator.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/ron the ripper.xml b/app/SabrehavenServer/data/monster/781/ron the ripper.xml new file mode 100644 index 0000000..2a3cdf2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/ron the ripper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/seagull.xml b/app/SabrehavenServer/data/monster/781/seagull.xml new file mode 100644 index 0000000..a64e4a6 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/seagull.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/thornback tortoise.xml b/app/SabrehavenServer/data/monster/781/thornback tortoise.xml new file mode 100644 index 0000000..253c02d --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/thornback tortoise.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/thul.xml b/app/SabrehavenServer/data/monster/781/thul.xml new file mode 100644 index 0000000..4a17f3f --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/thul.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/tiquandas revenge.xml b/app/SabrehavenServer/data/monster/781/tiquandas revenge.xml new file mode 100644 index 0000000..d1aa277 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/tiquandas revenge.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/toad.xml b/app/SabrehavenServer/data/monster/781/toad.xml new file mode 100644 index 0000000..9b4f224 --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/toad.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/781/tortoise.xml b/app/SabrehavenServer/data/monster/781/tortoise.xml new file mode 100644 index 0000000..876b0aa --- /dev/null +++ b/app/SabrehavenServer/data/monster/781/tortoise.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/apprentice sheng.xml b/app/SabrehavenServer/data/monster/790/apprentice sheng.xml new file mode 100644 index 0000000..9ac6d28 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/apprentice sheng.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/betrayed wraith.xml b/app/SabrehavenServer/data/monster/790/betrayed wraith.xml new file mode 100644 index 0000000..fef2a23 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/betrayed wraith.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/blightwalker.xml b/app/SabrehavenServer/data/monster/790/blightwalker.xml new file mode 100644 index 0000000..b84e5c8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/blightwalker.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/countess sorrow.xml b/app/SabrehavenServer/data/monster/790/countess sorrow.xml new file mode 100644 index 0000000..6207cee --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/countess sorrow.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/dark torturer.xml b/app/SabrehavenServer/data/monster/790/dark torturer.xml new file mode 100644 index 0000000..91c3b3e --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/dark torturer.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/defiler.xml b/app/SabrehavenServer/data/monster/790/defiler.xml new file mode 100644 index 0000000..6b3b807 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/defiler.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/demon (goblin).xml b/app/SabrehavenServer/data/monster/790/demon (goblin).xml new file mode 100644 index 0000000..755e1a3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/demon (goblin).xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/monster/790/destroyer.xml b/app/SabrehavenServer/data/monster/790/destroyer.xml new file mode 100644 index 0000000..db0251a --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/destroyer.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/diabolic imp.xml b/app/SabrehavenServer/data/monster/790/diabolic imp.xml new file mode 100644 index 0000000..a3508b2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/diabolic imp.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/dracola.xml b/app/SabrehavenServer/data/monster/790/dracola.xml new file mode 100644 index 0000000..356d267 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/dracola.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/fury.xml b/app/SabrehavenServer/data/monster/790/fury.xml new file mode 100644 index 0000000..07ed8ce --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/fury.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/grynch clan goblin.xml b/app/SabrehavenServer/data/monster/790/grynch clan goblin.xml new file mode 100644 index 0000000..c2d368b --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/grynch clan goblin.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/hand of cursed fate.xml b/app/SabrehavenServer/data/monster/790/hand of cursed fate.xml new file mode 100644 index 0000000..db6bbe3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/hand of cursed fate.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/hellfire fighter.xml b/app/SabrehavenServer/data/monster/790/hellfire fighter.xml new file mode 100644 index 0000000..a075d17 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/hellfire fighter.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/hellhound.xml b/app/SabrehavenServer/data/monster/790/hellhound.xml new file mode 100644 index 0000000..00916e2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/hellhound.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/juggernaut.xml b/app/SabrehavenServer/data/monster/790/juggernaut.xml new file mode 100644 index 0000000..34bac7e --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/juggernaut.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/lost soul.xml b/app/SabrehavenServer/data/monster/790/lost soul.xml new file mode 100644 index 0000000..15e8a2c --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/lost soul.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/massacre.xml b/app/SabrehavenServer/data/monster/790/massacre.xml new file mode 100644 index 0000000..4fa71ae --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/massacre.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/mr. punish.xml b/app/SabrehavenServer/data/monster/790/mr. punish.xml new file mode 100644 index 0000000..030e9b1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/mr. punish.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/munster.xml b/app/SabrehavenServer/data/monster/790/munster.xml new file mode 100644 index 0000000..f8089de --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/munster.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/nightmare.xml b/app/SabrehavenServer/data/monster/790/nightmare.xml new file mode 100644 index 0000000..0156afc --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/nightmare.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/nomad.xml b/app/SabrehavenServer/data/monster/790/nomad.xml new file mode 100644 index 0000000..4eb3912 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/nomad.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/phantasm summon.xml b/app/SabrehavenServer/data/monster/790/phantasm summon.xml new file mode 100644 index 0000000..52beff2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/phantasm summon.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/phantasm.xml b/app/SabrehavenServer/data/monster/790/phantasm.xml new file mode 100644 index 0000000..f3fd13d --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/phantasm.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/plaguesmith.xml b/app/SabrehavenServer/data/monster/790/plaguesmith.xml new file mode 100644 index 0000000..1bd5f4d --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/plaguesmith.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/son of verminor.xml b/app/SabrehavenServer/data/monster/790/son of verminor.xml new file mode 100644 index 0000000..3dddb91 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/son of verminor.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/spectre.xml b/app/SabrehavenServer/data/monster/790/spectre.xml new file mode 100644 index 0000000..e9f075d --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/spectre.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/the handmaiden.xml b/app/SabrehavenServer/data/monster/790/the handmaiden.xml new file mode 100644 index 0000000..39dc681 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/the handmaiden.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/the imperor.xml b/app/SabrehavenServer/data/monster/790/the imperor.xml new file mode 100644 index 0000000..e006ec7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/the imperor.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/the plasmother.xml b/app/SabrehavenServer/data/monster/790/the plasmother.xml new file mode 100644 index 0000000..546abe5 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/the plasmother.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/undead dragon.xml b/app/SabrehavenServer/data/monster/790/undead dragon.xml new file mode 100644 index 0000000..adca963 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/undead dragon.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/790/wyvern.xml b/app/SabrehavenServer/data/monster/790/wyvern.xml new file mode 100644 index 0000000..dd45c95 --- /dev/null +++ b/app/SabrehavenServer/data/monster/790/wyvern.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/Training Monk.xml b/app/SabrehavenServer/data/monster/Training Monk.xml new file mode 100644 index 0000000..0f1163e --- /dev/null +++ b/app/SabrehavenServer/data/monster/Training Monk.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/monster/amazon.xml b/app/SabrehavenServer/data/monster/amazon.xml new file mode 100644 index 0000000..086316b --- /dev/null +++ b/app/SabrehavenServer/data/monster/amazon.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/ancientscarab.xml b/app/SabrehavenServer/data/monster/ancientscarab.xml new file mode 100644 index 0000000..46e994f --- /dev/null +++ b/app/SabrehavenServer/data/monster/ancientscarab.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/apocalypse.xml b/app/SabrehavenServer/data/monster/apocalypse.xml new file mode 100644 index 0000000..bc1aad5 --- /dev/null +++ b/app/SabrehavenServer/data/monster/apocalypse.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/assassin.xml b/app/SabrehavenServer/data/monster/assassin.xml new file mode 100644 index 0000000..3094f53 --- /dev/null +++ b/app/SabrehavenServer/data/monster/assassin.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/badger.xml b/app/SabrehavenServer/data/monster/badger.xml new file mode 100644 index 0000000..fbba5a2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/badger.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bandit.xml b/app/SabrehavenServer/data/monster/bandit.xml new file mode 100644 index 0000000..53988c3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bandit.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/banshee.xml b/app/SabrehavenServer/data/monster/banshee.xml new file mode 100644 index 0000000..04e24a2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/banshee.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bat.xml b/app/SabrehavenServer/data/monster/bat.xml new file mode 100644 index 0000000..6989e62 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bat.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bazir.xml b/app/SabrehavenServer/data/monster/bazir.xml new file mode 100644 index 0000000..cf07a62 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bazir.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bear.xml b/app/SabrehavenServer/data/monster/bear.xml new file mode 100644 index 0000000..bee5c9e --- /dev/null +++ b/app/SabrehavenServer/data/monster/bear.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/behemoth.xml b/app/SabrehavenServer/data/monster/behemoth.xml new file mode 100644 index 0000000..54a1bf3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/behemoth.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/beholder.xml b/app/SabrehavenServer/data/monster/beholder.xml new file mode 100644 index 0000000..3ba751a --- /dev/null +++ b/app/SabrehavenServer/data/monster/beholder.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/blackknight.xml b/app/SabrehavenServer/data/monster/blackknight.xml new file mode 100644 index 0000000..67b04af --- /dev/null +++ b/app/SabrehavenServer/data/monster/blackknight.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/blacksheep.xml b/app/SabrehavenServer/data/monster/blacksheep.xml new file mode 100644 index 0000000..dff5c86 --- /dev/null +++ b/app/SabrehavenServer/data/monster/blacksheep.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bluedjinn.xml b/app/SabrehavenServer/data/monster/bluedjinn.xml new file mode 100644 index 0000000..87c4b31 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bluedjinn.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bonebeast.xml b/app/SabrehavenServer/data/monster/bonebeast.xml new file mode 100644 index 0000000..1022412 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bonebeast.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/bug.xml b/app/SabrehavenServer/data/monster/bug.xml new file mode 100644 index 0000000..a30e225 --- /dev/null +++ b/app/SabrehavenServer/data/monster/bug.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/butterflyblue.xml b/app/SabrehavenServer/data/monster/butterflyblue.xml new file mode 100644 index 0000000..1c8b161 --- /dev/null +++ b/app/SabrehavenServer/data/monster/butterflyblue.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/butterflypurple.xml b/app/SabrehavenServer/data/monster/butterflypurple.xml new file mode 100644 index 0000000..ab1805b --- /dev/null +++ b/app/SabrehavenServer/data/monster/butterflypurple.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/butterflyred.xml b/app/SabrehavenServer/data/monster/butterflyred.xml new file mode 100644 index 0000000..2e4c883 --- /dev/null +++ b/app/SabrehavenServer/data/monster/butterflyred.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/butterflyyellow.xml b/app/SabrehavenServer/data/monster/butterflyyellow.xml new file mode 100644 index 0000000..e0acc42 --- /dev/null +++ b/app/SabrehavenServer/data/monster/butterflyyellow.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/carniphila.xml b/app/SabrehavenServer/data/monster/carniphila.xml new file mode 100644 index 0000000..e6ec095 --- /dev/null +++ b/app/SabrehavenServer/data/monster/carniphila.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/caverat.xml b/app/SabrehavenServer/data/monster/caverat.xml new file mode 100644 index 0000000..6b5afab --- /dev/null +++ b/app/SabrehavenServer/data/monster/caverat.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/centipede.xml b/app/SabrehavenServer/data/monster/centipede.xml new file mode 100644 index 0000000..599bd34 --- /dev/null +++ b/app/SabrehavenServer/data/monster/centipede.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/chicken.xml b/app/SabrehavenServer/data/monster/chicken.xml new file mode 100644 index 0000000..d2e6394 --- /dev/null +++ b/app/SabrehavenServer/data/monster/chicken.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/cobra.xml b/app/SabrehavenServer/data/monster/cobra.xml new file mode 100644 index 0000000..73f44e7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/cobra.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/crab.xml b/app/SabrehavenServer/data/monster/crab.xml new file mode 100644 index 0000000..d68c19f --- /dev/null +++ b/app/SabrehavenServer/data/monster/crab.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/crocodile.xml b/app/SabrehavenServer/data/monster/crocodile.xml new file mode 100644 index 0000000..d9c9ec8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/crocodile.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/cryptshambler.xml b/app/SabrehavenServer/data/monster/cryptshambler.xml new file mode 100644 index 0000000..ca632b7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/cryptshambler.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/cyclops.xml b/app/SabrehavenServer/data/monster/cyclops.xml new file mode 100644 index 0000000..8b80d25 --- /dev/null +++ b/app/SabrehavenServer/data/monster/cyclops.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/darkmonk.xml b/app/SabrehavenServer/data/monster/darkmonk.xml new file mode 100644 index 0000000..f79e1b5 --- /dev/null +++ b/app/SabrehavenServer/data/monster/darkmonk.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/deathslicer.xml b/app/SabrehavenServer/data/monster/deathslicer.xml new file mode 100644 index 0000000..5d81036 --- /dev/null +++ b/app/SabrehavenServer/data/monster/deathslicer.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/deer.xml b/app/SabrehavenServer/data/monster/deer.xml new file mode 100644 index 0000000..d7de253 --- /dev/null +++ b/app/SabrehavenServer/data/monster/deer.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/demodras.xml b/app/SabrehavenServer/data/monster/demodras.xml new file mode 100644 index 0000000..059bc84 --- /dev/null +++ b/app/SabrehavenServer/data/monster/demodras.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/demon.xml b/app/SabrehavenServer/data/monster/demon.xml new file mode 100644 index 0000000..a5e2460 --- /dev/null +++ b/app/SabrehavenServer/data/monster/demon.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/demonskeleton.xml b/app/SabrehavenServer/data/monster/demonskeleton.xml new file mode 100644 index 0000000..1fcb7a8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/demonskeleton.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dharalion.xml b/app/SabrehavenServer/data/monster/dharalion.xml new file mode 100644 index 0000000..e70dd5f --- /dev/null +++ b/app/SabrehavenServer/data/monster/dharalion.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dog.xml b/app/SabrehavenServer/data/monster/dog.xml new file mode 100644 index 0000000..8b77c07 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dog.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dragon.xml b/app/SabrehavenServer/data/monster/dragon.xml new file mode 100644 index 0000000..ebed74e --- /dev/null +++ b/app/SabrehavenServer/data/monster/dragon.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dragonlord.xml b/app/SabrehavenServer/data/monster/dragonlord.xml new file mode 100644 index 0000000..9ed6ee4 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dragonlord.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dwarf.xml b/app/SabrehavenServer/data/monster/dwarf.xml new file mode 100644 index 0000000..2e317ac --- /dev/null +++ b/app/SabrehavenServer/data/monster/dwarf.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dwarfgeomancer.xml b/app/SabrehavenServer/data/monster/dwarfgeomancer.xml new file mode 100644 index 0000000..fe13f56 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dwarfgeomancer.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dwarfguard.xml b/app/SabrehavenServer/data/monster/dwarfguard.xml new file mode 100644 index 0000000..2186e89 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dwarfguard.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dwarfsoldier.xml b/app/SabrehavenServer/data/monster/dwarfsoldier.xml new file mode 100644 index 0000000..a5a243f --- /dev/null +++ b/app/SabrehavenServer/data/monster/dwarfsoldier.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dworcfleshhunter.xml b/app/SabrehavenServer/data/monster/dworcfleshhunter.xml new file mode 100644 index 0000000..833ebf3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dworcfleshhunter.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dworcvenomsniper.xml b/app/SabrehavenServer/data/monster/dworcvenomsniper.xml new file mode 100644 index 0000000..b8da58e --- /dev/null +++ b/app/SabrehavenServer/data/monster/dworcvenomsniper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/dworcvoodoomaster.xml b/app/SabrehavenServer/data/monster/dworcvoodoomaster.xml new file mode 100644 index 0000000..38d4521 --- /dev/null +++ b/app/SabrehavenServer/data/monster/dworcvoodoomaster.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/efreet.xml b/app/SabrehavenServer/data/monster/efreet.xml new file mode 100644 index 0000000..cfdc446 --- /dev/null +++ b/app/SabrehavenServer/data/monster/efreet.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/elderbeholder.xml b/app/SabrehavenServer/data/monster/elderbeholder.xml new file mode 100644 index 0000000..da6f0d1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/elderbeholder.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/elephant.xml b/app/SabrehavenServer/data/monster/elephant.xml new file mode 100644 index 0000000..ca2eb8e --- /dev/null +++ b/app/SabrehavenServer/data/monster/elephant.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/elf.xml b/app/SabrehavenServer/data/monster/elf.xml new file mode 100644 index 0000000..e4acc24 --- /dev/null +++ b/app/SabrehavenServer/data/monster/elf.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/elfarcanist.xml b/app/SabrehavenServer/data/monster/elfarcanist.xml new file mode 100644 index 0000000..c40323f --- /dev/null +++ b/app/SabrehavenServer/data/monster/elfarcanist.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/elfscout.xml b/app/SabrehavenServer/data/monster/elfscout.xml new file mode 100644 index 0000000..364bfd6 --- /dev/null +++ b/app/SabrehavenServer/data/monster/elfscout.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/evileye.xml b/app/SabrehavenServer/data/monster/evileye.xml new file mode 100644 index 0000000..eed1a2c --- /dev/null +++ b/app/SabrehavenServer/data/monster/evileye.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/fernfang.xml b/app/SabrehavenServer/data/monster/fernfang.xml new file mode 100644 index 0000000..17ce892 --- /dev/null +++ b/app/SabrehavenServer/data/monster/fernfang.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/ferumbras.xml b/app/SabrehavenServer/data/monster/ferumbras.xml new file mode 100644 index 0000000..cc11d19 --- /dev/null +++ b/app/SabrehavenServer/data/monster/ferumbras.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/firedevil.xml b/app/SabrehavenServer/data/monster/firedevil.xml new file mode 100644 index 0000000..09338e7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/firedevil.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/fireelemental.xml b/app/SabrehavenServer/data/monster/fireelemental.xml new file mode 100644 index 0000000..21d7546 --- /dev/null +++ b/app/SabrehavenServer/data/monster/fireelemental.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/flamethrower.xml b/app/SabrehavenServer/data/monster/flamethrower.xml new file mode 100644 index 0000000..d1308ca --- /dev/null +++ b/app/SabrehavenServer/data/monster/flamethrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/flamingo.xml b/app/SabrehavenServer/data/monster/flamingo.xml new file mode 100644 index 0000000..85fed16 --- /dev/null +++ b/app/SabrehavenServer/data/monster/flamingo.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/frosttroll.xml b/app/SabrehavenServer/data/monster/frosttroll.xml new file mode 100644 index 0000000..a73c560 --- /dev/null +++ b/app/SabrehavenServer/data/monster/frosttroll.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/gamemaster.xml b/app/SabrehavenServer/data/monster/gamemaster.xml new file mode 100644 index 0000000..a5662f1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/gamemaster.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/gargoyle.xml b/app/SabrehavenServer/data/monster/gargoyle.xml new file mode 100644 index 0000000..10d2fc4 --- /dev/null +++ b/app/SabrehavenServer/data/monster/gargoyle.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/gazer.xml b/app/SabrehavenServer/data/monster/gazer.xml new file mode 100644 index 0000000..308b9d0 --- /dev/null +++ b/app/SabrehavenServer/data/monster/gazer.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/ghost.xml b/app/SabrehavenServer/data/monster/ghost.xml new file mode 100644 index 0000000..a35a45e --- /dev/null +++ b/app/SabrehavenServer/data/monster/ghost.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/ghoul.xml b/app/SabrehavenServer/data/monster/ghoul.xml new file mode 100644 index 0000000..bab0eb7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/ghoul.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/giantspider.xml b/app/SabrehavenServer/data/monster/giantspider.xml new file mode 100644 index 0000000..268d2dc --- /dev/null +++ b/app/SabrehavenServer/data/monster/giantspider.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/goblin.xml b/app/SabrehavenServer/data/monster/goblin.xml new file mode 100644 index 0000000..c418628 --- /dev/null +++ b/app/SabrehavenServer/data/monster/goblin.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/greendjinn.xml b/app/SabrehavenServer/data/monster/greendjinn.xml new file mode 100644 index 0000000..0a0a821 --- /dev/null +++ b/app/SabrehavenServer/data/monster/greendjinn.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/grorlam.xml b/app/SabrehavenServer/data/monster/grorlam.xml new file mode 100644 index 0000000..8a4b60a --- /dev/null +++ b/app/SabrehavenServer/data/monster/grorlam.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/halloweenhare.xml b/app/SabrehavenServer/data/monster/halloweenhare.xml new file mode 100644 index 0000000..726ee5d --- /dev/null +++ b/app/SabrehavenServer/data/monster/halloweenhare.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/hero.xml b/app/SabrehavenServer/data/monster/hero.xml new file mode 100644 index 0000000..d43cc45 --- /dev/null +++ b/app/SabrehavenServer/data/monster/hero.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/hornedfox.xml b/app/SabrehavenServer/data/monster/hornedfox.xml new file mode 100644 index 0000000..e6d8269 --- /dev/null +++ b/app/SabrehavenServer/data/monster/hornedfox.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/hunter.xml b/app/SabrehavenServer/data/monster/hunter.xml new file mode 100644 index 0000000..7ebe470 --- /dev/null +++ b/app/SabrehavenServer/data/monster/hunter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/hyaena.xml b/app/SabrehavenServer/data/monster/hyaena.xml new file mode 100644 index 0000000..add1ef9 --- /dev/null +++ b/app/SabrehavenServer/data/monster/hyaena.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/hydra.xml b/app/SabrehavenServer/data/monster/hydra.xml new file mode 100644 index 0000000..e35a186 --- /dev/null +++ b/app/SabrehavenServer/data/monster/hydra.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/illusion.xml b/app/SabrehavenServer/data/monster/illusion.xml new file mode 100644 index 0000000..05866e2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/illusion.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/infernatil.xml b/app/SabrehavenServer/data/monster/infernatil.xml new file mode 100644 index 0000000..07048ff --- /dev/null +++ b/app/SabrehavenServer/data/monster/infernatil.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/kongra.xml b/app/SabrehavenServer/data/monster/kongra.xml new file mode 100644 index 0000000..3e5c1da --- /dev/null +++ b/app/SabrehavenServer/data/monster/kongra.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/larva.xml b/app/SabrehavenServer/data/monster/larva.xml new file mode 100644 index 0000000..b80902a --- /dev/null +++ b/app/SabrehavenServer/data/monster/larva.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/lich.xml b/app/SabrehavenServer/data/monster/lich.xml new file mode 100644 index 0000000..2afb8f1 --- /dev/null +++ b/app/SabrehavenServer/data/monster/lich.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/lion.xml b/app/SabrehavenServer/data/monster/lion.xml new file mode 100644 index 0000000..cff0798 --- /dev/null +++ b/app/SabrehavenServer/data/monster/lion.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/lizardsentinel.xml b/app/SabrehavenServer/data/monster/lizardsentinel.xml new file mode 100644 index 0000000..ebfcc14 --- /dev/null +++ b/app/SabrehavenServer/data/monster/lizardsentinel.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/lizardsnakecharmer.xml b/app/SabrehavenServer/data/monster/lizardsnakecharmer.xml new file mode 100644 index 0000000..555d16b --- /dev/null +++ b/app/SabrehavenServer/data/monster/lizardsnakecharmer.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/lizardtemplar.xml b/app/SabrehavenServer/data/monster/lizardtemplar.xml new file mode 100644 index 0000000..57a74e6 --- /dev/null +++ b/app/SabrehavenServer/data/monster/lizardtemplar.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/magicthrower.xml b/app/SabrehavenServer/data/monster/magicthrower.xml new file mode 100644 index 0000000..3fc40db --- /dev/null +++ b/app/SabrehavenServer/data/monster/magicthrower.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/marid.xml b/app/SabrehavenServer/data/monster/marid.xml new file mode 100644 index 0000000..a1a7865 --- /dev/null +++ b/app/SabrehavenServer/data/monster/marid.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/merlkin.xml b/app/SabrehavenServer/data/monster/merlkin.xml new file mode 100644 index 0000000..6d48f00 --- /dev/null +++ b/app/SabrehavenServer/data/monster/merlkin.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/mimic.xml b/app/SabrehavenServer/data/monster/mimic.xml new file mode 100644 index 0000000..5ddc26f --- /dev/null +++ b/app/SabrehavenServer/data/monster/mimic.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/minotaur.xml b/app/SabrehavenServer/data/monster/minotaur.xml new file mode 100644 index 0000000..d7ad215 --- /dev/null +++ b/app/SabrehavenServer/data/monster/minotaur.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/minotaurarcher.xml b/app/SabrehavenServer/data/monster/minotaurarcher.xml new file mode 100644 index 0000000..216ebd7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/minotaurarcher.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/minotaurguard.xml b/app/SabrehavenServer/data/monster/minotaurguard.xml new file mode 100644 index 0000000..a4bd67c --- /dev/null +++ b/app/SabrehavenServer/data/monster/minotaurguard.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/minotaurmage.xml b/app/SabrehavenServer/data/monster/minotaurmage.xml new file mode 100644 index 0000000..52d0e3d --- /dev/null +++ b/app/SabrehavenServer/data/monster/minotaurmage.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/monk.xml b/app/SabrehavenServer/data/monster/monk.xml new file mode 100644 index 0000000..f69caa0 --- /dev/null +++ b/app/SabrehavenServer/data/monster/monk.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/monsters.xml b/app/SabrehavenServer/data/monster/monsters.xml new file mode 100644 index 0000000..38eb1e9 --- /dev/null +++ b/app/SabrehavenServer/data/monster/monsters.xml @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/morgaroth.xml b/app/SabrehavenServer/data/monster/morgaroth.xml new file mode 100644 index 0000000..0475508 --- /dev/null +++ b/app/SabrehavenServer/data/monster/morgaroth.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/mummy.xml b/app/SabrehavenServer/data/monster/mummy.xml new file mode 100644 index 0000000..28d2cc4 --- /dev/null +++ b/app/SabrehavenServer/data/monster/mummy.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/murius.xml b/app/SabrehavenServer/data/monster/murius.xml new file mode 100644 index 0000000..5b5cc51 --- /dev/null +++ b/app/SabrehavenServer/data/monster/murius.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/necromancer.xml b/app/SabrehavenServer/data/monster/necromancer.xml new file mode 100644 index 0000000..3cdff05 --- /dev/null +++ b/app/SabrehavenServer/data/monster/necromancer.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/necropharus.xml b/app/SabrehavenServer/data/monster/necropharus.xml new file mode 100644 index 0000000..070a972 --- /dev/null +++ b/app/SabrehavenServer/data/monster/necropharus.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/oldwidow.xml b/app/SabrehavenServer/data/monster/oldwidow.xml new file mode 100644 index 0000000..2eb1394 --- /dev/null +++ b/app/SabrehavenServer/data/monster/oldwidow.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orc.xml b/app/SabrehavenServer/data/monster/orc.xml new file mode 100644 index 0000000..15578d0 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orc.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcberserker.xml b/app/SabrehavenServer/data/monster/orcberserker.xml new file mode 100644 index 0000000..df2ecd3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcberserker.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcleader.xml b/app/SabrehavenServer/data/monster/orcleader.xml new file mode 100644 index 0000000..53b288a --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcleader.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcrider.xml b/app/SabrehavenServer/data/monster/orcrider.xml new file mode 100644 index 0000000..3eb9b52 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcrider.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcshaman.xml b/app/SabrehavenServer/data/monster/orcshaman.xml new file mode 100644 index 0000000..49e3558 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcshaman.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcspearman.xml b/app/SabrehavenServer/data/monster/orcspearman.xml new file mode 100644 index 0000000..4e479a5 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcspearman.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcwarlord.xml b/app/SabrehavenServer/data/monster/orcwarlord.xml new file mode 100644 index 0000000..2f287cb --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcwarlord.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orcwarrior.xml b/app/SabrehavenServer/data/monster/orcwarrior.xml new file mode 100644 index 0000000..320ef96 --- /dev/null +++ b/app/SabrehavenServer/data/monster/orcwarrior.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/orshabaal.xml b/app/SabrehavenServer/data/monster/orshabaal.xml new file mode 100644 index 0000000..b9c4d4f --- /dev/null +++ b/app/SabrehavenServer/data/monster/orshabaal.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/panda.xml b/app/SabrehavenServer/data/monster/panda.xml new file mode 100644 index 0000000..89d93e8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/panda.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/parrot.xml b/app/SabrehavenServer/data/monster/parrot.xml new file mode 100644 index 0000000..cdfd242 --- /dev/null +++ b/app/SabrehavenServer/data/monster/parrot.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohashmunrah.xml b/app/SabrehavenServer/data/monster/pharaohashmunrah.xml new file mode 100644 index 0000000..95fb73e --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohashmunrah.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohdipthrah.xml b/app/SabrehavenServer/data/monster/pharaohdipthrah.xml new file mode 100644 index 0000000..1ba7b75 --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohdipthrah.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohmahrdis.xml b/app/SabrehavenServer/data/monster/pharaohmahrdis.xml new file mode 100644 index 0000000..81aace7 --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohmahrdis.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohmorguthis.xml b/app/SabrehavenServer/data/monster/pharaohmorguthis.xml new file mode 100644 index 0000000..49d48f9 --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohmorguthis.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohomruc.xml b/app/SabrehavenServer/data/monster/pharaohomruc.xml new file mode 100644 index 0000000..d373a8e --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohomruc.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohrahemos.xml b/app/SabrehavenServer/data/monster/pharaohrahemos.xml new file mode 100644 index 0000000..572e3f3 --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohrahemos.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohthalas.xml b/app/SabrehavenServer/data/monster/pharaohthalas.xml new file mode 100644 index 0000000..2a26acb --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohthalas.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pharaohvashresamun.xml b/app/SabrehavenServer/data/monster/pharaohvashresamun.xml new file mode 100644 index 0000000..ce66bbb --- /dev/null +++ b/app/SabrehavenServer/data/monster/pharaohvashresamun.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/pig.xml b/app/SabrehavenServer/data/monster/pig.xml new file mode 100644 index 0000000..81da4aa --- /dev/null +++ b/app/SabrehavenServer/data/monster/pig.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/plaguethrower.xml b/app/SabrehavenServer/data/monster/plaguethrower.xml new file mode 100644 index 0000000..b7e67ca --- /dev/null +++ b/app/SabrehavenServer/data/monster/plaguethrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/poisonspider.xml b/app/SabrehavenServer/data/monster/poisonspider.xml new file mode 100644 index 0000000..b79b442 --- /dev/null +++ b/app/SabrehavenServer/data/monster/poisonspider.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/polarbear.xml b/app/SabrehavenServer/data/monster/polarbear.xml new file mode 100644 index 0000000..ef536b8 --- /dev/null +++ b/app/SabrehavenServer/data/monster/polarbear.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/priestess.xml b/app/SabrehavenServer/data/monster/priestess.xml new file mode 100644 index 0000000..22f10ef --- /dev/null +++ b/app/SabrehavenServer/data/monster/priestess.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/rabbit.xml b/app/SabrehavenServer/data/monster/rabbit.xml new file mode 100644 index 0000000..2b057cf --- /dev/null +++ b/app/SabrehavenServer/data/monster/rabbit.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/raids/orc.xml b/app/SabrehavenServer/data/monster/raids/orc.xml new file mode 100644 index 0000000..2af5ccc --- /dev/null +++ b/app/SabrehavenServer/data/monster/raids/orc.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/raids/orcwarlord.xml b/app/SabrehavenServer/data/monster/raids/orcwarlord.xml new file mode 100644 index 0000000..3d38cef --- /dev/null +++ b/app/SabrehavenServer/data/monster/raids/orcwarlord.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/rat.xml b/app/SabrehavenServer/data/monster/rat.xml new file mode 100644 index 0000000..dcbe97a --- /dev/null +++ b/app/SabrehavenServer/data/monster/rat.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/rotworm.xml b/app/SabrehavenServer/data/monster/rotworm.xml new file mode 100644 index 0000000..a6b04ab --- /dev/null +++ b/app/SabrehavenServer/data/monster/rotworm.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/scarab.xml b/app/SabrehavenServer/data/monster/scarab.xml new file mode 100644 index 0000000..46fb18a --- /dev/null +++ b/app/SabrehavenServer/data/monster/scarab.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/scorpion.xml b/app/SabrehavenServer/data/monster/scorpion.xml new file mode 100644 index 0000000..ee60d82 --- /dev/null +++ b/app/SabrehavenServer/data/monster/scorpion.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/serpentspawn.xml b/app/SabrehavenServer/data/monster/serpentspawn.xml new file mode 100644 index 0000000..3bb3581 --- /dev/null +++ b/app/SabrehavenServer/data/monster/serpentspawn.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/sheep.xml b/app/SabrehavenServer/data/monster/sheep.xml new file mode 100644 index 0000000..9330ce2 --- /dev/null +++ b/app/SabrehavenServer/data/monster/sheep.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/shredderthrower.xml b/app/SabrehavenServer/data/monster/shredderthrower.xml new file mode 100644 index 0000000..4e0cd8a --- /dev/null +++ b/app/SabrehavenServer/data/monster/shredderthrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/sibang.xml b/app/SabrehavenServer/data/monster/sibang.xml new file mode 100644 index 0000000..237986b --- /dev/null +++ b/app/SabrehavenServer/data/monster/sibang.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/skeleton.xml b/app/SabrehavenServer/data/monster/skeleton.xml new file mode 100644 index 0000000..6906195 --- /dev/null +++ b/app/SabrehavenServer/data/monster/skeleton.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/skunk.xml b/app/SabrehavenServer/data/monster/skunk.xml new file mode 100644 index 0000000..59286cb --- /dev/null +++ b/app/SabrehavenServer/data/monster/skunk.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/slime.xml b/app/SabrehavenServer/data/monster/slime.xml new file mode 100644 index 0000000..a263bb4 --- /dev/null +++ b/app/SabrehavenServer/data/monster/slime.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/slime2.xml b/app/SabrehavenServer/data/monster/slime2.xml new file mode 100644 index 0000000..0686017 --- /dev/null +++ b/app/SabrehavenServer/data/monster/slime2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/smuggler.xml b/app/SabrehavenServer/data/monster/smuggler.xml new file mode 100644 index 0000000..e50768b --- /dev/null +++ b/app/SabrehavenServer/data/monster/smuggler.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/snake.xml b/app/SabrehavenServer/data/monster/snake.xml new file mode 100644 index 0000000..3463066 --- /dev/null +++ b/app/SabrehavenServer/data/monster/snake.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/spider.xml b/app/SabrehavenServer/data/monster/spider.xml new file mode 100644 index 0000000..d1cdd17 --- /dev/null +++ b/app/SabrehavenServer/data/monster/spider.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/spitnettle.xml b/app/SabrehavenServer/data/monster/spitnettle.xml new file mode 100644 index 0000000..c6fb9c0 --- /dev/null +++ b/app/SabrehavenServer/data/monster/spitnettle.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/stalker.xml b/app/SabrehavenServer/data/monster/stalker.xml new file mode 100644 index 0000000..ed5fd8a --- /dev/null +++ b/app/SabrehavenServer/data/monster/stalker.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/stonegolem.xml b/app/SabrehavenServer/data/monster/stonegolem.xml new file mode 100644 index 0000000..415acaa --- /dev/null +++ b/app/SabrehavenServer/data/monster/stonegolem.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/swamptroll.xml b/app/SabrehavenServer/data/monster/swamptroll.xml new file mode 100644 index 0000000..fb19a94 --- /dev/null +++ b/app/SabrehavenServer/data/monster/swamptroll.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/tarantula.xml b/app/SabrehavenServer/data/monster/tarantula.xml new file mode 100644 index 0000000..f00446e --- /dev/null +++ b/app/SabrehavenServer/data/monster/tarantula.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/terrorbird.xml b/app/SabrehavenServer/data/monster/terrorbird.xml new file mode 100644 index 0000000..2046f48 --- /dev/null +++ b/app/SabrehavenServer/data/monster/terrorbird.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/tiger.xml b/app/SabrehavenServer/data/monster/tiger.xml new file mode 100644 index 0000000..0deca88 --- /dev/null +++ b/app/SabrehavenServer/data/monster/tiger.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/troll.xml b/app/SabrehavenServer/data/monster/troll.xml new file mode 100644 index 0000000..7ae0e1a --- /dev/null +++ b/app/SabrehavenServer/data/monster/troll.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/valkyrie.xml b/app/SabrehavenServer/data/monster/valkyrie.xml new file mode 100644 index 0000000..2904c7d --- /dev/null +++ b/app/SabrehavenServer/data/monster/valkyrie.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/vampire.xml b/app/SabrehavenServer/data/monster/vampire.xml new file mode 100644 index 0000000..99a37bc --- /dev/null +++ b/app/SabrehavenServer/data/monster/vampire.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/warlock.xml b/app/SabrehavenServer/data/monster/warlock.xml new file mode 100644 index 0000000..4e88e0d --- /dev/null +++ b/app/SabrehavenServer/data/monster/warlock.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/warwolf.xml b/app/SabrehavenServer/data/monster/warwolf.xml new file mode 100644 index 0000000..54dadab --- /dev/null +++ b/app/SabrehavenServer/data/monster/warwolf.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/wasp.xml b/app/SabrehavenServer/data/monster/wasp.xml new file mode 100644 index 0000000..7075365 --- /dev/null +++ b/app/SabrehavenServer/data/monster/wasp.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/wildwarrior.xml b/app/SabrehavenServer/data/monster/wildwarrior.xml new file mode 100644 index 0000000..cd0591b --- /dev/null +++ b/app/SabrehavenServer/data/monster/wildwarrior.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/winterwolf.xml b/app/SabrehavenServer/data/monster/winterwolf.xml new file mode 100644 index 0000000..2c6d56a --- /dev/null +++ b/app/SabrehavenServer/data/monster/winterwolf.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/witch.xml b/app/SabrehavenServer/data/monster/witch.xml new file mode 100644 index 0000000..2923747 --- /dev/null +++ b/app/SabrehavenServer/data/monster/witch.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/wolf.xml b/app/SabrehavenServer/data/monster/wolf.xml new file mode 100644 index 0000000..1e4f393 --- /dev/null +++ b/app/SabrehavenServer/data/monster/wolf.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/monster/yeti.xml b/app/SabrehavenServer/data/monster/yeti.xml new file mode 100644 index 0000000..0973034 --- /dev/null +++ b/app/SabrehavenServer/data/monster/yeti.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/lib/movements.lua b/app/SabrehavenServer/data/movements/lib/movements.lua new file mode 100644 index 0000000..b081a0f --- /dev/null +++ b/app/SabrehavenServer/data/movements/lib/movements.lua @@ -0,0 +1,2 @@ +-- Nothing -- + diff --git a/app/SabrehavenServer/data/movements/movements.xml b/app/SabrehavenServer/data/movements/movements.xml new file mode 100644 index 0000000..d3b494b --- /dev/null +++ b/app/SabrehavenServer/data/movements/movements.xml @@ -0,0 +1,839 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/movements/scripts/calassa/calassa_back.lua b/app/SabrehavenServer/data/movements/scripts/calassa/calassa_back.lua new file mode 100644 index 0000000..2ecccf1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/calassa/calassa_back.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 31914, y = 32713, z = 6}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31914, y = 32713, z = 6}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31914, y = 32713, z = 6}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31914, y = 32713, z = 6}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/calassa/calassa_enter.lua b/app/SabrehavenServer/data/movements/scripts/calassa/calassa_enter.lua new file mode 100644 index 0000000..123f7a5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/calassa/calassa_enter.lua @@ -0,0 +1,23 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local headItem = player:getSlotItem(CONST_SLOT_HEAD) + if headItem and isInArray({5460}, headItem.itemid) then + player:teleportTo(Position(31915, 32716, 12)) + player:getPosition():sendMagicEffect(2) + player:getPosition():sendMagicEffect(CONST_ME_LOSEENERGY) + else + position.y = position.y - 3 + player:teleportTo(position) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + end + return true +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31915, y = 32716, z = 12}) + Game.sendMagicEffect({x = 31915, y = 32716, z = 12}, 2) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_ferumbras_floor.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_ferumbras_floor.lua new file mode 100644 index 0000000..5875468 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_ferumbras_floor.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32121, y = 32709, z = 7}) + Game.sendMagicEffect({x = 32121, y = 32709, z = 7}, 11) + + local isInGhostMode = player:isInGhostMode() + local spectators = Game.getSpectators(player:getPosition(), false, true, 3, 3) + for i = 1, #spectators do + player:say("This entrance has been sealed by the Edron Academy.", TALKTYPE_MONSTER_SAY, isInGhostMode, spectators[i], player:getPosition()) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32121, y = 32709, z = 7}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32121, y = 32709, z = 7}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_kharos.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_kharos.lua new file mode 100644 index 0000000..81db255 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_kharos.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32101, y = 32545, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32101, y = 32545, z = 07}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32101, y = 32545, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32101, y = 32545, z = 07}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_malada.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_malada.lua new file mode 100644 index 0000000..119173b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_malada.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17512) ~= 1 then + player:setStorageValue(17512, 1) + return true + end + + doRelocate(item:getPosition(),{x = 31919, y = 32659, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32659, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31919, y = 32659, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32659, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_ramoa.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_ramoa.lua new file mode 100644 index 0000000..f23fe0c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_ramoa.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17510) ~= 1 then + player:setStorageValue(17510, 1) + return true + end + + doRelocate(item:getPosition(),{x = 32036, y = 32558, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32036, y = 32558, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32036, y = 32558, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32036, y = 32558, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_sacrifice_mushroom_talahu.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_sacrifice_mushroom_talahu.lua new file mode 100644 index 0000000..7606cd2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_sacrifice_mushroom_talahu.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 31919, y = 32594, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32594, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31919, y = 32594, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32594, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/back_talahu.lua b/app/SabrehavenServer/data/movements/scripts/goroma/back_talahu.lua new file mode 100644 index 0000000..29dc695 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/back_talahu.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17511) ~= 1 then + player:setStorageValue(17511, 1) + return true + end + + doRelocate(item:getPosition(),{x = 31948, y = 32554, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31948, y = 32554, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31948, y = 32554, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31948, y = 32554, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier.lua b/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier.lua new file mode 100644 index 0000000..6ddc959 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17509) < 5 then + position:sendMagicEffect(CONST_ME_ENERGYHIT) + + position.x = position.x + 2 + player:teleportTo(position) + return true + end + + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier_kharos.lua b/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier_kharos.lua new file mode 100644 index 0000000..22623be --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/energy_barrier_kharos.lua @@ -0,0 +1,25 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17513) ~= 2 then + player:setStorageValue(17513, 1) + doRelocate(item:getPosition(),{x = 32121, y = 32709, z = 7}) + Game.sendMagicEffect({x = 32121, y = 32709, z = 7}, 11) + + local isInGhostMode = player:isInGhostMode() + local spectators = Game.getSpectators(player:getPosition(), false, true, 3, 3) + for i = 1, #spectators do + player:say("This entrance has been sealed by the Edron Academy.", TALKTYPE_MONSTER_SAY, isInGhostMode, spectators[i], player:getPosition()) + end + return true + end + + doRelocate(item:getPosition(),{x = 32121, y = 32703, z = 7}) + Game.sendMagicEffect({x = 32121, y = 32703, z = 7}, 11) + player:setStorageValue(17513, 1) + + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/entrance_kharos.lua b/app/SabrehavenServer/data/movements/scripts/goroma/entrance_kharos.lua new file mode 100644 index 0000000..35f8156 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/entrance_kharos.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + position.y = position.y - 1 + player:teleportTo(position) + + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/final_ferumbras_floor.lua b/app/SabrehavenServer/data/movements/scripts/goroma/final_ferumbras_floor.lua new file mode 100644 index 0000000..3d8f5db --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/final_ferumbras_floor.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32121, y = 32693, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32121, y = 32693, z = 04}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32121, y = 32693, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32121, y = 32693, z = 04}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/sacrifice_mushroom_talahu.lua b/app/SabrehavenServer/data/movements/scripts/goroma/sacrifice_mushroom_talahu.lua new file mode 100644 index 0000000..9f94e4f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/sacrifice_mushroom_talahu.lua @@ -0,0 +1,31 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if Game.isItemThere({x = 31918, y = 32598, z = 10},3723) and Game.isItemThere({x = 31918, y = 32599, z = 10},3725) and Game.isItemThere({x = 31920, y = 32598, z = 10},3732) and Game.isItemThere({x = 31920, y = 32599, z = 10},3728) then + Game.sendMagicEffect({x = 31918, y = 32598, z = 10}, 15) + Game.removeItemOnMap({x = 31918, y = 32598, z = 10}, 3723) + Game.sendMagicEffect({x = 31918, y = 32599, z = 10}, 15) + Game.removeItemOnMap({x = 31918, y = 32599, z = 10}, 3725) + Game.sendMagicEffect({x = 31920, y = 32598, z = 10}, 15) + Game.removeItemOnMap({x = 31920, y = 32598, z = 10}, 3732) + Game.sendMagicEffect({x = 31920, y = 32599, z = 10}, 15) + Game.removeItemOnMap({x = 31920, y = 32599, z = 10}, 3728) + + doRelocate(item:getPosition(),{x = 31914, y = 32605, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31914, y = 32605, z = 10}, 11) + else + doRelocate(item:getPosition(),{x = 31919, y = 32597, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32597, z = 10}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31915, y = 32606, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31915, y = 32606, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/second_ferumbras_floor.lua b/app/SabrehavenServer/data/movements/scripts/goroma/second_ferumbras_floor.lua new file mode 100644 index 0000000..e0085c3 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/second_ferumbras_floor.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32124, y = 32694, z = 05}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32124, y = 32694, z = 05}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32124, y = 32694, z = 05}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32124, y = 32694, z = 05}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/to_malada.lua b/app/SabrehavenServer/data/movements/scripts/goroma/to_malada.lua new file mode 100644 index 0000000..ea1ca4d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/to_malada.lua @@ -0,0 +1,22 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17512) ~= 1 then + Game.sendMagicEffect({x = 31926, y = 32656, z = 08}, 11) + player:teleportTo({x = 31926, y = 32656, z = 08}) + return true + end + + doRelocate(item:getPosition(),{x = 32021, y = 32680, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32021, y = 32680, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32021, y = 32680, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32021, y = 32680, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/to_ramoa.lua b/app/SabrehavenServer/data/movements/scripts/goroma/to_ramoa.lua new file mode 100644 index 0000000..b11c46e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/to_ramoa.lua @@ -0,0 +1,22 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17510) ~= 1 then + Game.sendMagicEffect({x = 32043, y = 32559, z = 08}, 11) + player:teleportTo({x = 32043, y = 32559, z = 08}) + return true + end + + doRelocate(item:getPosition(),{x = 31948, y = 32554, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31948, y = 32554, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31948, y = 32554, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31948, y = 32554, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/goroma/to_talahu.lua b/app/SabrehavenServer/data/movements/scripts/goroma/to_talahu.lua new file mode 100644 index 0000000..8c1a25f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/goroma/to_talahu.lua @@ -0,0 +1,22 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17511) ~= 1 then + Game.sendMagicEffect({x = 31951, y = 32555, z = 08}, 11) + player:teleportTo({x = 31951, y = 32555, z = 08}) + return true + end + + doRelocate(item:getPosition(),{x = 31919, y = 32659, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32659, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 31919, y = 32659, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 31919, y = 32659, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/laguna_islands/erayo_hut_stairs.lua b/app/SabrehavenServer/data/movements/scripts/laguna_islands/erayo_hut_stairs.lua new file mode 100644 index 0000000..1c7a7d1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/laguna_islands/erayo_hut_stairs.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:getCondition(CONDITION_INVISIBLE, CONDITIONID_COMBAT) or creature:getCondition(CONDITION_INVISIBLE, CONDITIONID_RING) then + if item:getId() == 485 then + doRelocate(item:getPosition(),{x = 32517, y = 32910, z = 8}) + elseif item:getId() == 1947 then + doRelocate(item:getPosition(),{x = 32517, y = 32908, z = 7}) + end + + return true + end + + doRelocate(item:getPosition(),{x = 32519, y = 32914, z = 7}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32519, y = 32914, z = 7}, 11) + creature:say("Why are you sneaking around in my house? Think I don't see you?", TALKTYPE_MONSTER_SAY, false, 0, creature:getPosition()) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/liberty_bay/back_cult_piano.lua b/app/SabrehavenServer/data/movements/scripts/liberty_bay/back_cult_piano.lua new file mode 100644 index 0000000..3fc72c2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/liberty_bay/back_cult_piano.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32410, y = 32794, z = 9}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32410, y = 32794, z = 9}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32410, y = 32794, z = 9}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32410, y = 32794, z = 9}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/liberty_bay/citizenship.lua b/app/SabrehavenServer/data/movements/scripts/liberty_bay/citizenship.lua new file mode 100644 index 0000000..8b3be1d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/liberty_bay/citizenship.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32317, y = 32826, z = 07}) + creature:getPlayer():setTown(Town("Liberty Bay")) + Game.sendMagicEffect({x = 32317, y = 32826, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32313, y = 32819, z = 07}) + Game.sendMagicEffect({x = 32313, y = 32819, z = 07}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/candelabrum.lua b/app/SabrehavenServer/data/movements/scripts/misc/candelabrum.lua new file mode 100644 index 0000000..dc7ba47 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/candelabrum.lua @@ -0,0 +1,7 @@ +function onRemoveItem(item, tileitem, position) + if item:getPosition():getDistance(position) > 0 then + item:transform(2912, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/damage.lua b/app/SabrehavenServer/data/movements/scripts/misc/damage.lua new file mode 100644 index 0000000..fa0c4d1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/damage.lua @@ -0,0 +1,26 @@ +function onStepIn(creature, item, position, fromPosition) + local tile = Tile(position) + if tile:hasFlag(TILESTATE_PROTECTIONZONE) then + return + end + + if item:getId() == 2145 then + item:transform(2146, 1) + item:decay() + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -60, -60) + elseif item:getId() == 2146 or item:getId() == 2148 then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -60, -60) + elseif item:getId() == 3482 then + if not creature:isPlayer() then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -30, -30) + else + position:sendMagicEffect(CONST_ME_POFF) + end + item:transform(3481, 1) + item:decay() + elseif item:getId() == 3944 then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -30, -30) + item:transform(3945, 1) + item:decay() + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/depot_switch.lua b/app/SabrehavenServer/data/movements/scripts/misc/depot_switch.lua new file mode 100644 index 0000000..2966946 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/depot_switch.lua @@ -0,0 +1,36 @@ +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local player = Player(creature) + local lookPosition = player:getPosition() + lookPosition:getNextPosition(player:getDirection()) + local depotItem = Tile(lookPosition):getItemByType(ITEM_TYPE_DEPOT) + if depotItem ~= nil then + local depotItems = player:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount() + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or ".")) + end + + if item:getId() == 431 then + item:transform(430) + elseif item:getId() == 419 then + item:transform(420) + elseif item:getId() == 452 then + item:transform(453) + elseif item:getId() == 563 then + item:transform(564) + end +end + +function onStepOut(creature, item, position, fromPosition) + if item:getId() == 430 then + item:transform(431) + elseif item:getId() == 420 then + item:transform(419) + elseif item:getId() == 453 then + item:transform(452) + elseif item:getId() == 564 then + item:transform(563) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/doors.lua b/app/SabrehavenServer/data/movements/scripts/misc/doors.lua new file mode 100644 index 0000000..34b65da --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/doors.lua @@ -0,0 +1,61 @@ +local verticalDoors = { + [1643] = 1642, + [1647] = 1646, + [1661] = 1660, + [1665] = 1664, + [1675] = 1674, + [1679] = 1678, + [1697] = 1696, + [1699] = 1698, + [5112] = 5111, + [5114] = 5113, + [5130] = 5129, + [5132] = 5131, + [5288] = 5287, + [5292] = 5291, + [5748] = 5749, + [6202] = 6201, + [6206] = 6205, + [6259] = 6258, + [6263] = 6262, +} + +local horizontalDoors = { + [1645] = 1644, + [1649] = 1648, + [1663] = 1662, + [1667] = 1666, + [1677] = 1676, + [1681] = 1680, + [1688] = 1687, + [1690] = 1689, + [5103] = 5102, + [5105] = 5104, + [5121] = 5120, + [5123] = 5122, + [5290] = 5289, + [5294] = 5293, + [5746] = 5745, + [6204] = 6203, + [6208] = 6207, + [6261] = 6260, + [6265] = 6264, +} + +function onStepOut(creature, item, fromPosition, toPosition) + local door = verticalDoors[item:getId()] + if door then + doRelocate(item:getPosition(), item:getPosition():moveRel(1, 0, 0)) + item:transform(door) + item:decay() + return true + end + + door = horizontalDoors[item:getId()] + if door then + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 1, 0)) + item:transform(door) + item:decay() + return true + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/drowning.lua b/app/SabrehavenServer/data/movements/scripts/misc/drowning.lua new file mode 100644 index 0000000..e346815 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/drowning.lua @@ -0,0 +1,31 @@ +local condition = Condition(CONDITION_DROWN) +condition:setTiming(5) + + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if player == nil then + return true + end + + local helmet = player:getSlotItem(CONST_SLOT_HEAD) + if helmet ~= nil and helmet:getId() == 5460 then + return true + end + + if math.random(1, 10) == 1 then + position:sendMagicEffect(CONST_ME_BUBBLES) + end + player:addCondition(condition) + return true +end + +function onStepOut(creature, item, position, fromPosition) + local player = creature:getPlayer() + if player == nil then + return true + end + + player:removeCondition(CONDITION_DROWN) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/dustbin.lua b/app/SabrehavenServer/data/movements/scripts/misc/dustbin.lua new file mode 100644 index 0000000..9527c1c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/dustbin.lua @@ -0,0 +1,4 @@ +function onAddItem(item, tileitem, position) + item:remove() + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/floorchange.lua b/app/SabrehavenServer/data/movements/scripts/misc/floorchange.lua new file mode 100644 index 0000000..236712b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/floorchange.lua @@ -0,0 +1,114 @@ +local list = { + [293] = {x = 0, y = 0, z = 1}, + [294] = {x = 0, y = 0, z = 1}, + [369] = {x = 0, y = 0, z = 1}, + [370] = {x = 0, y = 0, z = 1}, + [385] = {x = 0, y = 0, z = 1}, + [394] = {x = 0, y = 0, z = 1}, + [411] = {x = 0, y = 0, z = 1}, + [412] = {x = 0, y = 0, z = 1}, + [413] = {x = 0, y = 1, z = 1}, + [414] = {x = 0, y = 1, z = 1}, + [428] = {x = 0, y = 1, z = 1}, + [4823] = {x = 0, y = 1, z = 1}, + [4824] = {x = 0, y = 1, z = 1}, + [4825] = {x = 0, y = 1, z = 1}, + [4826] = {x = 0, y = 1, z = 1}, + [432] = {x = 0, y = 0, z = 1}, + [433] = {x = 0, y = 0, z = 1}, + [434] = {x = 0, y = 1, z = 1}, + [437] = {x = 0, y = 1, z = 1}, + [438] = {x = 0, y = 1, z = 1}, + [451] = {x = 0, y = 1, z = 1}, + [465] = {x = 0, y = -1, z = 1}, + [466] = {x = -1, y = 0, z = 1}, + [467] = {x = 1, y = 0, z = 1}, + [471] = {x = -1, y = -1, z = 1}, + [472] = {x = 1, y = -1, z = 1}, + [473] = {x = -1, y = 1, z = 1}, + [474] = {x = 1, y = 1, z = 1}, + [475] = {x = 0, y = 0, z = 1}, + [476] = {x = 0, y = 0, z = 1}, + [482] = {x = 0, y = 0, z = 1}, + [5081] = {x = 0, y = 0, z = 1}, + [483] = {x = 0, y = 0, z = 1}, + [484] = {x = 0, y = 1, z = 1}, + [485] = {x = 0, y = 1, z = 1}, + [566] = {x = 0, y = 1, z = 1}, + [567] = {x = 1, y = 0, z = 1}, + [594] = {x = 0, y = 0, z = 1}, + [595] = {x = 0, y = 0, z = 1}, + [600] = {x = -1, y = 0, z = 1}, + [601] = {x = 1, y = 0, z = 1}, + [604] = {x = -1, y = 0, z = 1}, + [605] = {x = 1, y = 0, z = 1}, + [607] = {x = 0, y = 0, z = 1}, + [609] = {x = 0, y = 0, z = 1}, + [610] = {x = 0, y = 0, z = 1}, + [615] = {x = 0, y = 0, z = 1}, + [1066] = {x = 0, y = 0, z = 1}, + [1067] = {x = 0, y = 0, z = 1}, + [1080] = {x = 0, y = 0, z = 1}, + [1156] = {x = 0, y = 1, z = 1}, + [1947] = {x = 0, y = -1, z = -1}, + [1950] = {x = 1, y = 0, z = -1}, + [1952] = {x = -1, y = 0, z = -1}, + [1954] = {x = 0, y = 1, z = -1}, + [1956] = {x = 0, y = -1, z = -1}, + [1958] = {x = 0, y = -1, z = -1}, + [1960] = {x = 1, y = 0, z = -1}, + [1962] = {x = -1, y = 0, z = -1}, + [1964] = {x = 0, y = 1, z = -1}, + [1966] = {x = 0, y = -1, z = -1}, + [1969] = {x = 1, y = 0, z = -1}, + [1971] = {x = -1, y = 0, z = -1}, + [1973] = {x = 0, y = 1, z = -1}, + [1975] = {x = 0, y = -1, z = -1}, + [1977] = {x = 0, y = -1, z = -1}, + [1978] = {x = -1, y = 0, z = -1}, + [2192] = {x = -1, y = -1, z = -1}, + [2194] = {x = 1, y = -1, z = -1}, + [2196] = {x = 1, y = 1, z = -1}, + [2198] = {x = -1, y = 1, z = -1}, + [5257] = {x = -1, y = 0, z = -1}, + [5258] = {x = 0, y = -1, z = -1}, + [5259] = {x = -1, y = 0, z = -1}, + [5544] = {x = 0, y = 0, z = 1}, + [5691] = {x = 1, y = 0, z = 1}, + [5731] = {x = 0, y = 0, z = 1}, + [5763] = {x = 0, y = 0, z = 1}, + [6172] = {x = 0, y = 0, z = 1}, + [6173] = {x = 0, y = 0, z = 1}, +} + +function onStepIn(creature, item, position, fromPosition) + local entry = list[item:getId()] + local relPos = item:getPosition():moveRel(entry.x, entry.y, entry.z) + + local tile = Tile(relPos) + if tile == nil or tile:getGround() == nil then + return false + end + + Tile(item:getPosition()):relocateTo(relPos) + if item:getId() == 293 then + item:transform(294) + item:decay() + elseif item:getId() == 475 then + item:transform(476) + item:decay() + elseif item:getId() == 1066 then + item:transform(1067) + item:decay() + end + return true +end + +function onAddItem(item, tileitem, position) + if tileitem:getId() ~= 293 and tileitem:getId() ~= 475 and tileitem:getId() ~= 476 and tileitem:getId() ~= 1066 then + local entry = list[tileitem:getId()] + local relPos = tileitem:getPosition():moveRel(entry.x, entry.y, entry.z) + item:moveTo(relPos) + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/lava.lua b/app/SabrehavenServer/data/movements/scripts/misc/lava.lua new file mode 100644 index 0000000..d7854df --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/lava.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(16) + item:remove() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/lit_candlestick.lua b/app/SabrehavenServer/data/movements/scripts/misc/lit_candlestick.lua new file mode 100644 index 0000000..c13663c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/lit_candlestick.lua @@ -0,0 +1,8 @@ +function onAddItem(item, tileitem, position) + if item:getId() == 2918 or item:getId() == 2917 then + tileitem:transform(6279, 1) + item:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + item:remove() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/open_trap.lua b/app/SabrehavenServer/data/movements/scripts/misc/open_trap.lua new file mode 100644 index 0000000..1f0e3f2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/open_trap.lua @@ -0,0 +1,8 @@ +function onRemoveItem(item, tileitem, position) + if item:getPosition():getDistance(position) > 0 then + item:transform(3481, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/raid_sacrifice.lua b/app/SabrehavenServer/data/movements/scripts/misc/raid_sacrifice.lua new file mode 100644 index 0000000..479f9d0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/raid_sacrifice.lua @@ -0,0 +1,38 @@ +local config = { + [17578] = { name = "edronorshabaal", message = "CHAMEK ATH UTHUL ARAK!", count=100 }, + [17579] = { name = "ferumbras", message = "Hahahaha! I have never been killed by the Avar Tar!", count=100 }, + [17580] = { name = "morgaroth", message = "THE TRIANGLE OF TERROR WILL RISE!", count=100 }, + [17581] = { name = "libertybaypirates", message = "Plundeeeeer!", count=100 } +} + +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33268, y = 31835, z = 9}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33268, y = 31835, z = 9}, 11) + item:getPosition():sendMonsterSay("Mystic flame ward you off.") +end + +function onAddItem(item, tileitem, position) + local movementId = tileitem:getMovementId() + local raid = config[movementId] + + if (item:getId() ~= 5776) then + doRelocate(item:getPosition(),{x = 33268, y = 31835, z = 9}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33268, y = 31835, z = 9}, 11) + tileitem:getPosition():sendMonsterSay("Mystic flame spit out the sacrifice.") + return true + end + + local currentCount = getGlobalStorageValue(movementId) + item:getCount() + setGlobalStorageValue(movementId, currentCount) + if (currentCount >= raid.count) then + item:getPosition():sendMonsterSay(raid.message) + Game.startRaid(raid.name) + tileitem:remove() + setGlobalStorageValue(movementId, 0) + end + + item:getPosition():sendMagicEffect(CONST_ME_FIREAREA) + item:remove() +end diff --git a/app/SabrehavenServer/data/movements/scripts/misc/sandstone_wall.lua b/app/SabrehavenServer/data/movements/scripts/misc/sandstone_wall.lua new file mode 100644 index 0000000..80a97e7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/sandstone_wall.lua @@ -0,0 +1,39 @@ +function onStepIn(creature, item, fromPosition, toPosition) + if item:getId() == 478 then + item:transform(479, 1) + item:decay() + elseif item:getId() == 480 then + item:transform(481, 1) + item:decay() + end +end + +function onStepOut(creature, item, fromPosition, toPosition) + if item:getId() == 479 then + item:transform(478, 1) + item:decay() + elseif item:getId() == 481 then + item:transform(480, 1) + item:decay() + end +end + +function onAddItem(item, tileitem, position) + if tileitem:getId() == 478 then + tileitem:transform(479, 1) + tileitem:decay() + elseif tileitem:getId() == 480 then + tileitem:transform(481, 1) + tileitem:decay() + end +end + +function onRemoveItem(item, tileitem, position) + if tileitem:getId() == 479 then + tileitem:transform(478, 1) + tileitem:decay() + elseif tileitem:getId() == 481 then + tileitem:transform(480, 1) + tileitem:decay() + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/serpentine_tower_hole.lua b/app/SabrehavenServer/data/movements/scripts/misc/serpentine_tower_hole.lua new file mode 100644 index 0000000..e6ef0ec --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/serpentine_tower_hole.lua @@ -0,0 +1,14 @@ +function onAddItem(item, tileitem, position) + if item:getId() == 5898 then + tileitem:getPosition():sendMonsterSay("MORE! MORE!") + item:remove(-1) + return true + elseif item:getId() == 5776 then + tileitem:getPosition():sendMonsterSay("OHHH! WILL USE IT LATER!") + item:remove(-1) + return true + end + + tileitem:getPosition():sendMonsterSay("I WANT EYES!") + item:remove(-1) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/swamp.lua b/app/SabrehavenServer/data/movements/scripts/misc/swamp.lua new file mode 100644 index 0000000..f8ae678 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/swamp.lua @@ -0,0 +1,8 @@ +function onAddItem(item, tileitem, position) + if (item:getType():isMovable() and Tile(position):getThingCount() == 2) or + (tileitem:getId() >= 4874 and tileitem:getId() <= 4880) then + item:getPosition():sendMagicEffect(9) + item:remove() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/tar.lua b/app/SabrehavenServer/data/movements/scripts/misc/tar.lua new file mode 100644 index 0000000..5f8c0d9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/tar.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(3) + item:remove() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/turtles.lua b/app/SabrehavenServer/data/movements/scripts/misc/turtles.lua new file mode 100644 index 0000000..8bd9d3d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/turtles.lua @@ -0,0 +1,34 @@ +local config = { + [51191] = Position(32359, 32901, 7), + [51192] = Position(32340, 32538, 7), + [51193] = Position(32472, 32869, 7), + [51194] = Position(32415, 32916, 7), + [51195] = Position(32490, 32979, 7), + [51196] = Position(32440, 32971, 7), + [51197] = Position(32523, 32923, 7), + [51198] = Position(32527, 32951, 7) +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local targetPosition = config[item:getMovementId()] + if not targetPosition then + return true + end + + if item:getMovementId() == 51191 and player:getStorageValue(17502) < 13 then + player:teleportTo(Position(32340, 32540, 7)) + position:sendMagicEffect(CONST_ME_TELEPORT) + Position(32340, 32540, 7):sendMagicEffect(CONST_ME_TELEPORT) + return true + end + + player:teleportTo(targetPosition) + position:sendMagicEffect(CONST_ME_TELEPORT) + targetPosition:sendMagicEffect(CONST_ME_TELEPORT) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/misc/water.lua b/app/SabrehavenServer/data/movements/scripts/misc/water.lua new file mode 100644 index 0000000..65420e1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/misc/water.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(2) + item:remove() + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/1.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/1.lua new file mode 100644 index 0000000..2966946 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/1.lua @@ -0,0 +1,36 @@ +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local player = Player(creature) + local lookPosition = player:getPosition() + lookPosition:getNextPosition(player:getDirection()) + local depotItem = Tile(lookPosition):getItemByType(ITEM_TYPE_DEPOT) + if depotItem ~= nil then + local depotItems = player:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount() + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or ".")) + end + + if item:getId() == 431 then + item:transform(430) + elseif item:getId() == 419 then + item:transform(420) + elseif item:getId() == 452 then + item:transform(453) + elseif item:getId() == 563 then + item:transform(564) + end +end + +function onStepOut(creature, item, position, fromPosition) + if item:getId() == 430 then + item:transform(431) + elseif item:getId() == 420 then + item:transform(419) + elseif item:getId() == 453 then + item:transform(452) + elseif item:getId() == 564 then + item:transform(563) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/10.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/10.lua new file mode 100644 index 0000000..f9f8ada --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/10.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32266, y = 31916, z = 12}, 394) then + Game.transformItemOnMap({x = 32266, y = 31916, z = 12}, 394, 372) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/100.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/100.lua new file mode 100644 index 0000000..74d5d6a --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/100.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3239) >= 1 and creature:getPlayer():getStorageValue(267) == 0 then + doRelocate(item:getPosition(),{x = 33178, y = 33016, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33178, y = 33016, z = 14}, 11) + creature:getPlayer():removeItem(3239, 1) + else + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/101.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/101.lua new file mode 100644 index 0000000..264dd74 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/101.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/102.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/102.lua new file mode 100644 index 0000000..a07d6dc --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/102.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33179, y = 32880, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32880, z = 11}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33179, y = 32880, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32880, z = 11}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/103.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/103.lua new file mode 100644 index 0000000..9657677 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/103.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3240) >= 1 and creature:getPlayer():getStorageValue(261) == 0 then + doRelocate(item:getPosition(),{x = 33174, y = 32937, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33174, y = 32937, z = 15}, 11) + creature:getPlayer():removeItem(3240, 1) + else + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/104.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/104.lua new file mode 100644 index 0000000..3888fb5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/104.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33158, y = 32771, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33158, y = 32771, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33158, y = 32771, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33158, y = 32771, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/105.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/105.lua new file mode 100644 index 0000000..b06d4d5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/105.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33190, y = 32947, z = 15}) + Game.sendMagicEffect({x = 33183, y = 32757, z = 15}, 11) + Game.sendMagicEffect({x = 33191, y = 32947, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33190, y = 32947, z = 15}) + Game.sendMagicEffect({x = 33183, y = 32757, z = 15}, 11) + Game.sendMagicEffect({x = 33191, y = 32947, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/106.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/106.lua new file mode 100644 index 0000000..e529b2c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/106.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/107.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/107.lua new file mode 100644 index 0000000..88dba55 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/107.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/108.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/108.lua new file mode 100644 index 0000000..71f5b0d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/108.lua @@ -0,0 +1,23 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33176, y = 32880, z = 11},2570) and Game.isItemThere ({x = 33175, y = 32884, z = 11},2570) and Game.isItemThere ({x = 33176, y = 32889, z = 11},2570) and Game.isItemThere ({x = 33182, y = 32880, z = 11},2570) and Game.isItemThere ({x = 33183, y = 32884, z = 11},2570) and Game.isItemThere ({x = 33181, y = 32889, z = 11}, 2570) then + doRelocate(item:getPosition(),{x = 33198, y = 32885, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33198, y = 32885, z = 11}, 11) + Game.transformItemOnMap({x = 33176, y = 32880, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33175, y = 32884, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33176, y = 32889, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33182, y = 32880, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33183, y = 32884, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33181, y = 32889, z = 11}, 2570, 2569) + else + doRelocate(item:getPosition(),{x = 33179, y = 32889, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32889, z = 11}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33179, y = 32889, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32889, z = 11}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/109.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/109.lua new file mode 100644 index 0000000..ba7d1bb --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/109.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33128, y = 32656, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33128, y = 32656, z = 15}, 11) + creature:getPlayer():setStorageValue(259,0) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33128, y = 32656, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33128, y = 32656, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/11.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/11.lua new file mode 100644 index 0000000..b5ec0be --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/11.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32266, y = 31892, z = 12}, 394) then + Game.transformItemOnMap({x = 32266, y = 31892, z = 12}, 394, 372) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/110.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/110.lua new file mode 100644 index 0000000..4c6d59c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/110.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/111.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/111.lua new file mode 100644 index 0000000..601ca22 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/111.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3237) >= 1 and creature:getPlayer():getStorageValue(263) == 0 then + doRelocate(item:getPosition(),{x = 33182, y = 32715, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33182, y = 32715, z = 14}, 11) + creature:getPlayer():removeItem(3237, 1) + else + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/112.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/112.lua new file mode 100644 index 0000000..4f07d47 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/112.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33149, y = 32870, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33149, y = 32870, z = 11}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33149, y = 32870, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33149, y = 32870, z = 11}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/113.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/113.lua new file mode 100644 index 0000000..c2d2a40 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/113.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32839, z = 09}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32839, z = 09}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32839, z = 09}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32839, z = 09}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/114.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/114.lua new file mode 100644 index 0000000..e4aae80 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/114.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33147, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33147, y = 32864, z = 07}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33147, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33147, y = 32864, z = 07}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/115.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/115.lua new file mode 100644 index 0000000..634d341 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/115.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/116.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/116.lua new file mode 100644 index 0000000..33bc981 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/116.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33145, y = 32862, z = 07}, 3465) and creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33151, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33151, y = 32864, z = 07}, 15) + else + doRelocate(item:getPosition(),{x = 33145, y = 32863, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33145, y = 32863, z = 07}, 15) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33145, y = 32863, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33145, y = 32863, z = 07}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/117.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/117.lua new file mode 100644 index 0000000..264dd74 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/117.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/118.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/118.lua new file mode 100644 index 0000000..09f8436 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/118.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33125, y = 32760, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33125, y = 32760, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33125, y = 32760, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33125, y = 32760, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/119.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/119.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/119.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/12.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/12.lua new file mode 100644 index 0000000..221ed60 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/12.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32266, y = 31898, z = 12},{x = 32266, y = 31886, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/120.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/120.lua new file mode 100644 index 0000000..e2e9cad --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/120.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + Game.sendMagicEffect({x = 33124, y = 32759, z = 14}, 11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + Game.sendMagicEffect({x = 33124, y = 32759, z = 14}, 11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/121.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/121.lua new file mode 100644 index 0000000..89302bb --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/121.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33085, y = 32781, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33085, y = 32781, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33085, y = 32781, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33085, y = 32781, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/122.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/122.lua new file mode 100644 index 0000000..fc4932c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/122.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33193, y = 32664, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33193, y = 32664, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33193, y = 32664, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33193, y = 32664, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/123.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/123.lua new file mode 100644 index 0000000..ce78d67 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/123.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3236) >= 1 and creature:getPlayer():getStorageValue(264) == 0 then + doRelocate(item:getPosition(),{x = 33146, y = 32666, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33146, y = 32666, z = 15}, 11) + creature:getPlayer():removeItem(3236, 1) + else + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/124.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/124.lua new file mode 100644 index 0000000..805f0c1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/124.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33204, y = 32956, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33204, y = 32956, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33204, y = 32956, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33204, y = 32956, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/125.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/125.lua new file mode 100644 index 0000000..36bba3a --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/125.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3241) >= 1 and creature:getPlayer():getStorageValue(265) == 0 then + doRelocate(item:getPosition(),{x = 33126, y = 32592, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33126, y = 32591, z = 15}, 11) + creature:getPlayer():removeItem(3241, 1) + else + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/126.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/126.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/126.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/127.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/127.lua new file mode 100644 index 0000000..8b2283c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/127.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33028, y = 32588, z = 13},2570) and Game.isItemThere ({x = 33006, y = 32563, z = 13},2570) and Game.isItemThere ({x = 33027, y = 32530, z = 13},2570) and Game.isItemThere ({x = 33036, y = 32507, z = 13},2570) and Game.isItemThere ({x = 33055, y = 32487, z = 13},2570) and Game.isItemThere ({x = 33077, y = 32507, z = 13},2570) and Game.isItemThere ({x = 33089, y = 32514, z = 13},2570) and Game.isItemThere ({x = 33104, y = 32514, z = 13},2570) and Game.isItemThere ({x = 33130, y = 32489, z = 13},2570) and Game.isItemThere ({x = 33147, y = 32524, z = 13},2570) and Game.isItemThere ({x = 33123, y = 32599, z = 13}, 2570) then + doRelocate(item:getPosition(),{x = 33083, y = 32571, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32571, z = 14}, 11) + else + doRelocate(item:getPosition(),{x = 33083, y = 32568, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32568, z = 13}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33083, y = 32568, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32568, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/128.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/128.lua new file mode 100644 index 0000000..2e6cf28 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/128.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33080, y = 32569, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33080, y = 32569, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33080, y = 32569, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33080, y = 32569, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/129.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/129.lua new file mode 100644 index 0000000..3d3ab1d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/129.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3235) >= 1 and creature:getPlayer():getStorageValue(266) == 0 then + doRelocate(item:getPosition(),{x = 33051, y = 32777, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33051, y = 32777, z = 14}, 11) + creature:getPlayer():removeItem(3235, 1) + else + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/13.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/13.lua new file mode 100644 index 0000000..84929f6 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/13.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32266, y = 31899, z = 12},{x = 32266, y = 31911, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/130.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/130.lua new file mode 100644 index 0000000..6bcaeb6 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/130.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:getPlayer():getStorageValue(260) == 1 then + doRelocate(item:getPosition(),{x = 33095, y = 32590, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33095, y = 32590, z = 15}, 11) + creature:getPlayer():setStorageValue(260, 0) + else + doRelocate(item:getPosition(),{x = 33073, y = 32604, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33073, y = 32604, z = 15}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33073, y = 32604, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33073, y = 32604, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/131.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/131.lua new file mode 100644 index 0000000..2e8744d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/131.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33083, y = 32610, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32610, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33083, y = 32610, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32610, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/132.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/132.lua new file mode 100644 index 0000000..13d22b4 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/132.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/133.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/133.lua new file mode 100644 index 0000000..7dde55c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/133.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32744, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32744, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33280, y = 32744, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32744, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/134.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/134.lua new file mode 100644 index 0000000..dff7392 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/134.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(276,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/135.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/135.lua new file mode 100644 index 0000000..6bb7aa8 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/135.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(275,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/136.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/136.lua new file mode 100644 index 0000000..e3998c1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/136.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32577, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32577, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32577, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32577, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/137.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/137.lua new file mode 100644 index 0000000..44b1db4 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/137.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(272,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/138.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/138.lua new file mode 100644 index 0000000..e4548cd --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/138.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(277,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/139.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/139.lua new file mode 100644 index 0000000..cd7efe7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/139.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(274,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/14.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/14.lua new file mode 100644 index 0000000..e3c26e0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/14.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32265, y = 31899, z = 12},{x = 32265, y = 31911, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/140.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/140.lua new file mode 100644 index 0000000..676af8c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/140.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(271,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/141.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/141.lua new file mode 100644 index 0000000..4c6d59c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/141.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/142.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/142.lua new file mode 100644 index 0000000..04f2dba --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/142.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(273,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/143.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/143.lua new file mode 100644 index 0000000..73cb899 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/143.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33235, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33235, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33235, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33235, y = 32705, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/144.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/144.lua new file mode 100644 index 0000000..226cb69 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/144.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33192, y = 32846, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33192, y = 32846, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33192, y = 32846, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33192, y = 32846, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/145.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/145.lua new file mode 100644 index 0000000..265f081 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/145.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33195, y = 32852, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33195, y = 32852, z = 04}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33195, y = 32852, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33195, y = 32852, z = 04}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/146.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/146.lua new file mode 100644 index 0000000..507ea25 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/146.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33157, y = 32834, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33157, y = 32834, z = 07}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33157, y = 32834, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33157, y = 32834, z = 07}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/147.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/147.lua new file mode 100644 index 0000000..194003e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/147.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/148.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/148.lua new file mode 100644 index 0000000..d9d3d84 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/148.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33029, y = 32868, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33029, y = 32868, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33029, y = 32868, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33029, y = 32868, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/149.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/149.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/149.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/15.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/15.lua new file mode 100644 index 0000000..45c508f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/15.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32265, y = 31898, z = 12},{x = 32265, y = 31886, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/150.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/150.lua new file mode 100644 index 0000000..0259c21 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/150.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33212, y = 31671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33212, y = 31671, z = 13}, 11) + creature:getPlayer():setStorageValue(203,1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/151.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/151.lua new file mode 100644 index 0000000..43813c6 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/151.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33021, y = 32605, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33021, y = 32605, z = 07}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33021, y = 32605, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33021, y = 32605, z = 07}, 15) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/152.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/152.lua new file mode 100644 index 0000000..15901ef --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/152.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32840, y = 32533, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32840, y = 32533, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32840, y = 32533, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32840, y = 32533, z = 09}, 15) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/153.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/153.lua new file mode 100644 index 0000000..6e048a3 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/153.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(293) >= 17 then + doRelocate(item:getPosition(),{x = 32748, y = 32537, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32748, y = 32537, z = 10}, 21) + elseif creature:isPlayer() and creature:getPlayer():getStorageValue(293) < 17 then + doRelocate(item:getPosition(),{x = 32839, y = 32532, z = 09}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32839, y = 32532, z = 09}, 21) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/154.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/154.lua new file mode 100644 index 0000000..132b874 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/154.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32839, y = 32532, z = 09}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32839, y = 32532, z = 09}, 21) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/155.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/155.lua new file mode 100644 index 0000000..c22e1b2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/155.lua @@ -0,0 +1,19 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32876, y = 32584, z = 10},4996) and Game.isItemThere ({x = 32823, y = 32525, z = 10},4996) and Game.isItemThere ({x = 32792, y = 32527, z = 10},4996) and Game.isItemThere ({x = 32744, y = 32586, z = 10}, 4996) then + doRelocate(item:getPosition(),{x = 32884, y = 32632, z = 11}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32884, y = 32632, z = 11}, 21) + else + doRelocate(item:getPosition(),{x = 32853, y = 32543, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32853, y = 32543, z = 10}, 21) + item:getPosition():sendMonsterSay("Spectral guardians ward you off.") + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32853, y = 32543, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32853, y = 32543, z = 10}, 21) + item:getPosition():sendMonsterSay("Spectral guardians ward you off.") +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/156.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/156.lua new file mode 100644 index 0000000..968207d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/156.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32874, y = 31942, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32874, y = 31942, z = 11}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/157.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/157.lua new file mode 100644 index 0000000..987ae43 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/157.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32874, y = 31953, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32874, y = 31953, z = 12}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/158.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/158.lua new file mode 100644 index 0000000..af9c688 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/158.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32584, y = 32465, z = 09},2567) and Game.isItemThere ({x = 32583, y = 32482, z = 09},2567) and Game.isItemThere ({x = 32610, y = 32523, z = 09},2567) and Game.isItemThere ({x = 32619, y = 32523, z = 09},2567) and Game.isItemThere ({x = 32647, y = 32483, z = 09},2567) and Game.isItemThere ({x = 32645, y = 32465, z = 09}, 2567) then + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32485, z = 10}) + Game.sendMagicEffect({x = 32615, y = 32485, z = 10}, 11) + else + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 11) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/159.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/159.lua new file mode 100644 index 0000000..9e994e2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/159.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/16.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/16.lua new file mode 100644 index 0000000..085c05e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/16.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not Game.isItemThere({x = 32259, y = 31891, z = 10}, 2129) then + doRelocate({x = 32259, y = 31891, z = 10},{x = 32259, y = 31892, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31891, z = 10}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/160.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/160.lua new file mode 100644 index 0000000..fda5c49 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/160.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32660, y = 32113, z = 08}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32660, y = 32113, z = 08}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/161.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/161.lua new file mode 100644 index 0000000..863f13e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/161.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() then + doRelocate(item:getPosition(),{x = 32644, y = 32104, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/162.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/162.lua new file mode 100644 index 0000000..7193ea5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/162.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() then + doRelocate(item:getPosition(),{x = 32659, y = 32105, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/163.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/163.lua new file mode 100644 index 0000000..7334336 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/163.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isPaladin() then + doRelocate(item:getPosition(),{x = 32676, y = 32088, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/164.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/164.lua new file mode 100644 index 0000000..d8c2e74 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/164.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isKnight() then + doRelocate(item:getPosition(),{x = 32641, y = 32115, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/165.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/165.lua new file mode 100644 index 0000000..468458e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/165.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33324, y = 31592, z = 15}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33324, y = 31592, z = 15}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/166.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/166.lua new file mode 100644 index 0000000..aef1dbf --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/166.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33316, y = 31591, z = 15}, 1949) then + doRelocate(item:getPosition(),{x = 33328, y = 31592, z = 14}) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33316, y = 31591, z = 15}, 1949) then + doRelocate(item:getPosition(),{x = 33328, y = 31592, z = 14}) + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/167.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/167.lua new file mode 100644 index 0000000..5786947 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/167.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33277, y = 31592, z = 11}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33277, y = 31592, z = 11}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33277, y = 31592, z = 11}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33277, y = 31592, z = 11}, 15) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/168.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/168.lua new file mode 100644 index 0000000..4070a0b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/168.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33279, y = 31592, z = 12}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33279, y = 31592, z = 12}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33279, y = 31592, z = 12}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33279, y = 31592, z = 12}, 15) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/169.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/169.lua new file mode 100644 index 0000000..5500abe --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/169.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33234, y = 31642, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33234, y = 31642, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33234, y = 31642, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33234, y = 31642, z = 14}, 11) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/17.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/17.lua new file mode 100644 index 0000000..3b62616 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/17.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not Game.isItemThere({x = 32259, y = 31890, z = 10}, 2129) then + doRelocate({x = 32259, y = 31890, z = 10},{x = 32259, y = 31889, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31890, z = 10}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/170.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/170.lua new file mode 100644 index 0000000..1ed4100 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/170.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33070, y = 31620, z = 15}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33070, y = 31620, z = 15}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/171.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/171.lua new file mode 100644 index 0000000..39101b9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/171.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(323) == 1 and creature:getPlayer():getItemCount(5021) >= 1 then + doRelocate(item:getPosition(),{x = 32498, y = 31621, z = 06}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32498, y = 31621, z = 06}, 11) + creature:getPlayer():removeItem(5021, 1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/172.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/172.lua new file mode 100644 index 0000000..caad98c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/172.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(323) == 1 and creature:getPlayer():getItemCount(5021) >= 1 then + doRelocate(item:getPosition(),{x = 32664, y = 32735, z = 06}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32664, y = 32735, z = 06}, 11) + creature:getPlayer():removeItem(5021, 1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/173.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/173.lua new file mode 100644 index 0000000..e30d84b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/173.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32274, y = 31847, z = 15},{x = 32171, y = 31855, z = 15}) + Game.sendMagicEffect({x = 32171, y = 31855, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/174.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/174.lua new file mode 100644 index 0000000..1f0d072 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/174.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32274, y = 31858, z = 15},{x = 32216, y = 31846, z = 15}) + Game.sendMagicEffect({x = 32216, y = 31846, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/175.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/175.lua new file mode 100644 index 0000000..928430a --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/175.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32267, y = 31847, z = 15},{x = 32275, y = 31905, z = 13}) + Game.sendMagicEffect({x = 32275, y = 31905, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/176.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/176.lua new file mode 100644 index 0000000..58bf9c3 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/176.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32267, y = 31858, z = 15},{x = 32186, y = 31938, z = 14}) + Game.sendMagicEffect({x = 32186, y = 31938, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/177.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/177.lua new file mode 100644 index 0000000..db20c48 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/177.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32266, y = 31857, z = 12},{x = 32266, y = 31864, z = 12}) + Game.sendMagicEffect({x = 32266, y = 31864, z = 12}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/178.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/178.lua new file mode 100644 index 0000000..0c1b5b1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/178.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32266, y = 31863, z = 12},{x = 32259, y = 31892, z = 10}) + Game.sendMagicEffect({x = 32259, y = 31892, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/179.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/179.lua new file mode 100644 index 0000000..887ab5b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/179.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32262, y = 31889, z = 10},{x = 32259, y = 31892, z = 10}) + Game.sendMagicEffect({x = 32259, y = 31892, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/18.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/18.lua new file mode 100644 index 0000000..276055b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/18.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32104, y = 32082, z = 07},4615) and Game.isItemThere ({x = 32102, y = 32084, z = 07},2123) then + Game.removeItemOnMap({x = 32101, y = 32085, z = 07}, 3271) + Game.sendMagicEffect({x = 32101, y = 32085, z = 07}, 14) + Game.transformItemOnMap({x = 32100, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32101, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32100, y = 32085, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32085, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32100, y = 32086, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32101, y = 32086, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32086, z = 07}, 2123, 2125) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/180.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/180.lua new file mode 100644 index 0000000..e06dca8 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/180.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32259, y = 31858, z = 15},{x = 32312, y = 31974, z = 13}) + Game.sendMagicEffect({x = 32312, y = 31974, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/181.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/181.lua new file mode 100644 index 0000000..90d533f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/181.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32259, y = 31847, z = 15},{x = 32245, y = 31891, z = 14}) + Game.sendMagicEffect({x = 32245, y = 31891, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/182.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/182.lua new file mode 100644 index 0000000..ab59b32 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/182.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32201, y = 31845, z = 07}) + Game.sendMagicEffect({x = 32173, y = 31929, z = 07}, 11) + creature:getPlayer():setStorageValue(Obj2,205,1) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/183.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/183.lua new file mode 100644 index 0000000..8bf3847 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/183.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32177, y = 31864, z = 15},{x = 32177, y = 31870, z = 15}) + Game.sendMagicEffect({x = 32177, y = 31870, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/184.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/184.lua new file mode 100644 index 0000000..578689f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/184.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32180, y = 31871, z = 15},3027) and Game.isItemThere ({x = 32173, y = 31871, z = 15},3026) then + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31863, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31870, z = 15}, 11) + Game.removeItemOnMap({x = 32173, y = 31871, z = 15}, 3026) + Game.removeItemOnMap({x = 32180, y = 31871, z = 15}, 3027) + Game.sendMagicEffect({x = 32173, y = 31871, z = 15}, 3) + Game.sendMagicEffect({x = 32180, y = 31871, z = 15}, 3) + else + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31870, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -100, -100) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31870, z = 15}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/185.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/185.lua new file mode 100644 index 0000000..1c112de --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/185.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32176, y = 31864, z = 15},{x = 32176, y = 31870, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31870, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/186.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/186.lua new file mode 100644 index 0000000..8fa55fa --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/186.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32180, y = 31871, z = 15},3027) and Game.isItemThere ({x = 32173, y = 31871, z = 15},3026) then + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31863, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31863, z = 15}, 11) + Game.removeItemOnMap({x = 32173, y = 31871, z = 15}, 3026) + Game.removeItemOnMap({x = 32180, y = 31871, z = 15}, 3027) + Game.sendMagicEffect({x = 32173, y = 31871, z = 15}, 3) + Game.sendMagicEffect({x = 32180, y = 31871, z = 15}, 3) + else + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31870, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -100, -100) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31870, z = 15}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/187.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/187.lua new file mode 100644 index 0000000..6856ee7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/187.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(10) == 0 and Game.isItemThere({x = 32309, y = 31975, z = 13},1996) and Game.isItemThere ({x = 32309, y = 31976, z = 13},1996) and Game.isItemThere ({x = 32311, y = 31975, z = 13},1996) and Game.isItemThere ({x = 32311, y = 31976, z = 13},1996) and Game.isItemThere ({x = 32313, y = 31975, z = 13},1998) and Game.isItemThere ({x = 32313, y = 31976, z = 13}, 1998) then + Game.sendMagicEffect({x = 32311, y = 31978, z = 13}, 7) + creature:getPlayer():setStorageValue(10,1) + doRelocate(item:getPosition(),{x = 32261, y = 31856, z = 15}) + Game.sendMagicEffect({x = 32261, y = 31856, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32311, y = 31977, z = 13}) + Game.sendMagicEffect({x = 32311, y = 31977, z = 13}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -250, -250) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/188.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/188.lua new file mode 100644 index 0000000..1263897 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/188.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(5) == 0 then + Game.createMonster("ghost", {x = 32275, y = 31901, z = 13}) + Game.createMonster("ghost", {x = 32276, y = 31905, z = 13}) + creature:getPlayer():setStorageValue(5,1) + doRelocate(item:getPosition(),{x = 32266, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32266, y = 31849, z = 15}, 14) + Game.createMonster("demon skeleton", {x = 32275, y = 31903, z = 13}) + else + doRelocate(item:getPosition(),{x = 32277, y = 31903, z = 13}) + Game.sendMagicEffect({x = 32277, y = 31903, z = 13}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/189.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/189.lua new file mode 100644 index 0000000..9eed079 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/189.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(4) == 0 and Game.isItemThere({x = 32243, y = 31892, z = 14},2886) then + Game.removeItemOnMap({x = 32243, y = 31892, z = 14}, 2886) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 14) + creature:getPlayer():setStorageValue(4,1) + doRelocate(item:getPosition(),{x = 32261, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32261, y = 31849, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32249, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32249, y = 31892, z = 14}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/19.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/19.lua new file mode 100644 index 0000000..0d7de80 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/19.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32104, y = 32082, z = 07}, 4597) then + Game.transformItemOnMap({x = 32104, y = 32082, z = 07}, 4597, 4615) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/190.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/190.lua new file mode 100644 index 0000000..56e6924 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/190.lua @@ -0,0 +1,27 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(7) == 0 and Game.isItemThere({x = 32214, y = 31850, z = 15}, 2113) then + doRelocate(item:getPosition(),{x = 32271, y = 31857, z = 15}) + creature:getPlayer():setStorageValue(7,1) + Game.sendMagicEffect({x = 32271, y = 31857, z = 15}, 14) + Game.sendMagicEffect({x = 32217, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31847, z = 14}, 12) + Game.sendMagicEffect({x = 32213, y = 31847, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31848, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31848, z = 14}, 12) + Game.createItem(2122, 1, {x = 32215, y = 31848, z = 15}) + Game.transformItemOnMap({x = 32214, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32215, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32216, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32220, y = 31842, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31843, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31844, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31845, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31846, z = 15}, 2772, 2773) + else + doRelocate(item:getPosition(),{x = 32215, y = 31848, z = 15}) + Game.sendMagicEffect({x = 32215, y = 31848, z = 15}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/191.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/191.lua new file mode 100644 index 0000000..35c299f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/191.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) == 1 and creature:getPlayer():getStorageValue(9) == 0 then + creature:getPlayer():setStorageValue(9,1) + doRelocate(item:getPosition(),{x = 32268, y = 31856, z = 15}) + Game.sendMagicEffect({x = 32268, y = 31856, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32191, y = 31938, z = 14}) + Game.sendMagicEffect({x = 32191, y = 31938, z = 14}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/192.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/192.lua new file mode 100644 index 0000000..0884e8a --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/192.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(6) == 0 then + Game.createItem(2121, 1, {x = 32171, y = 31854, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31854, z = 15}) + creature:getPlayer():setStorageValue(6,1) + doRelocate(item:getPosition(),{x = 32272, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32272, y = 31849, z = 15}, 14) + Game.createItem(2121, 1, {x = 32172, y = 31854, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32171, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32172, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31856, z = 15}) + Game.createItem(2121, 1, {x = 32171, y = 31856, z = 15}) + Game.createItem(2121, 1, {x = 32172, y = 31856, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_EARTHDAMAGE, -33, -33) + else + doRelocate(item:getPosition(),{x = 32171, y = 31854, z = 15}) + Game.sendMagicEffect({x = 32171, y = 31854, z = 15}, 1) + doTargetCombatHealth(0, creature, COMBAT_EARTHDAMAGE, -155, -155) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/193.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/193.lua new file mode 100644 index 0000000..303e4e5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/193.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32816, y = 31601, z = 09},3206) and creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32701, y = 31639, z = 06}) + Game.removeItemOnMap({x = 32816, y = 31601, z = 09}, 3206) + Game.sendMagicEffect({x = 32701, y = 31639, z = 06}, 11) + Game.sendMagicEffect({x = 32816, y = 31601, z = 09}, 14) + creature:getPlayer():setStorageValue(65,0) + creature:getPlayer():setStorageValue(66,0) + else + doRelocate(item:getPosition(),{x = 32818, y = 31599, z = 09}) + Game.sendMagicEffect({x = 32818, y = 31599, z = 09}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32818, y = 31599, z = 09}) + Game.sendMagicEffect({x = 32818, y = 31599, z = 09}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/194.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/194.lua new file mode 100644 index 0000000..fee1ef7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/194.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32794, y = 31577, z = 05}) + Game.sendMagicEffect({x = 32794, y = 31577, z = 05}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/195.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/195.lua new file mode 100644 index 0000000..1198776 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/195.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32812, y = 31577, z = 05}) + Game.sendMagicEffect({x = 32812, y = 31577, z = 05}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/196.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/196.lua new file mode 100644 index 0000000..38176e6 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/196.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32803, y = 31584, z = 01},2773) and Game.isItemThere ({x = 32805, y = 31584, z = 01},2773) and Game.isItemThere ({x = 32802, y = 31584, z = 01},2772) and Game.isItemThere ({x = 32804, y = 31584, z = 01}, 2772) then + doRelocate(item:getPosition(),{x = 32701, y = 31639, z = 06}) + Game.sendMagicEffect({x = 32701, y = 31639, z = 06}, 11) + else + doRelocate(item:getPosition(),{x = 32803, y = 31587, z = 01}) + Game.sendMagicEffect({x = 32803, y = 31587, z = 01}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32803, y = 31587, z = 01}) + Game.sendMagicEffect({x = 32803, y = 31587, z = 01}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/197.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/197.lua new file mode 100644 index 0000000..a932e1d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/197.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32725, y = 31589, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32725, y = 31589, z = 12}) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/198.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/198.lua new file mode 100644 index 0000000..d7c534b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/198.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33213, y = 32454, z = 01}) + creature:getPlayer():setTown(Town("Darashia")) + Game.sendMagicEffect({x = 33213, y = 32454, z = 01}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33212, y = 32455, z = 02}) + Game.sendMagicEffect({x = 33212, y = 32455, z = 02}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/199.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/199.lua new file mode 100644 index 0000000..82d0486 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/199.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33217, y = 31814, z = 08}) + creature:getPlayer():setTown(Town("Edron")) + Game.sendMagicEffect({x = 33217, y = 31814, z = 08}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33210, y = 31806, z = 08}) + Game.sendMagicEffect({x = 33210, y = 31806, z = 08}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/2.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/2.lua new file mode 100644 index 0000000..ef20da0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/2.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + if Game.isItemThere({x = 33368, y = 31756, z = 11}, 355) then + Game.transformItemOnMap({x = 33368, y = 31756, z = 11}, 355, 386) + end + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + Game.transformItemOnMap({x = 33368, y = 31756, z = 11}, 386, 355) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/20.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/20.lua new file mode 100644 index 0000000..e141166 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/20.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(293) == 7 then + creature:getPlayer():setStorageValue(296,1) + item:getPosition():sendMagicEffect(13) + item:getPosition():sendMonsterSay("!-! -O- I_I (/( --I Morgathla") + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/200.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/200.lua new file mode 100644 index 0000000..6f57d37 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/200.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33194, y = 32853, z = 08}) + creature:getPlayer():setTown(Town("Ankrahmun")) + Game.sendMagicEffect({x = 33194, y = 32853, z = 08}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33195, y = 32851, z = 06}) + Game.sendMagicEffect({x = 33195, y = 32851, z = 06}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/201.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/201.lua new file mode 100644 index 0000000..6e7bacd --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/201.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32957, y = 32076, z = 07}) + creature:getPlayer():setTown(Town("Venore")) + Game.sendMagicEffect({x = 32957, y = 32076, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32952, y = 32035, z = 07}) + Game.sendMagicEffect({x = 32952, y = 32035, z = 07}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/202.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/202.lua new file mode 100644 index 0000000..e65c90c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/202.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32649, y = 31925, z = 11}) + creature:getPlayer():setTown(Town("Kazordoon")) + Game.sendMagicEffect({x = 32649, y = 31925, z = 11}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32647, y = 31925, z = 12}) + Game.sendMagicEffect({x = 32647, y = 31925, z = 12}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/203.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/203.lua new file mode 100644 index 0000000..e65c90c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/203.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32649, y = 31925, z = 11}) + creature:getPlayer():setTown(Town("Kazordoon")) + Game.sendMagicEffect({x = 32649, y = 31925, z = 11}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32647, y = 31925, z = 12}) + Game.sendMagicEffect({x = 32647, y = 31925, z = 12}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/204.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/204.lua new file mode 100644 index 0000000..d8a7edb --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/204.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32732, y = 31634, z = 07}) + creature:getPlayer():setTown(Town("Ab'Dendriel")) + Game.sendMagicEffect({x = 32732, y = 31634, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32607, y = 31681, z = 07}) + Game.sendMagicEffect({x = 32607, y = 31681, z = 07}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/205.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/205.lua new file mode 100644 index 0000000..aa1e0b2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/205.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32595, y = 32744, z = 06}) + creature:getPlayer():setTown(Town("Port Hope")) + Game.sendMagicEffect({x = 32595, y = 32744, z = 06}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32595, y = 32744, z = 06}) + Game.sendMagicEffect({x = 32595, y = 32744, z = 06}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/206.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/206.lua new file mode 100644 index 0000000..17fd93e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/206.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32369, y = 32241, z = 07}) + creature:getPlayer():setTown(Town("Thais")) + Game.sendMagicEffect({x = 32369, y = 32241, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32369, y = 32242, z = 06}) + Game.sendMagicEffect({x = 32369, y = 32242, z = 06}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/207.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/207.lua new file mode 100644 index 0000000..83b17b7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/207.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32360, y = 31782, z = 07}) + creature:getPlayer():setTown(Town("Carlin")) + Game.sendMagicEffect({x = 32360, y = 31782, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32360, y = 31780, z = 08}) + Game.sendMagicEffect({x = 32360, y = 31780, z = 08}, 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/208.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/208.lua new file mode 100644 index 0000000..0f88e66 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/208.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32569, y = 32110, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32569, y = 32110, z = 07}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32569, y = 32110, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32569, y = 32110, z = 07}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/209.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/209.lua new file mode 100644 index 0000000..5a8b655 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/209.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32526, y = 32156, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32526, y = 32156, z = 12}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/21.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/21.lua new file mode 100644 index 0000000..146ff38 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/21.lua @@ -0,0 +1,10 @@ +local condition = Condition(CONDITION_POISON) +condition:setTiming(1000) + +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:addCondition(condition) + creature:getPlayer():setStorageValue(270,1) + Game.sendMagicEffect({x = 33362, y = 32811, z = 14}, 9) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/210.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/210.lua new file mode 100644 index 0000000..a846e12 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/210.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32554, y = 32212, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32554, y = 32212, z = 11}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/211.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/211.lua new file mode 100644 index 0000000..be623e2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/211.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32492, y = 31697, z = 07}) + Game.sendMagicEffect({x = 32492, y = 31697, z = 07}, 13) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32492, y = 31697, z = 07}) + Game.sendMagicEffect({x = 32492, y = 31697, z = 07}, 13) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/212.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/212.lua new file mode 100644 index 0000000..62d0d60 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/212.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/213.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/213.lua new file mode 100644 index 0000000..28f610f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/213.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32281, y = 32389, z = 10}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32281, y = 32389, z = 10}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32281, y = 32389, z = 10}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32281, y = 32389, z = 10}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/214.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/214.lua new file mode 100644 index 0000000..62d0d60 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/214.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/215.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/215.lua new file mode 100644 index 0000000..e398e00 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/215.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32233, y = 32276, z = 9}, 1949) then + doRelocate(item:getPosition(),{x = 32225, y = 32275, z = 10}) + Game.sendMagicEffect({x = 32233, y = 32276, z = 09}, 15) + Game.sendMagicEffect({x = 32225, y = 32275, z = 10}, 15) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32233, y = 32276, z = 9}, 1949) then + doRelocate(item:getPosition(),{x = 32225, y = 32275, z = 10}) + Game.sendMagicEffect({x = 32233, y = 32276, z = 09}, 15) + Game.sendMagicEffect({x = 32225, y = 32275, z = 10}, 15) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/216.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/216.lua new file mode 100644 index 0000000..38aec49 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/216.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32225, y = 32276, z = 10}, 1949) then + doRelocate(item:getPosition(),{x = 32232, y = 32276, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32232, y = 32276, z = 09}, 15) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32225, y = 32276, z = 10}, 1949) then + doRelocate(item:getPosition(),{x = 32232, y = 32276, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32232, y = 32276, z = 09}, 15) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/217.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/217.lua new file mode 100644 index 0000000..f1f5e31 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/217.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32107, y = 31567, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32107, y = 31567, z = 09}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/218.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/218.lua new file mode 100644 index 0000000..7b48d45 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/218.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32189, y = 31625, z = 04}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32189, y = 31625, z = 04}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/219.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/219.lua new file mode 100644 index 0000000..5cab3bd --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/219.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32081, y = 32172, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32081, y = 32172, z = 09}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/22.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/22.lua new file mode 100644 index 0000000..72bd0ad --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/22.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33199, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33199, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33199, y = 32699, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33199, y = 32699, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/220.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/220.lua new file mode 100644 index 0000000..818b085 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/220.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32090, y = 32172, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32090, y = 32172, z = 09}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/221.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/221.lua new file mode 100644 index 0000000..0d49c0b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/221.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32481, y = 31904, z = 04},{x = 32481, y = 31904, z = 05}) + Game.sendMagicEffect({x = 32481, y = 31904, z = 05}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32481, y = 31904, z = 04},{x = 32481, y = 31904, z = 05}) + Game.sendMagicEffect({x = 32481, y = 31904, z = 05}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/222.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/222.lua new file mode 100644 index 0000000..826c814 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/222.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32481, y = 31905, z = 01},{x = 32480, y = 31905, z = 02}) + Game.sendMagicEffect({x = 32480, y = 31905, z = 02}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32481, y = 31905, z = 01},{x = 32480, y = 31905, z = 02}) + Game.sendMagicEffect({x = 32480, y = 31905, z = 02}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/223.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/223.lua new file mode 100644 index 0000000..caac536 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/223.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32479, y = 31904, z = 02},{x = 32479, y = 31904, z = 03}) + Game.sendMagicEffect({x = 32479, y = 31904, z = 03}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32479, y = 31904, z = 02},{x = 32479, y = 31904, z = 03}) + Game.sendMagicEffect({x = 32479, y = 31904, z = 03}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/224.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/224.lua new file mode 100644 index 0000000..6295ccf --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/224.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32476, y = 31904, z = 05},{x = 32476, y = 31904, z = 06}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 06}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32476, y = 31904, z = 05},{x = 32476, y = 31904, z = 06}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 06}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/225.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/225.lua new file mode 100644 index 0000000..01105ca --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/225.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32476, y = 31904, z = 03},{x = 32476, y = 31904, z = 04}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 04}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32476, y = 31904, z = 03},{x = 32476, y = 31904, z = 04}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 04}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/226.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/226.lua new file mode 100644 index 0000000..03ac52a --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/226.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/227.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/227.lua new file mode 100644 index 0000000..668cc74 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/227.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() then + doRelocate(item:getPosition(),{x = 32851, y = 32339, z = 06}) + else + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/228.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/228.lua new file mode 100644 index 0000000..4a6431b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/228.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32776, y = 32255, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32776, y = 32255, z = 11}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/229.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/229.lua new file mode 100644 index 0000000..6ff5272 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/229.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32767, y = 32229, z = 07}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32767, y = 32229, z = 07}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/23.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/23.lua new file mode 100644 index 0000000..7b10866 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/23.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33225, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33225, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33225, y = 32699, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33225, y = 32699, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/230.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/230.lua new file mode 100644 index 0000000..3ca1ce9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/230.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() then + item:transform(453, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(452, 1) + item:decay() +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/231.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/231.lua new file mode 100644 index 0000000..7564ca4 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/231.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32918, y = 32072, z = 12}) + Game.sendMagicEffect({x = 32918, y = 32072, z = 11}, 13) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/232.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/232.lua new file mode 100644 index 0000000..95fb86b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/232.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32078, z = 05}, 2114, 2113) +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32078, z = 05}, 2113, 2114) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/233.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/233.lua new file mode 100644 index 0000000..0935d16 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/233.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32079, z = 5}, 2114, 2113) +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32079, z = 5}, 2113, 2114) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/234.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/234.lua new file mode 100644 index 0000000..78e0196 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/234.lua @@ -0,0 +1,31 @@ +function onRemoveItem(item, tileitem, position) + local demonsInRoomCount = 0; + local spectators = Game.getSpectators(Position(33063, 31624, 15), false, false, 20, 20, 20, 20) + for i = 1, #spectators do + local creature = spectators[i] + if creature:getName():lower() == "demon" then + demonsInRoomCount = demonsInRoomCount + 1 + end + end + + if (demonsInRoomCount <= 1) then + local demon1 = Game.isMonsterThere({x = 33336, y = 31954, z = 15}, "Demon") + local demon2 = Game.isMonsterThere({x = 33340, y = 31954, z = 15}, "Demon") + local demon3 = Game.isMonsterThere({x = 33340, y = 31958, z = 15}, "Demon") + local demon4 = Game.isMonsterThere({x = 33336, y = 31958, z = 15}, "Demon") + if demon1 ~= nil and demon2 ~= nil and demon3 ~= nil and demon4 ~= nil then + demon1:addHealth(-demon1:getMaxHealth()) + demon2:addHealth(-demon2:getMaxHealth()) + demon3:addHealth(-demon3:getMaxHealth()) + demon4:addHealth(-demon4:getMaxHealth()) + Game.createMonster("Demon", {x = 33060, y = 31623, z = 15}) + Game.createMonster("Demon", {x = 33066, y = 31623, z = 15}) + Game.createMonster("Demon", {x = 33066, y = 31627, z = 15}) + Game.createMonster("Demon", {x = 33060, y = 31627, z = 15}) + Game.sendMagicEffect({x = 33060, y = 31622, z = 15}, 14) + Game.sendMagicEffect({x = 33066, y = 31622, z = 15}, 14) + Game.sendMagicEffect({x = 33066, y = 31628, z = 15}, 14) + Game.sendMagicEffect({x = 33060, y = 31628, z = 15}, 14) + end + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/235.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/235.lua new file mode 100644 index 0000000..23db42b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/235.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33289, y = 32481, z = 6}) +end + +function onAddItem(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33289, y = 32481, z = 6}) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/236.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/236.lua new file mode 100644 index 0000000..5246b4d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/236.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + local player = Player(creature) + player:setStorageValue(260, 0) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/237.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/237.lua new file mode 100644 index 0000000..6b57483 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/237.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + local player = Player(creature) + player:setStorageValue(260, 1) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/24.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/24.lua new file mode 100644 index 0000000..eb0eb7f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/24.lua @@ -0,0 +1,13 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33198, y = 32876, z = 11},3222) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3223) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3224) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3225) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3226) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3227) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3228) then + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3222) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3223) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3224) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3225) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3226) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3227) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3228) + Game.createItem(3229, 1, {x = 33198, y = 32876, z = 11}) + Game.sendMagicEffect({x = 33198, y = 32876, z = 11}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/25.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/25.lua new file mode 100644 index 0000000..fe26398 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/25.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/26.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/26.lua new file mode 100644 index 0000000..82f0bdb --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/26.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32796, y = 31594, z = 05}, 1270) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32796, y = 31594, z = 05},{x = 32797, y = 31594, z = 05}) + Game.createItem(1270, 1, {x = 32796, y = 31594, z = 05}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/27.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/27.lua new file mode 100644 index 0000000..06a7119 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/27.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32796, y = 31576, z = 05}, 1270) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32796, y = 31576, z = 05},{x = 32797, y = 31576, z = 05}) + Game.createItem(1270, 1, {x = 32796, y = 31576, z = 05}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/28.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/28.lua new file mode 100644 index 0000000..6967e3b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/28.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32692, y = 32102, z = 10}, 1281) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32692, y = 32102, z = 10},{x = 32691, y = 32102, z = 10}) + Game.createItem(1281, 1, {x = 32692, y = 32102, z = 10}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/29.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/29.lua new file mode 100644 index 0000000..59745b6 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/29.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() and Game.isItemThere({x = 32679, y = 32089, z = 08}, 3059) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32692, y = 32102, z = 10},{x = 32691, y = 32102, z = 10}) + Game.createItem(1281, 1, {x = 32692, y = 32102, z = 10}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/3.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/3.lua new file mode 100644 index 0000000..5e654fb --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/3.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32771, y = 32297, z = 10},389) then + Game.removeItemOnMap({x = 32771, y = 32297, z = 10}, 389) + Game.transformItemOnMap({x = 32770, y = 32282, z = 10}, 371, 395) + end +end + +function onStepOut(creature, item, position, fromPosition) + if not Game.isItemThere({x = 32771, y = 32297, z = 10}, 389) and creature:isPlayer() then + doRelocate({x = 32771, y = 32297, z = 10},{x = 32771, y = 32296, z = 10}) + Game.createItem(389, 1, {x = 32771, y = 32297, z = 10}) + Game.transformItemOnMap({x = 32770, y = 32282, z = 10}, 395, 371) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/30.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/30.lua new file mode 100644 index 0000000..8673d9c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/30.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isKnight() and Game.isItemThere({x = 32673, y = 32094, z = 08}, 3264) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/31.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/31.lua new file mode 100644 index 0000000..d7363a5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/31.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isPaladin() and Game.isItemThere({x = 32673, y = 32083, z = 08}, 3349) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/32.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/32.lua new file mode 100644 index 0000000..061d28b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/32.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() and Game.isItemThere({x = 32667, y = 32089, z = 08}, 3585) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/33.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/33.lua new file mode 100644 index 0000000..e905df1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/33.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33190, y = 31629, z = 13}) and Game.isItemThere({x = 33210, y = 31630, z = 13},1295) then + Game.removeItemOnMap({x = 33210, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33211, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33212, y = 31630, z = 13}, 1295) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33190, y = 31629, z = 13}) then + doRelocate({x = 33210, y = 31630, z = 13},{x = 33210, y = 31631, z = 13}) + doRelocate({x = 33211, y = 31630, z = 13},{x = 33211, y = 31631, z = 13}) + doRelocate({x = 33212, y = 31630, z = 13},{x = 33212, y = 31631, z = 13}) + Game.createItem(1295, 1, {x = 33210, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33211, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33212, y = 31630, z = 13}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/34.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/34.lua new file mode 100644 index 0000000..1170e40 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/34.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33191, y = 31629, z = 13}) and Game.isItemThere({x = 33210, y = 31630, z = 13},1295) then + Game.removeItemOnMap({x = 33210, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33211, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33212, y = 31630, z = 13}, 1295) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33191, y = 31629, z = 13}) then + doRelocate({x = 33210, y = 31630, z = 13},{x = 33210, y = 31631, z = 13}) + doRelocate({x = 33211, y = 31630, z = 13},{x = 33211, y = 31631, z = 13}) + doRelocate({x = 33212, y = 31630, z = 13},{x = 33212, y = 31631, z = 13}) + Game.createItem(1295, 1, {x = 33210, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33211, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33212, y = 31630, z = 13}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/35.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/35.lua new file mode 100644 index 0000000..20d9c40 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/35.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if not Game.isItemThere({x = 32266, y = 31861, z = 11}, 2772) then + Game.transformItemOnMap({x = 32266, y = 31861, z = 11}, 2773, 2772) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + if not Game.isItemThere({x = 32266, y = 31861, z = 11}, 2772) then + Game.transformItemOnMap({x = 32266, y = 31861, z = 11}, 2773, 2772) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/36.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/36.lua new file mode 100644 index 0000000..4e9ce6f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/36.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32255, y = 31837, z = 09},2026) then + Game.sendMagicEffect({x = 32255, y = 31837, z = 09}, 3) + Game.removeItemOnMap({x = 32255, y = 31837, z = 09}, 2026) + doRelocate({x = 32255, y = 31837, z = 10},{x = 32255, y = 31837, z = 09}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/37.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/37.lua new file mode 100644 index 0000000..c319e79 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/37.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(3) == 0 then + Game.createMonster("Warlock", {x = 32216, y = 31841, z = 15}) + Game.createMonster("Warlock", {x = 32216, y = 31834, z = 15}) + creature:getPlayer():setStorageValue(3,1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/38.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/38.lua new file mode 100644 index 0000000..a26d6aa --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/38.lua @@ -0,0 +1,10 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) == 0 then + creature:getPlayer():setStorageValue(8, 1) + end + Game.sendMagicEffect(item:getPosition(), 15) +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 15) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/39.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/39.lua new file mode 100644 index 0000000..88e6157 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/39.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) ~= 0 then + Game.sendMagicEffect(item:getPosition(), 15) + else + Game.sendMagicEffect(item:getPosition(), 14) + end +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/4.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/4.lua new file mode 100644 index 0000000..9a27453 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/4.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32592, y = 31787, z = 04},3695) and creature:getPlayer():getStorageValue(45) < 1 then + Game.removeItemOnMap({x = 32592, y = 31787, z = 04}, 3695) + Game.sendMagicEffect({x = 32592, y = 31787, z = 04}, 15) + doRelocate({x = 32592, y = 31787, z = 04},{x = 32593, y = 31787, z = 04}) + Game.createItem(3696, 1, {x = 32592, y = 31787, z = 04}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/40.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/40.lua new file mode 100644 index 0000000..29310b2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/40.lua @@ -0,0 +1,10 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) ~= 0 then + creature:getPlayer():setStorageValue(8, 0) + end + Game.sendMagicEffect(item:getPosition(), 14) +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 14) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/41.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/41.lua new file mode 100644 index 0000000..faffa65 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/41.lua @@ -0,0 +1,25 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32243, y = 31892, z = 14}, 2886) and item:getFluidType() == FLUID_BLOOD then + Game.sendMagicEffect({x = 32242, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31892, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31893, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31893, z = 14}, 1) + else + doRelocate({x = 32243, y = 31892, z = 14},{x = 32244, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 3) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32243, y = 31892, z = 14}, 2886) and item:getFluidType() == FLUID_BLOOD then + Game.sendMagicEffect({x = 32242, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31892, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31893, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31893, z = 14}, 1) + else + doRelocate({x = 32243, y = 31892, z = 14},{x = 32244, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/42.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/42.lua new file mode 100644 index 0000000..5a9fb8c --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/42.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(220) < 1 then + creature:getPlayer():setStorageValue(220,1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/43.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/43.lua new file mode 100644 index 0000000..0051d09 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/43.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(64) < 1 then + creature:getPlayer():setStorageValue(64,1) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/44.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/44.lua new file mode 100644 index 0000000..41e1954 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/44.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32282, z = 09}, 429, 438) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32282, z = 09}, 438, 429) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/45.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/45.lua new file mode 100644 index 0000000..d257246 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/45.lua @@ -0,0 +1,19 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.sendMagicEffect({x = 32468, y = 32119, z = 14}, 15) + Game.sendMagicEffect({x = 32482, y = 32170, z = 14}, 15) + Game.createItem(435, 1, {x = 32482, y = 32170, z = 14}) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + Game.sendMagicEffect({x = 32468, y = 32119, z = 14}, 14) + Game.sendMagicEffect({x = 32482, y = 32170, z = 14}, 14) + Game.removeItemOnMap({x = 32482, y = 32170, z = 14}, 435) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/46.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/46.lua new file mode 100644 index 0000000..a96cc13 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/46.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32479, y = 31920, z = 07},3696) and Game.isItemThere ({x = 32478, y = 31920, z = 07},3696) and Game.isItemThere ({x = 32478, y = 31902, z = 07}, 1791) then + Game.transformItemOnMap({x = 32478, y = 31902, z = 07}, 1791, 1947) + Game.sendMagicEffect({x = 32478, y = 31902, z = 07}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/47.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/47.lua new file mode 100644 index 0000000..976ac23 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/47.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42,1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/48.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/48.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/48.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/49.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/49.lua new file mode 100644 index 0000000..9d696d9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/49.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(43) < 1 then + creature:getPlayer():setStorageValue(43,1) + Game.sendMagicEffect({x = 32479, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/5.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/5.lua new file mode 100644 index 0000000..8eeaaad --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/5.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(48) < 1 then + creature:getPlayer():setStorageValue(48,1) + Game.sendMagicEffect({x = 32549, y = 32142, z = 07}, 15) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/50.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/50.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/50.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/51.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/51.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/51.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/52.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/52.lua new file mode 100644 index 0000000..a3a6412 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/52.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42, 1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/53.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/53.lua new file mode 100644 index 0000000..9d696d9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/53.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(43) < 1 then + creature:getPlayer():setStorageValue(43,1) + Game.sendMagicEffect({x = 32479, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/54.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/54.lua new file mode 100644 index 0000000..27176bc --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/54.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(44) < 1 then + creature:getPlayer():setStorageValue(44,1) + Game.sendMagicEffect({x = 32480, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/55.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/55.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/55.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/56.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/56.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/56.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/57.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/57.lua new file mode 100644 index 0000000..f61f240 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/57.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(44) < 1 then + creature:getPlayer():setStorageValue(44,1) + Game.sendMagicEffect({x = 32480, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/58.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/58.lua new file mode 100644 index 0000000..976ac23 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/58.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42,1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/59.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/59.lua new file mode 100644 index 0000000..f2e1d96 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/59.lua @@ -0,0 +1,17 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32476, y = 31900, z = 05},2471) and not Game.isItemThere ({x = 32478, y = 31904, z = 05}, 1948) then + Game.createItem(1948, 1, {x = 32478, y = 31904, z = 05}) + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 431, 430) + elseif Game.isItemThere({x = 32476, y = 31900, z = 05}, 2471) then + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 431, 430) + end +end + +function onRemoveItem(item, tileitem, position) + if Game.isItemThere({x = 32478, y = 31904, z = 05},1948) then + Game.removeItemOnMap({x = 32478, y = 31904, z = 05}, 1948) + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 430, 431) + else + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 430, 431) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/6.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/6.lua new file mode 100644 index 0000000..24cfb0f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/6.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Tile({x = 32502, y = 31890, z = 07}):getThingCount() == 2 then + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -200, -200) + Game.createItem(2121, 1, {x = 32497, y = 31889, z = 07}) + Game.createItem(2121, 1, {x = 32499, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32497, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32498, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32496, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32494, y = 31888, z = 07}) + Game.createItem(2121, 1, {x = 32502, y = 31890, z = 07}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/60.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/60.lua new file mode 100644 index 0000000..0ba18c2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/60.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32563, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32565, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32567, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32569, y = 31957, z = 01},3114) then + doRelocate(item:getPosition(),{x = 32479, y = 31923, z = 07}) + Game.sendMagicEffect({x = 32563, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32565, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32567, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32569, y = 31957, z = 01}, 10) + Game.removeItemOnMap({x = 32563, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32565, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32567, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32569, y = 31957, z = 01}, 3114) + Game.createItem(2121, 1, {x = 32563, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32565, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32567, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32569, y = 31957, z = 01}) + Game.sendMagicEffect({x = 32566, y = 31957, z = 01}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/61.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/61.lua new file mode 100644 index 0000000..c7dc270 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/61.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32566, y = 31959, z = 01}) + Game.sendMagicEffect({x = 32487, y = 31928, z = 07}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/62.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/62.lua new file mode 100644 index 0000000..a6fe84d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/62.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getLevel() < 2 then + doRelocate(item:getPosition(),{x = item:getPosition().x - 1, y = 32176, z = 07}) + Game.sendMagicEffect({x = item:getPosition().x - 1, y = 32176, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = tileitem:getPosition().x - 1, y = 32176, z = 07}) + Game.sendMagicEffect({x = tileitem:getPosition().x - 1, y = 32176, z = 07}, 13) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/63.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/63.lua new file mode 100644 index 0000000..7a4a9f5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/63.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = tileitem:getPosition().x + 3, y = tileitem:getPosition().y, z = 07}) + Game.sendMagicEffect(tileitem:getPosition(), 13) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/64.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/64.lua new file mode 100644 index 0000000..c241239 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/64.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33293, y = 32741, z = 13},3042) then + doRelocate({x = 33293, y = 32742, z = 13},{x = 33299, y = 32742, z = 13}) + Game.sendMagicEffect({x = 33293, y = 32742, z = 13}, 11) + Game.sendMagicEffect({x = 33299, y = 32742, z = 13}, 11) + Game.sendMagicEffect({x = 33293, y = 32741, z = 13}, 16) + Game.removeItemOnMap({x = 33293, y = 32741, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33293, y = 32741, z = 13}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/65.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/65.lua new file mode 100644 index 0000000..ba7e7c5 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/65.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33276, y = 32552, z = 14},3042) then + doRelocate({x = 33276, y = 32553, z = 14},{x = 33271, y = 32553, z = 14}) + Game.sendMagicEffect({x = 33276, y = 32553, z = 14}, 11) + Game.sendMagicEffect({x = 33271, y = 32553, z = 14}, 11) + Game.sendMagicEffect({x = 33276, y = 32552, z = 14}, 16) + Game.removeItemOnMap({x = 33276, y = 32552, z = 14}, 3042) + else + Game.sendMagicEffect({x = 33276, y = 32552, z = 14}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/66.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/66.lua new file mode 100644 index 0000000..73f87b8 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/66.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33240, y = 32855, z = 13},3042) then + doRelocate({x = 33240, y = 32856, z = 13},{x = 33246, y = 32850, z = 13}) + Game.sendMagicEffect({x = 33240, y = 32856, z = 13}, 11) + Game.sendMagicEffect({x = 33246, y = 32850, z = 13}, 11) + Game.sendMagicEffect({x = 33240, y = 32855, z = 13}, 16) + Game.removeItemOnMap({x = 33240, y = 32855, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33240, y = 32855, z = 13}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/67.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/67.lua new file mode 100644 index 0000000..b1a67c2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/67.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33233, y = 32692, z = 13},3042) then + doRelocate({x = 33234, y = 32692, z = 13},{x = 33234, y = 32687, z = 13}) + Game.sendMagicEffect({x = 33234, y = 32692, z = 13}, 11) + Game.sendMagicEffect({x = 33234, y = 32687, z = 13}, 11) + Game.sendMagicEffect({x = 33233, y = 32692, z = 13}, 16) + Game.removeItemOnMap({x = 33233, y = 32692, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33233, y = 32692, z = 13}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/68.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/68.lua new file mode 100644 index 0000000..59f599d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/68.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33161, y = 32831, z = 10},3042) then + doRelocate({x = 33162, y = 32831, z = 10},{x = 33158, y = 32832, z = 10}) + Game.sendMagicEffect({x = 33162, y = 32831, z = 10}, 11) + Game.sendMagicEffect({x = 33158, y = 32832, z = 10}, 11) + Game.sendMagicEffect({x = 33161, y = 32831, z = 10}, 16) + Game.removeItemOnMap({x = 33161, y = 32831, z = 10}, 3042) + else + Game.sendMagicEffect({x = 33161, y = 32831, z = 10}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/69.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/69.lua new file mode 100644 index 0000000..9d713ac --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/69.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33135, y = 32682, z = 12},3042) then + doRelocate({x = 33135, y = 32683, z = 12},{x = 33130, y = 32683, z = 12}) + Game.sendMagicEffect({x = 33135, y = 32683, z = 12}, 11) + Game.sendMagicEffect({x = 33130, y = 32683, z = 12}, 11) + Game.sendMagicEffect({x = 33135, y = 32682, z = 12}, 16) + Game.removeItemOnMap({x = 33135, y = 32682, z = 12}, 3042) + else + Game.sendMagicEffect({x = 33135, y = 32682, z = 12}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/7.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/7.lua new file mode 100644 index 0000000..e6abcbc --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/7.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32478, y = 31902, z = 07}, 1947) then + Game.transformItemOnMap({x = 32478, y = 31902, z = 07}, 1947, 1791) + Game.sendMagicEffect({x = 32478, y = 31902, z = 07}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/70.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/70.lua new file mode 100644 index 0000000..ff4b6ec --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/70.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33098, y = 32816, z = 13},3042) then + doRelocate({x = 33097, y = 32816, z = 13},{x = 33093, y = 32824, z = 13}) + Game.sendMagicEffect({x = 33097, y = 32816, z = 13}, 11) + Game.sendMagicEffect({x = 33093, y = 32824, z = 13}, 11) + Game.sendMagicEffect({x = 33098, y = 32816, z = 13}, 16) + Game.removeItemOnMap({x = 33098, y = 32816, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33098, y = 32816, z = 13}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/71.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/71.lua new file mode 100644 index 0000000..9a036e4 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/71.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33073, y = 32589, z = 13},3042) then + doRelocate({x = 33073, y = 32590, z = 13},{x = 33080, y = 32588, z = 13}) + Game.sendMagicEffect({x = 33073, y = 32590, z = 13}, 11) + Game.sendMagicEffect({x = 33080, y = 32589, z = 13}, 11) + Game.sendMagicEffect({x = 33073, y = 32589, z = 13}, 16) + Game.removeItemOnMap({x = 33073, y = 32589, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33073, y = 32589, z = 13}, 3) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/72.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/72.lua new file mode 100644 index 0000000..6e707ff --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/72.lua @@ -0,0 +1,5 @@ +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32775, y = 31595, z = 07}) + Game.sendMagicEffect({x = 32775, y = 31595, z = 07}, 13) + Game.sendMagicEffect({x = 32701, y = 31637, z = 06}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/73.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/73.lua new file mode 100644 index 0000000..fcb0e0e --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/73.lua @@ -0,0 +1,5 @@ +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32800, y = 31605, z = 07}) + Game.sendMagicEffect({x = 32800, y = 31605, z = 07}, 13) + Game.sendMagicEffect({x = 32701, y = 31638, z = 06}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/74.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/74.lua new file mode 100644 index 0000000..cf2eebf --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/74.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33368, y = 32763, z = 14},2567) and Game.isItemThere ({x = 33382, y = 32786, z = 14},2567) and Game.isItemThere ({x = 33305, y = 32734, z = 14},2567) and Game.isItemThere ({x = 33338, y = 32702, z = 14},2567) and Game.isItemThere ({x = 33320, y = 32682, z = 14},2567) and Game.isItemThere ({x = 33349, y = 32680, z = 14},2567) and Game.isItemThere ({x = 33358, y = 32701, z = 14},2567) and Game.isItemThere ({x = 33357, y = 32749, z = 14}, 2567) then + doRelocate(item:getPosition(),{x = 33367, y = 32805, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33367, y = 32805, z = 14}, 11) + else + doRelocate(item:getPosition(),{x = 33399, y = 32801, z = 14}) + Game.sendMagicEffect({x = 33399, y = 32801, z = 14}, 11) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33368, y = 32763, z = 14},2567) and Game.isItemThere ({x = 33382, y = 32786, z = 14},2567) and Game.isItemThere ({x = 33305, y = 32734, z = 14},2567) and Game.isItemThere ({x = 33338, y = 32702, z = 14},2567) and Game.isItemThere ({x = 33320, y = 32682, z = 14},2567) and Game.isItemThere ({x = 33349, y = 32680, z = 14},2567) and Game.isItemThere ({x = 33358, y = 32701, z = 14},2567) and Game.isItemThere ({x = 33357, y = 32749, z = 14}, 2567) then + doRelocate(tileitem:getPosition(),{x = 33367, y = 32805, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33367, y = 32805, z = 14}, 11) + else + doRelocate(tileitem:getPosition(),{x = 33399, y = 32801, z = 14}) + Game.sendMagicEffect({x = 33399, y = 32801, z = 14}, 11) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/75.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/75.lua new file mode 100644 index 0000000..c092580 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/75.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3238) >= 1 and creature:getPlayer():getStorageValue(262) == 0 then + doRelocate(item:getPosition(),{x = 33349, y = 32830, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33349, y = 32830, z = 14}, 11) + creature:getPlayer():removeItem(3238, 1) + else + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33280, y = 32740, z = 10}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/76.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/76.lua new file mode 100644 index 0000000..4ccc018 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/76.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33280, y = 32740, z = 10}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/77.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/77.lua new file mode 100644 index 0000000..289286f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/77.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/78.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/78.lua new file mode 100644 index 0000000..9309afc --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/78.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33265, y = 32678, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33265, y = 32678, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33265, y = 32678, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33265, y = 32678, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/79.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/79.lua new file mode 100644 index 0000000..26d49ec --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/79.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33264, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32671, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33264, y = 32671, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32671, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/8.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/8.lua new file mode 100644 index 0000000..e12486f --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/8.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32267, y = 31899, z = 12},{x = 32267, y = 31911, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/80.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/80.lua new file mode 100644 index 0000000..b36163d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/80.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33267, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33267, y = 32685, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33267, y = 32685, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33267, y = 32685, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/81.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/81.lua new file mode 100644 index 0000000..fb10821 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/81.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33264, y = 32695, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32695, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33264, y = 32695, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32695, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/82.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/82.lua new file mode 100644 index 0000000..ed0b24b --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/82.lua @@ -0,0 +1,22 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:getPlayer():getStorageValue(271) > 0 and creature:getPlayer():getStorageValue(272) > 0 and creature:getPlayer():getStorageValue(273) > 0 and creature:getPlayer():getStorageValue(274) > 0 and creature:getPlayer():getStorageValue(275) > 0 and creature:getPlayer():getStorageValue(276) > 0 and creature:getPlayer():getStorageValue(277) > 0 then + doRelocate(item:getPosition(),{x = 33164, y = 32694, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33164, y = 32694, z = 14}, 11) + creature:getPlayer():setStorageValue(271,0) + creature:getPlayer():setStorageValue(272,0) + creature:getPlayer():setStorageValue(273,0) + creature:getPlayer():setStorageValue(274,0) + creature:getPlayer():setStorageValue(275,0) + creature:getPlayer():setStorageValue(276,0) + creature:getPlayer():setStorageValue(277,0) + else + doRelocate(item:getPosition(),{x = 33259, y = 32707, z = 13}) + Game.sendMagicEffect({x = 33259, y = 32707, z = 13}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33259, y = 32707, z = 13}) + Game.sendMagicEffect({x = 33259, y = 32707, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/83.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/83.lua new file mode 100644 index 0000000..9cdcff0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/83.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33260, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33260, y = 32700, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33260, y = 32700, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33260, y = 32700, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/84.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/84.lua new file mode 100644 index 0000000..255c3ed --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/84.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33231, y = 32705, z = 08}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/85.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/85.lua new file mode 100644 index 0000000..96dccb7 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/85.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33259, y = 32681, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33259, y = 32681, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33259, y = 32681, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33259, y = 32681, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/86.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/86.lua new file mode 100644 index 0000000..76bf750 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/86.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33257, y = 32691, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33257, y = 32691, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33257, y = 32691, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33257, y = 32691, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/87.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/87.lua new file mode 100644 index 0000000..47dd2a0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/87.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33256, y = 32673, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33256, y = 32673, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33256, y = 32673, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33256, y = 32673, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/88.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/88.lua new file mode 100644 index 0000000..2f54934 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/88.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32685, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32685, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/89.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/89.lua new file mode 100644 index 0000000..eb0bdaa --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/89.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32704, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32704, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32704, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32704, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/9.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/9.lua new file mode 100644 index 0000000..7861617 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/9.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32267, y = 31898, z = 12},{x = 32267, y = 31886, z = 12}) + end +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/90.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/90.lua new file mode 100644 index 0000000..0085008 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/90.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32698, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32698, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32698, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32698, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/91.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/91.lua new file mode 100644 index 0000000..8eeb2ae --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/91.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33247, y = 32679, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32679, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33247, y = 32679, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32679, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/92.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/92.lua new file mode 100644 index 0000000..9d6c5c9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/92.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33249, y = 32693, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33249, y = 32693, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33249, y = 32693, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33249, y = 32693, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/93.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/93.lua new file mode 100644 index 0000000..901b2a8 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/93.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33247, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32671, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33247, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32671, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/94.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/94.lua new file mode 100644 index 0000000..67ce67d --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/94.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33258, y = 32708, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33258, y = 32708, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33258, y = 32708, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33258, y = 32708, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/95.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/95.lua new file mode 100644 index 0000000..0b06497 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/95.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33211, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32700, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33211, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32700, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/96.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/96.lua new file mode 100644 index 0000000..3bad916 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/96.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33211, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33211, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32699, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/97.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/97.lua new file mode 100644 index 0000000..88dba55 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/97.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/98.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/98.lua new file mode 100644 index 0000000..542c9ba --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/98.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33205, y = 33002, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33205, y = 33002, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33205, y = 33002, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33205, y = 33002, z = 14}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/99.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/99.lua new file mode 100644 index 0000000..634d341 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/99.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/nostalrius/_.lua b/app/SabrehavenServer/data/movements/scripts/nostalrius/_.lua new file mode 100644 index 0000000..938ede0 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/nostalrius/_.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + +end + +function onStepOut(creature, item, position, fromPosition) + +end + +function onAddItem(item, tileitem, position) + +end + +function onRemoveItem(item, tileitem, position) + +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazirTiles.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazirTiles.lua new file mode 100644 index 0000000..81deb05 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazirTiles.lua @@ -0,0 +1,22 @@ +local config = { + [16772] = Position(32754, 32365, 15), + [16773] = Position(32725, 32381, 15), + [16774] = Position(32827, 32241, 12), + [50082] = Position(32745, 32394, 14), + [50083] = Position(32745, 32394, 14) +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local targetPosition = config[item:getMovementId()] + if not targetPosition then + return true + end + + player:teleportTo(targetPosition) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_final_room_teleport.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_final_room_teleport.lua new file mode 100644 index 0000000..b2edfdc --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_final_room_teleport.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32745, y = 32387, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32745, y = 32387, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32745, y = 32387, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32745, y = 32387, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport.lua new file mode 100644 index 0000000..a7fe7ef --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32767, y = 32366, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32767, y = 32366, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32767, y = 32366, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32767, y = 32366, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport_back.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport_back.lua new file mode 100644 index 0000000..ae1bf62 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/bazir_maze_lever_teleport_back.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32818, y = 32344, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32818, y = 32344, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32818, y = 32344, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32818, y = 32344, z = 13}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/checkThrones.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/checkThrones.lua new file mode 100644 index 0000000..9331d91 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/checkThrones.lua @@ -0,0 +1,22 @@ +local cStorages = { + [2090] = 17679, -- ThroneInfernatil + [2091] = 17680, -- ThroneTafariel + [2092] = 17681, -- ThroneVerminor + [2093] = 17682, -- ThroneApocalypse + [2094] = 17683, -- ThroneBazir + [2095] = 17684, -- ThroneAshfalor + [2096] = 17685 -- ThronePumin +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(cStorages[item:getMovementId()]) ~= 1 then + player:teleportTo(fromPosition) + player:say('You\'ve not absorbed energy from this throne.', TALKTYPE_MONSTER_SAY) + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/drawbridge.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/drawbridge.lua new file mode 100644 index 0000000..78ddae2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/drawbridge.lua @@ -0,0 +1,42 @@ +local bridgePosition = Position(32851, 32309, 11) +local relocatePosition = Position(32852, 32310, 11) +local dirtIds = {4797, 4799} + +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local tile = Tile(bridgePosition) + local lavaItem = tile:getItemById(727) + if lavaItem then + lavaItem:transform(1771) + + local dirtItem + for i = 1, #dirtIds do + dirtItem = tile:getItemById(dirtIds[i]) + if dirtItem then + dirtItem:remove() + end + end + end + return true +end + +function onStepOut(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local tile = Tile(bridgePosition) + local bridgeItem = tile:getItemById(1771) + if bridgeItem then + tile:relocateTo(relocatePosition) + bridgeItem:transform(727) + + for i = 1, #dirtIds do + Game.createItem(dirtIds[i], 1, bridgePosition) + end + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/final_room_teleports.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/final_room_teleports.lua new file mode 100644 index 0000000..3f278fe --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/final_room_teleports.lua @@ -0,0 +1,45 @@ +local config = { + [17660] = Position(32835, 32275, 9), -- exit + [17661] = Position(32856, 32353, 15), -- Verminor Enter + [17662] = Position(32848, 32230, 15), -- Infernatil Enter + [17663] = Position(32767, 32227, 15), -- Tafariel Enter + [17664] = Position(32856, 32278, 15), -- Apocalypse Enter + [17665] = Position(32733, 32297, 15), -- Pumin Enter + [17666] = Position(32806, 32328, 15), -- Bazir Enter + [17667] = Position(32835, 32262, 15), -- Ashfalor Enter + [17668] = Position(32830, 32252, 10), -- reward room Enter + [17669] = Position(32821, 32244, 12), -- Verminor Exit + [17670] = Position(32821, 32241, 12), -- Infernatil Exit + [17671] = Position(32821, 32238, 12), -- Tafariel Exit + [17672] = Position(32824, 32237, 12), -- Apocalypse Exit + [17673] = Position(32827, 32238, 12), -- Pumin Exit + [17674] = Position(32827, 32241, 12), -- Bazir Exit + [17675] = Position(32827, 32244, 12), -- Ashfalor Exit + [17676] = Position(32824, 32232, 12), -- reward room Exit + [17677] = Position(32829, 32243, 11), -- chest room enter + [17678] = Position(32826, 32248, 10) -- chest room exit +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17510) ~= 1 then + player:setStorageValue(17510, 1) + return true + end + + local targetPosition = config[item:getMovementId()] + doRelocate(item:getPosition(), targetPosition) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect(targetPosition, 11) +end + +function onAddItem(item, tileitem, position) + local targetPosition = config[tileitem:getMovementId()] + doRelocate(item:getPosition(),targetPosition) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect(targetPosition, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/holy_tible_tile.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/holy_tible_tile.lua new file mode 100644 index 0000000..eea4588 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/holy_tible_tile.lua @@ -0,0 +1,20 @@ +local destinations = { + [17640] = Position(32791, 32327, 10), + [17641] = Position(32791, 32331, 10) +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + if player:getItemCount(2836) < 1 then + player:teleportTo(fromPosition) + return true + end + + player:teleportTo(destinations[item:getMovementId()]) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/ladder.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/ladder.lua new file mode 100644 index 0000000..e0f7888 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/ladder.lua @@ -0,0 +1,29 @@ +local ladderPosition = Position(32854, 32321, 11) + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local ladderItem = Tile(ladderPosition):getItemById(5542) + if not ladderItem then + Game.createItem(5542, 1, ladderPosition) + player:say('You hear a rumbling from far away.', TALKTYPE_MONSTER_SAY, false, player) + end + return true +end + +function onStepOut(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local ladderItem = Tile(ladderPosition):getItemById(5542) + if ladderItem then + ladderItem:remove() + player:say('You hear a rumbling from far away.', TALKTYPE_MONSTER_SAY, false, player) + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/puminTeleport.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/puminTeleport.lua new file mode 100644 index 0000000..47925d2 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/puminTeleport.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if 1 == 1 then + -- TODO: if player:getStorageValue(Storage.PitsOfInferno.Pumin) > 8 then + player:teleportTo(Position(32786, 32308, 15)) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + else + player:teleportTo(fromPosition) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 'To enter Pumin\'s domain you must gain permission from the bureaucrats.') + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/pumin_throne_room_teleport_back.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/pumin_throne_room_teleport_back.lua new file mode 100644 index 0000000..6a091aa --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/pumin_throne_room_teleport_back.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + doRelocate(item:getPosition(),{x = 32732, y = 32266, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32732, y = 32266, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32732, y = 32266, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32732, y = 32266, z = 15}, 11) +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/secondTrap.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/secondTrap.lua new file mode 100644 index 0000000..c492f15 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/secondTrap.lua @@ -0,0 +1,22 @@ +local stonePosition = Position(32826, 32274, 11) + +function removeStone() + local stoneItem = Tile(stonePosition):getItemById(1772) + if stoneItem then + stoneItem:remove() + stonePosition:sendMagicEffect(CONST_ME_POFF) + end +end + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + player:teleportTo(Position(32826, 32273, 12)) + player:getPosition():sendMagicEffect(CONST_ME_EXPLOSIONAREA) + Game.createItem(1772, 1, stonePosition) + addEvent(removeStone, 10 * 1000) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/thrones.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/thrones.lua new file mode 100644 index 0000000..277e3c1 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/thrones.lua @@ -0,0 +1,32 @@ +local config = { + [2080] = {storage = 17679, text = 'You have touched Infernatil\'s throne and absorbed some of his spirit.', effect = CONST_ME_FIREAREA, toPosition = Position(32909, 32211, 15)}, + [2081] = {storage = 17680, text = 'You have touched Tafariel\'s throne and absorbed some of his spirit.', effect = CONST_ME_MORTAREA, toPosition = Position(32761, 32243, 15)}, + [2082] = {storage = 17681, text = 'You have touched Verminor\'s throne and absorbed some of his spirit.', effect = CONST_ME_POISONAREA, toPosition = Position(32840, 32327, 15)}, + [2083] = {storage = 17682, text = 'You have touched Apocalypse\'s throne and absorbed some of his spirit.', effect = CONST_ME_EXPLOSIONAREA, toPosition = Position(32875, 32267, 15)}, + [2084] = {storage = 17683, text = 'You have touched Bazir\'s throne and absorbed some of his spirit.', effect = CONST_ME_MAGIC_GREEN, toPosition = Position(32745, 32385, 15)}, + [2085] = {storage = 17684, text = 'You have touched Ashfalor\'s throne and absorbed some of his spirit.', effect = CONST_ME_FIREAREA, toPosition = Position(32839, 32310, 15)}, + [2086] = {storage = 17685, text = 'You have touched Pumin\'s throne and absorbed some of his spirit.', effect = CONST_ME_MORTAREA, toPosition = Position(32785, 32279, 15)} +} + +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local throne = config[item:getMovementId()] + if not throne then + return true + end + + local cStorage = throne.storage + if creature:getStorageValue(cStorage) ~= 1 then + creature:setStorageValue(cStorage, 1) + creature:getPosition():sendMagicEffect(throne.effect) + creature:say(throne.text, TALKTYPE_MONSTER_SAY) + else + creature:teleportTo(throne.toPosition) + creature:getPosition():sendMagicEffect(CONST_ME_MORTAREA) + creature:say('Begone!', TALKTYPE_MONSTER_SAY) + end + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/tileTeleports.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/tileTeleports.lua new file mode 100644 index 0000000..bce7bb9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/tileTeleports.lua @@ -0,0 +1,42 @@ +local config = { + [28810] = Position(32838, 32304, 9), + [28811] = Position(32839, 32320, 9), + [28812] = Position(32844, 32310, 9), + [28813] = Position(32847, 32307, 9), + [28814] = Position(32856, 32306, 9), + [28815] = Position(32827, 32308, 9), + [28816] = Position(32840, 32317, 9), + [28817] = Position(32855, 32296, 9), + [28818] = Position(32857, 32307, 9), + [28819] = Position(32856, 32289, 9), + [28820] = Position(32843, 32313, 9), + [28821] = Position(32861, 32320, 9), + [28822] = Position(32841, 32323, 9), + [28823] = Position(32847, 32287, 9), + [28824] = Position(32854, 32323, 9), + [28825] = Position(32855, 32304, 9), + [28826] = Position(32841, 32323, 9), + [28827] = Position(32861, 32317, 9), + [28828] = Position(32827, 32314, 9), + [28829] = Position(32858, 32296, 9), + [28830] = Position(32861, 32301, 9), + [28831] = Position(32855, 32321, 9), + [28832] = Position(32855, 32320, 9), + [28833] = Position(32855, 32318, 9), + [28834] = Position(32855, 32319, 9) +} + +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + local targetPosition = config[item:getMovementId()] + if not targetPosition then + return true + end + + player:teleportTo(targetPosition) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/trap.lua b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/trap.lua new file mode 100644 index 0000000..26ca822 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/pits_of_inferno/trap.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + position.z = position.z + 1 + player:teleportTo(position) + position:sendMagicEffect(CONST_ME_EXPLOSIONAREA) + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/serpentine_tower/final_tile.lua b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/final_tile.lua new file mode 100644 index 0000000..a64af08 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/final_tile.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getOutfit().lookType ~= 17 then + position.y = position.y + 1 + player:teleportTo(position) + Game.sendMagicEffect(player:getPosition(), CONST_ME_ENERGYHIT) + player:addHealth(-20) + return true + end + + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/serpentine_tower/humble_one_first.lua b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/humble_one_first.lua new file mode 100644 index 0000000..f5b5cb9 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/humble_one_first.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(17605) ~= 1 and Game.isItemThere({x = 33145, y = 32870, z = 8}, 3246) then + position.y = position.y - 5 + player:teleportTo(position) + Game.sendMagicEffect(player:getPosition(), CONST_ME_HITAREA) + Position(33145, 32870, 8):sendMonsterSay("Only the humble can touch the boots!") + return true + end + + return true +end diff --git a/app/SabrehavenServer/data/movements/scripts/serpentine_tower/take_waterwalking_boots.lua b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/take_waterwalking_boots.lua new file mode 100644 index 0000000..d96e0a8 --- /dev/null +++ b/app/SabrehavenServer/data/movements/scripts/serpentine_tower/take_waterwalking_boots.lua @@ -0,0 +1,5 @@ +function onRemoveItem(item, tileitem, position) + if item:getId() == 3246 then + Position(33145, 32870, 8):sendMonsterSay("Ancient Eye: Be Greeted The Humble One!") + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/admiralwyrmslicer.npc b/app/SabrehavenServer/data/npc/admiralwyrmslicer.npc new file mode 100644 index 0000000..b3f1c3c --- /dev/null +++ b/app/SabrehavenServer/data/npc/admiralwyrmslicer.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Admiral Wyrmslicer" +Outfit = (132,19-113-112-114-0) +Home = [32396,32821,3] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. Have you anything to report?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"report" -> "I heard enough of the gossip and superstitions. I am fed up with that nonsense!" +"gossip" -> "Spare me the nonsense that people made up like sinister cultists, quara infiltrators, pirate hideouts voodoo curses and whatnot! ...", + "I've heared it all and it is completely rubbish and a waste of my time! We have everything firmly under control. And to ensure this I warn you not to talk to the townspeople about such issues!" +"cultistis" -> "The talks about the cult are getting tiresome. There is no such thing. It is only another superstition here." +"quara" -> "The quara are treacherous foes that don't fight with common tactics. This and the security of the seas, to what they can withdraw, make it difficult to handle them properly." +"pirate" -> "Pirates are vermin and we will wipe them from the map wherever they raise their ugly heads." +"voodoo" -> "Superstition! We have real sorcerers and none of them uses such prankish trickery." +"job" -> "I am admiral of the Thaian fleet and commander of this fort and the local military." +"Wyrmslicer" -> "My family is respected for many deeds of heroism. There is a well-known dragon slayer in our bloodline which earned us that name of honour." +"raymond striker" -> "Captain Striker is one of the more cunning pirates. Up to now, he managed to escape but sooner or later he will run out of luck." +"chondur" -> "I heard about that man. For some people he is some kind of father figure. If he should ever misuse that, we will take a closer look at him." +"thais" -> "Thais is of course the crown of our kingdom and the place where our hearts will belong to forever." +"venore" -> "The efforts Venore put into this city are admirable. It's only fair that they gain a certain profit from that." +"king" -> "LONG LIVE THE KING!" +"sugar" -> "We need the sugar. We will ensure with everything that is at our command that the supply of sugar is kept up." +"rum" -> "Rum can be just as good for morale as it can be bad. It has to be administered with care." +"governor" -> "The governor is a fine man and I do my best to support him in all his efforts." +"eleonore" -> "This young lady truly graces Liberty Bay with her presence. It is always a pleasure to meet her." +"liberty bay" -> "It might take some time but with our guidance the city will flourish and prosper." +"carlin" -> "Sooner or later Carlin will be nothing more than a footnote in history. Look at the size of our kingdom and compare it with Carlin ...", + "One day they will accept that it is better to rejoin the kingdom and to combine our efforts and resources for the good of all." +"charlotta" -> "I believe she is this old healer in town. I don't think she is happy with our presence here but she did nothing yet that would justify to arrest her." +"Isolde" -> "Competent and efficient. I give nothing about ugly rumours and she never ever betrayed the trust put into her." +"Theodore Loveless" -> "Mr. Loveless has a sharp and quick mind. If he were no trader, he would have made a great officer." +"Tristan" -> "A fine knight indeed. I am very pleased with his efforts." +"ferumbras" -> "I am not familiar with witchcraft and sorcery. Perhaps you should ask someone else." +} diff --git a/app/SabrehavenServer/data/npc/adrenius.npc b/app/SabrehavenServer/data/npc/adrenius.npc new file mode 100644 index 0000000..0442f21 --- /dev/null +++ b/app/SabrehavenServer/data/npc/adrenius.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# adrenius.npc: Datenbank für den Wüstenpriester Adrenius (Desert) + +Name = "Adrenius" +Outfit = (9,0-0-0-0-0) +Home = [32660,32112,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see, I am talking to someone else!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Leave me, I am used to it anyways..." + +"bye" -> "Good bye.", Idle +"name" -> "My name is Adrenius." +"job" -> "I'm a priest of Fafnar." +"fafnar" -> "Fafnar is the stronger one of the two suns above our world." +"thais" -> "Yyyyess. Yes, it's the capital city of Tibia I think." +"carlin" -> "Carlin? Don't you mean Thais?" +"king" -> "Who needs a king? I don't." +"weapon" -> "Who needs weapons? I never had and i never will have weapons - what for?" +"help" -> "Help? Help? Nothing more? Don't we all demand some help?" +"time" -> "Time? What is time? A word? A thing? An object?" +"sword" -> "Swords? Don't you have something else to do?" +"desert" -> "Sand, sand and again sand. Sand all over. Yes, I'd say: it's truly a desert!" +"excalibug" -> "What's that? You start annoying me." +"fight" -> "Leave me alone. I don't want to fight." +"god" -> "Fafnar is the greatest among the gods." +"way" -> "Way? Which way? I forgot where most ways go to... excuse me." +"door" -> "Who needs doors? Free your mind!" +"secret" -> "Secrets ... What do you mean?" +"treasure" -> "Treasures? What is a treasure for you?" +"book" -> "Read books, it increases your intelligence and, furthermore, it's a great source of inspiration!" +"gharonk" -> "Hmmmm... I don't know much about it." +"offer" -> "I can offer you religion and mysticism." +"library" -> "I heard of the library, but I never was very interested in it." +"netlios" -> "This fool! His book is nothing but a hoax! At least I believe that. Or did you find an answer for my questions?", Topic=1 + +Topic=1,"yes" -> Price=500, "By the way, I would like a donation for my temple. Are %P gold ok?", Topic=2 +Topic=1,"no" -> "Oh. So once again I am proved right." +Topic=1 -> "You can't even say 'yes' or 'no'. You are not worth talking to me!", Idle + +Topic=2,"yes",CountMoney>=Price -> DeleteMoney, "Thank you very much. Now, name me the first person in alphabetical order, his age, his fate, and how long he was on his journeys!", Topic=4 +Topic=2,"yes" -> "You want to fool me? May Fafnar burn your soul!", EffectMe(14), Burning(50,10), Idle +Topic=2 -> "Then I don't want to talk to you.", Idle + +Topic=4,"anaso","41","mother-bear","117" -> "Hmmm, maybe. What can you tell me about the second 'adventurer'?", Topic=5 +Topic=4 -> "No, sorry, that doesn't sound correct to me. Maybe you should reconsider your words one more time..." + +Topic=5,"elaeus","39","dragon","100" -> "Yes, that might be true. What did you find out about the third man?", Topic=6 +Topic=5 -> "No, no, no! Think about it, that simply can't be true!" + +Topic=6,"gadinius","42","fire","83" -> "Correct again! Hmmmm... I doubt you know anything about the fourth person!", Topic=7 +Topic=6 -> "Hmmmm... well, no. That is not true, it does not fit to the data provided by the books." + +Topic=7,"heso","40","troll","66" -> "Yes! Really, how did you figure that out? I bet, you don't know anything about the last adventurer!", Topic=8 +Topic=7 -> "No, sorry. Incorrect..." + +Topic=8,"hestus","38","poison","134" -> "That's right! Why didn't I see it? It's obvious, Netlios was right, and his stories are great! Wait, I'll give you something!", Data=4023, Create(2969) +Topic=8 -> "Well, and again it was shown: I am right and Netlios is wrong!" +} diff --git a/app/SabrehavenServer/data/npc/ahmet.npc b/app/SabrehavenServer/data/npc/ahmet.npc new file mode 100644 index 0000000..d38a884 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ahmet.npc @@ -0,0 +1,139 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ahmet.npc: Datenbank für den pyramidenhändler Ahmet + +Name = "Ahmet" +Outfit = (130,38-40-39-114-0) +Home = [33126,32810,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell equipment of all sorts." +"name" -> "I am the mourned Ahmet." +"time" -> "It were foolish of me to tell you the time, because then you won't buy a watch." +"temple" -> "The temple is a school for us mourned mortals. The teachings of the temple help us to find our way through our mortal days." +"pharaoh" -> "Blessed be the pharaoh. He is our saviour. I hope that one day I will be chosen." +"arkhothep" -> * +"oldpharaoh" -> "The foolish old pharaoh withheld knowledge and power from his son, knowing that he would surpass him in every aspects. But his son granted him the chance to ascend." +"ashmunrah" -> * +"scarab" -> "The eternal burrowers are the keepers of all the secrets their kind has unearthed in countless aeons." +"chosen" -> "Only the most worthy and pious are chosen to join the armies of the pharaoh. In undeath they follow the path of ascension." + +"tibia" -> "The world is nothing but a sigil of death, a monument of decay. We have to attune to death to become one with the world." + +"carlin" -> "The vain cities of the Tibian continent think they are at the centre of the universe. Little do they know about the wisdom of the pharaoh." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "We rarely see a traveler of the small folk here." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are a rare sight in our lands." +"elves" -> * +"elfes" -> * +"darama" -> "Life here is harsh, but only this way can we deny the temptations that might damage our Rah and our Uthun to our traitorous flesh." +"darashia" -> "The foolishness of their ways will eventually spell their doom." +"daraman" -> "He was close to the truth, but he lacked the wisdom and vision of our pharaoh." +"ankrahmun" -> "Our city will endure the sands of the desert and the grinding teeth of time." + +"pharaoh" -> "Our pharaoh holds the key to our ascension. Praised be our pharaoh." +"mortality" -> "Mortality is our curse. Mourned shall we be." +"false", "gods" -> "The great traitors are trying to doom us." + +"ascension" -> "Godhood is at our disposal if only we throw of the shackles of mortal flesh." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is what we are." +"Akh" -> "Your cursed bodys are called the Akh. As long as we are alive the Akh makes us weak and vulnerable." + +"undead" -> "Undeath is the path to ascension which the chosen may follow." +"undeath" -> * +"Rah" -> "The Rah could be called our soul." +"uthun" -> "The Uthun is that what we learn and remember." +"mourn" -> "We are mortals and thus miserable creatures that are to be mourned." + +"arena" -> "The arena is east of here." +"palace" -> "The palace is the home of our beloved pharaoh." + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2863, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2871, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=40, Data=1, "Do you want to buy a water hose for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2863, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2871, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Amount=%1, Price=40*%1, Data=1, "Do you want to buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "I hope it will serve you well, my prized customer.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "My twelve starving children don't allow me to sell it for less, o grandmaster of haggling." +Topic=1 -> "What a pity." + +Topic=3,"yes",Count(Type)>=Amount -> "I hardly can explain to my wife why I gave you that much money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you own none." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=3 -> "Maybe next time." + +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, lightsources and watches. Of course, I sell fishing rods and six-packs of worms, too." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"light" -> "I sell torches, candlesticks, candelabra, and oil." + +} diff --git a/app/SabrehavenServer/data/npc/ajax.npc b/app/SabrehavenServer/data/npc/ajax.npc new file mode 100644 index 0000000..701efed --- /dev/null +++ b/app/SabrehavenServer/data/npc/ajax.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Ajax" +Outfit = (143,78-101-120-94-1) +Home = [32417,31583,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",QuestValue(17532)=2,ExpiringQuestValue(17533)<0,! -> "You back. You know, you right. Brother is right. Fist not always good. Tell him that!", SetQuestValue(17532,3), Idle +ADDRESS,"hi$",QuestValue(17532)=2,ExpiringQuestValue(17533)<0,! -> * +ADDRESS,"hello$",ExpiringQuestValue(17533)>0,! -> "Head aches. GO AWAY!", SetExpiringQuestValue(17533, 3600000), Idle +ADDRESS,"hi$",ExpiringQuestValue(17533)>0,! -> * +ADDRESS,"hello$",! -> "Whatcha do in my place?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + +"fight" -> "You. Weak." +"job" -> "No time for such a stupid thing." +"name" -> "Ajax." + +"MINE",QuestValue(17532)=1 -> "YOURS? WHAT IS YOURS! NOTHING IS YOURS! IS MINE! GO AWAY, YES?!", EffectOpp(16), Burning(50,10), Topic=1 +Topic=1,"no" -> "YOU STUPID! STUBBORN! I KILL YOU! WILL LEAVE NOW?!", Topic=2 +Topic=1 -> "BYE!!!", Idle +Topic=2,"no" -> "ARRRRRRRRRR! YOU ME DRIVE MAD! HOW I MAKE YOU GO??", Topic=3 +Topic=2 -> "ARRRRRRRRRR!!!", Idle +Topic=3,"no" -> "I GIVE YOU NO!", Topic=4 +Topic=3 -> "ARRRRRRRRRR!!!", Idle +Topic=4,"say","please" -> "Please? What you mean please? Like I say please you say bye? Please?", Topic=5 +Topic=4 -> "ARRRRRRRRRR!!!", Idle +Topic=5,"yes" -> "Oh. Easy. Okay. Please is good. Now don't say anything. Head aches.", SetQuestValue(17532,2), SetExpiringQuestValue(17533, 3600000), Idle +Topic=5 -> "ARRRRRRRRRR!!!", Idle + +"Gelagos",QuestValue(17532)=5 -> "Annoying kid. Bro hates him, but talking no help. Bro needs fighting spirit!" +"fighting","spirit",QuestValue(17532)=5 -> "If you want to help bro, bring him fighting spirit. Magic fighting spirit. Ask Djinn." + +"present",QuestValue(17532)=12 -> "Bron gave me present. Ugly, but nice from him. Me want to give present too. You help me?", Topic=6 +Topic=6,"yes" -> "Good! Me make shiny weapon. If you help me, I make one for you too. Like axe I wear. I need stuff. Listen. ...", + "Me need 100 iron ore. Then need crude iron. Then after that 50 behemoth fangs. And 50 lizard leather. You understand?", + "Help me yes or no?", Topic=7 +Topic=6 -> "Maybe later." +Topic=7,"yes" -> "Good. You get 100 iron ore first. Come back.", SetQuestValue(17532,13) +Topic=7 -> "Maybe later." + +"iron","ore",QuestValue(17532)=13 -> Type=5880, Amount=100, "You bring 100 iron ore?", Topic=8 +Topic=8,"yes",Count(Type)>=Amount -> "Good! Now bring crude iron.", Delete(Type), SetQuestValue(17532,14) +Topic=8,"yes" -> "You do not have that many." +Topic=8 -> "Maybe another time." + +"crude","iron",QuestValue(17532)=14 -> Type=5892, Amount=1, "You bring crude iron?", Topic=9 +Topic=9,"yes",Count(Type)>=Amount -> "Good! Now bring 50 behemoth fangs.", Delete(Type), SetQuestValue(17532,14) +Topic=9,"yes" -> "You do not have it." +Topic=9 -> "Maybe another time." + +"behemoth","fang",QuestValue(17532)=14 -> Type=5893, Amount=50, "You bring 50 behemoth fangs?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "Good! Now bring 50 lizard leather.", Delete(Type), SetQuestValue(17532,15) +Topic=10,"yes" -> "You do not have that many." +Topic=10 -> "Maybe another time." + +"lizard","leather",QuestValue(17532)=15 -> Type=5876, Amount=50, "You bring 50 lizard leather?", Topic=11 +Topic=11,"yes",Count(Type)>=Amount -> "Ah! All stuff there. I will start making axes now. Come later and ask me for axe.", Delete(Type), SetQuestValue(17532,16), SetExpiringQuestValue(17534, 7200000) +Topic=11,"yes" -> "You do not have that many." +Topic=11 -> "Maybe another time." + +"addon",ExpiringQuestValue(17534)>0 -> "Wait! I making axes now. Come later, okey?" +"axe",ExpiringQuestValue(17534)>0 -> * + +"addon",ExpiringQuestValue(17534)<0,QuestValue(17532)=16 -> "Axe is done! For you. Take. Wear like me.", SetQuestValue(17532,17), AddOutfitAddon(147,1), AddOutfitAddon(143,1), EffectOpp(13) +"axe",ExpiringQuestValue(17534)<0,QuestValue(17532)=16 -> * + +"addon",QuestValue(17532)=17 -> "Nice axe. Nice axe." +"axe",QuestValue(17532)=17 -> * + +} diff --git a/app/SabrehavenServer/data/npc/albert.npc b/app/SabrehavenServer/data/npc/albert.npc new file mode 100644 index 0000000..0b0ed4a --- /dev/null +++ b/app/SabrehavenServer/data/npc/albert.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# albert.npc: Datenbank für den Heiler Albert + +Name = "Albert" +Outfit = (130,78-0-49-95-0) +Home = [33312,31762,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome in my humble hut, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "If you are heavily wounded or poisoned, feel free to return for a healing." + +"bye" -> "Good bye, %N!", Idle +"job" -> "I am a healer." +"name" -> "My Name is Albert Fibulanian." +"tibia" -> "Tibia is a world big enough for everyone. I wish people would realize that." +"thais" -> "The sinful city of Thais is a monument of corruption and murder. I am glad I left for Edron and thank the gods every day for this isle." +"edron" -> * +"god" -> "The gods of good take care of us." +"king" -> "The king does much to enhance the life of his people, but he could do more." +"tibianus" -> * +"army" -> "I dream of times which see no need for armies or warriors." +"banor" -> * +"ferumbras" -> "The fallen one is a perfect example where evil leads us to." +"excalibug" -> "It's only another instrument of pain and destruction." +"news" -> "I have only news about weather, taxes, and harvests. I heared nothing that might interest a traveller like you." +"daniel" -> "I healed his wounds, but nothing can heal his soul after the betrayal of some of his knightly brethren." +"kaine" -> "Another victim of his own ambitions. I mourn for his soul." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +"time" -> "Now, it is %T." +} diff --git a/app/SabrehavenServer/data/npc/aldee.npc b/app/SabrehavenServer/data/npc/aldee.npc new file mode 100644 index 0000000..63978a6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/aldee.npc @@ -0,0 +1,175 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aldee.npc: Datenbank für den Händler Al Dee (Newbie) + +Name = "Al Dee" +Outfit = (128,97-77-87-115-0) +Home = [32063,32180,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'll be with you in a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant. What can I do for you?" +"name" -> "My name is Al Dee, but you can call me Al. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is save from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only I dared to take the risk, and a risk it was!" +"dallheim" -> "Some call him a hero." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich man!" +"thais" -> "Thais is a crowded town." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"club" -> "I don't buy this garbage!" +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"pick" -> Type=3462, Amount=1, "Picks are hard to come by. I trade them only for high quality small axes. Do you want to trade?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Splendid! Here take your pickaxe.", Delete(Type), Create(3456) +Topic=3,"yes" -> "Sorry, I am looking for a SMALL axe." +Topic=3,"no" -> "Well, then not." +Topic=3 -> * + + +} diff --git a/app/SabrehavenServer/data/npc/aldo.npc b/app/SabrehavenServer/data/npc/aldo.npc new file mode 100644 index 0000000..ea2dd33 --- /dev/null +++ b/app/SabrehavenServer/data/npc/aldo.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aldo.npc: Datenbank für den Schuhverkäufer Aldo + +Name = "Aldo" +Outfit = (128,40-37-116-76-0) +Home = [32953,32110,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Just great, another ... 'customer'. Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Yes, yes, I can only talk to one after the other! You will have to wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, get lost." + +"bye" -> "That's music in my ears.", Idle +"name" -> "I'm Aldo. No one calls me 'lucky Aldo' though, guess why!" +"job" -> "I am a salesman, I sell headgear ... uhm ... oh well, and shoes." +"time" -> "Is it time for lunch already? Hey, stop making fun of me!" +"king" -> "One day I will sell the king a pair of shoes made by me and will get out of that stinky hole I live in and my family will never find me. HE, HE!" +"tibianus" -> * +"army" -> "So many feet ... so many ... a nightmare!" +"ferumbras" -> "Can't be worse than my wife." +"wife" -> "Leave me alone with her while I am working at least. My only pleasure around here!" +"excalibug" -> "I have other stuff to worry about, like paying my bills." +"bill" -> "Yes, I have to pay o lot of bills, and some georges, and a john, and several steves." +"thais" -> "I will never in my life make it there." +"tibia" -> "I doubt I will ever see much of it. It's like i am cursed to haunt this site here for the rest of my life." +"carlin" -> "A city ruled by women!? Could anything be worse?" +"amazon" -> "I heard that chicks wear some revealing pieces of armor!" +"news" -> "Hey, I am a man. Look for some women to share gossip." +"rumour" -> * +"rumor" -> * +"hugo" -> "My boss, an evil slaver of good people like me." + +"offer" -> "I am damned to sell headgear, trousers, and shoes for the rest of my life." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"headgear" -> "We have leather helmets and studded helmets." +"shoes" -> " We sell leather boots and sandals." +"trouser" -> "We offer leather legs and studded legs." + +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"sandals" -> Type=3551, Amount=1, Price=2000, "Do you want to buy one of my wonderful sandals for %P gold?", Topic=1 +"leather","boot" -> Type=3552, Amount=1, Price=2, "Do you want to buy one of my wonderful leather boots for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"studded","legs" -> Type=3362, Amount=1, Price=60, "Do you want to buy studded legs for %P gold?", Topic=1 + +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"sandals" -> Type=3551, Amount=%1, Price=2000*%1, "Do you want to buy %A of my wonderful sandals for %P gold?", Topic=1 +%1,1<%1,"leather","boot" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to buy %A of my wonderful leather boots for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"studded","legs" -> Type=3362, Amount=%1, Price=60*%1, "Do you want to buy %A studded legs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here. I hope that's it now.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "At last, someone poorer than me." +Topic=1,"no" -> "Good decision!" + +"soft","boots",ClientVersion>=790 -> Type=6530, Amount=1, Price=10000, "You want me to repair your soft boots? I'm not sure if I want to touch this stinking mess. 10000 gold, deal?", Topic=2 +"repair",ClientVersion>=790 -> * +Topic=2,"yes",CountMoney>=Price,Count(Type)>=Amount -> "Here. I hope that's it now.", DeleteMoney, Delete(Type), Create(6529) +Topic=2,"yes" -> "At last, someone poorer than me." +Topic=2,"no" -> "Good decision!" + +} diff --git a/app/SabrehavenServer/data/npc/alesar.npc b/app/SabrehavenServer/data/npc/alesar.npc new file mode 100644 index 0000000..58d1fa0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/alesar.npc @@ -0,0 +1,194 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alesar.npc: Datenbank für den Djinnschmied Alesar (Waffen und Rüstungen, Efreet) + +Name = "Alesar" +Outfit = (80,0-0-0-0-0) +Home = [33048,32621,5] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "What do you want from me, %N?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "I am already talking to one of you creeps. So shut up until it is your turn, %N.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Humans..." + +"bye" -> "Finally.", Idle +"farewell" -> * +"name" -> "My name is none of your business." +"alesar" -> "That is my name. So what!" +"job" -> "What does it look like, fool? I'm a smith! But I won't sell you anything until Malor orders me otherwise." +"trade" -> "I won't sell you anything, human. Malor doesn't want me to trade with strangers." +"permission" -> * + +"daraman" -> "Don't you dare mention Daraman in my presence, human. I am through with his insidious lies and through with your accursed race!" +"haroun" -> "Haroun? What? You know ... where do you know that name from? Did he send you?", Topic=1 +Topic=1,"yes" -> "Treacherous liar! You would not be here if you had really talked to him. Get out of my sight or I will test my latest sabre on you!", Idle +Topic=1 -> " Of course not. How could you ... Well, at least you are honest, human. I appreciate that." + +"human" -> "I used to have illusions about you humans. I thought humans were good, noble creatures. ...", + "I thought djinns and humans shared a destiny, and that we could live side by side peacefully. ...", + "But now I have learnt my lesson. I have had the privilege to look deep into the human mind, much deeper than most of my brothers. ...", + "And guess what! I did not like what I see. You are nothing but a race of cruel, perfidious bloodsuckers who hide their wickedness behind a thin layer of civilisation and so-called humanity. ...", + "Your race is a blemish on the face of Tibia. The sooner it is gone the better!" +"djinn" -> "One day we will teach your race a lesson it will never forget." +"efreet" -> "The efreet are those djinn who never fell for Daraman's insidious propaganda. I wish I would have been as smart from the start. ...", + "But errors can be corrected!" +"marid" -> "Those among my brothers and sisters who still do not see the truth call themselves the Marid. I used to be one of them, but I left them when the truth dawned upon me. ...", + "Now I follow Malor, although I would never fight against my kind." +"gabel" -> "Gabel is a kind-hearted, honest djinn. I would hate to see him die just because he believes in Daraman's lies. ...", + "After all, I believed them myself. " +"king" -> "We need a strong king to unite us in our struggle against the humans." +"malor" -> "Malor is overambitious and unnecessarily cruel, but he is the only djinn who could unite our race, so I follow him. ...", + "The truth is I despise him, but that is of no importance as long as you humans will be exterminated." +"mal'ouquah" -> "I do not like this place. But then it does not really matter where I am. I have a forge and I don't see any humans. That's all I need. ...", + "Of course, now you are here. Doesn't help me to feel myself at home here." +"ashta'daramai",QuestValue(287)=0 -> "I used to live in Ashta'daramai. That was before I realised the extent of my blindness." +"ashta'daramai",QuestValue(287)>0 -> "Ashta'daramai is Gabel's fortress which lies to the north. ...", + "Of course you cannot enter it through the front door. ...", + "But from my time there, I know that there is also an unguarded back door in the north of the fortress." +"zathroth" -> "Legend has it that Zathroth was trying to make us beings of unalloyed evil, but he found us to be impure, so he abandoned us and started over. ...", + "It is not flattering to think we are nothing but examples of bad workmanship, but I see it from a different perspective: Since our god left us on our own it is up to ourselves to forge our destiny. ...", + "One day Zathroth will look at us in amazement." +"tibia" -> "One day we djinn will rid this world of evil." +"darashia" -> "I don't care about human cities. If I had my way, they would all be burnt to down today." +"edron" -> * +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> * + +"pharaoh" -> "The pharaoh in Ankrahmun is a dangerous fool. Just your typical human, in other words." +"palace" -> "So?" +"ascension" -> "What are you talking about? More human pseudo-philosophical flapdoodle?" +"rah" -> * +"uthun" -> * +"akh" -> * +"scarab" -> "I like them. They are peaceful, but if they are provoked they fight ferociously. And they are know to eat humans!" +"kha'zeel" -> "These mountains are our refuge from those pesky humans. Too bad there are always some who come up here anyway. You, for example." +"kha'labal" -> "The desert Kha'labal was once a beautiful land, but it was devastated in the course of the war. Damn humans! This is all your fault!" +"melchior" -> "I remember him. He was a greedy, double-dealing hyena. As far as I know his bleached bones are now lying somewhere in the Kha'labal." +"djema" -> "Djema? Well - I suppose she is the only human I still like. But she has been brought up by djinns. Who knows - perhaps humans can learn." +"baa'leal" -> "Baa'leal is Malor's lieutenant. Unflinchingly loyal, but not quite as clever as he thinks he is." +"bo'ques" -> "I miss Bo'ques' cooking, but not his pompous airs and graces." +"fa'hradin" -> "Fa'hradin, that old cynic is way too smart to believe in Daraman's lies. He should reconsider his loyalties." + +"wares" -> "I sell and buy weapons, armors, helmets, legs, and shields." +"offer" -> * +"goods" -> * +"smith" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"weapon" -> "At this time I'm only selling ice rapiers and serpent swords. But I would buy scimitars, giant swords, serpent swords, poison daggers, knight axes, dragon hammers and skull staffs from you." +"shield" -> "I am selling only ancient shields. But I buy tower shields, black shields, ancient shields and vampire shields." +"armor" -> "I am buying and selling dark armors. But I would also buy a knight armor from you." +"helmet" -> "I am buying and selling dark helmets. Furthermore I'm buying warrior helmets, strange helmets and mystic turbans." +"trousers" -> "At this time I'm only buying knight legs." +"legs" -> * + +"ice","rapier" -> Type=3284, Amount=1, Price=5000, "Do you want to buy an ice rapier for %P gold?", Topic=10 +"serpent","sword" -> Type=3297, Amount=1, Price=6000, "Do you want to buy a serpent sword for %P gold?", Topic=10 +"ancient","shield" -> Type=3432, Amount=1, Price=5000, "Do you want to buy an ancient shield for %P gold?", Topic=10 +"dark","armor" -> Type=3383, Amount=1, Price=1500, "Do you want to buy a dark armor for %P gold?", Topic=10 +"dark","helmet" -> Type=3384, Amount=1, Price=1000, "Do you want to buy a dark helmet for %P gold?", Topic=10 + +%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=5000*%1, "Do you want to buy %A ice rapiers for %P gold?", Topic=10 +%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=6000*%1, "Do you want to buy %A serpent swords for %P gold?", Topic=10 +%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=5000*%1, "Do you want to buy %A ancient shields for %P gold?", Topic=10 +%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=1500*%1, "Do you want to buy %A dark armors for %P gold?", Topic=10 +%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=1000*%1, "Do you want to buy %A dark helmets for %P gold?", Topic=10 + +"sell","serpent","sword" -> Type=3297, Amount=1, Price=900, "Do you want to sell a serpent sword for %P gold?", Topic=11 +"sell","dragon","hammer" -> Type=3322, Amount=1, Price=2000, "Do you want to sell a dragon hammer for %P gold?", Topic=11 +"sell","giant","sword" -> Type=3281, Amount=1, Price=17000, "Do you want to sell a giant sword for %P gold?", Topic=11 +"sell","poison","dagger" -> Type=3299, Amount=1, Price=50, "Do you want to sell a poison dagger for %P gold?", Topic=11 +"sell","scimitar" -> Type=3307, Amount=1, Price=150, "Do you want to sell a scimitar for %P gold?", Topic=11 +"sell","skull","staff" -> Type=3324, Amount=1, Price=6000, "Do you want to sell a skull staff for %P gold?", Topic=11 +"sell","knight","axe" -> Type=3318, Amount=1, Price=2000, "Do you want to sell a knight axe for %P gold?", Topic=11 + +"sell","tower","shield" -> Type=3428, Amount=1, Price=8000, "Do you want to sell a tower shield for %P gold?", Topic=11 +"sell","black","shield" -> Type=3429, Amount=1, Price=800, "Do you want to sell a black shield for %P gold?", Topic=11 +"sell","ancient","shield" -> Type=3432, Amount=1, Price=900, "Do you want to sell an ancient shield for %P gold?", Topic=11 +"sell","vampire","shield" -> Type=3434, Amount=1, Price=15000, "Do you want to sell a vampire shield for %P gold?", Topic=11 + +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=5000, "Do you want to sell a warrior helmet for %P gold?", Topic=11 +"sell","knight","armor" -> Type=3370, Amount=1, Price=5000, "Do you want to sell a knight armor for %P gold?", Topic=11 +"sell","knight","legs" -> Type=3371, Amount=1, Price=5000, "Do you want to sell a pair of knight legs for %P gold?", Topic=11 +"sell","strange","helmet" -> Type=3373, Amount=1, Price=500, "Do you want to sell a strange helmet for %P gold?", Topic=11 +"sell","dark","armor" -> Type=3383, Amount=1, Price=400, "Do you want to sell a dark armor for %P gold?", Topic=11 +"sell","dark","helmet" -> Type=3384, Amount=1, Price=250, "Do you want to sell a dark helmet for %P gold?", Topic=11 +"sell","mystic","turban" -> Type=3574, Amount=1, Price=150, "Do you want to sell a mystic turban for %P gold?", Topic=11 + +"sell",%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=900*%1, "Do you want to sell %A serpent swords for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","hammer" -> Type=3322, Amount=%1, Price=2000*%1, "Do you want to sell %A dragon hammers for %P gold?", Topic=11 +"sell",%1,1<%1,"giant","sword" -> Type=3281, Amount=%1, Price=17000*%1, "Do you want to sell %A giant swords for %P gold?", Topic=11 +"sell",%1,1<%1,"poison","dagger" -> Type=3299, Amount=%1, Price=50*%1, "Do you want to sell %A poison daggers for %P gold?", Topic=11 +"sell",%1,1<%1,"scimitar" -> Type=3307, Amount=%1, Price=150*%1, "Do you want to sell %A scimitars for %P gold?", Topic=11 +"sell",%1,1<%1,"skull","staff" -> Type=3324, Amount=%1, Price=6000*%1, "Do you want to sell %A skull staffs for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","axe" -> Type=3318, Amount=%1, Price=2000*%1, "Do you want to sell %A knight axes for %P gold?", Topic=11 + +"sell",%1,1<%1,"tower","shield" -> Type=3428, Amount=%1, Price=8000*%1, "Do you want to sell %A tower shields for %P gold?", Topic=11 +"sell",%1,1<%1,"black","shield" -> Type=3429, Amount=%1, Price=800*%1, "Do you want to sell %A black shields for %P gold?", Topic=11 +"sell",%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=900*%1, "Do you want to sell %A ancient shields for %P gold?", Topic=11 +"sell",%1,1<%1,"vampire","shield" -> Type=3434, Amount=%1, Price=15000*%1, "Do you want to sell %A vampire shields for %P gold?", Topic=11 + +"sell",%1,1<%1,"strange","helmet" -> Type=3373, Amount=%1, Price=500*%1, "Do you want to sell %A strange helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=400*%1, "Do you want to sell %A dark armors for %P gold?", Topic=11 +"sell",%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=250*%1, "Do you want to sell %A dark helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=5000*%1, "Do you want to sell %A warrior helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=5000*%1, "Do you want to sell %A knight armors for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=5000*%1, "Do you want to sell %A pairs of knight legs for %P gold?", Topic=11 +"sell",%1,1<%1,"mystic","turban" -> Type=3574, Amount=%1, Price=150*%1, "Do you want to sell %A mystic turbans for %P gold?", Topic=11 + +Topic=10,QuestValue(288)<3,! -> "No chance, human. Malor doesn't want me to trade with strangers." +Topic=10,"yes",CountMoney>=Price -> "Thank you. Here you are.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "You do not have enough gold." +Topic=10 -> "Well, obviously not." + +Topic=11,QuestValue(288)<3,! -> "No chance, human. Malor doesn't want me to trade with strangers." +Topic=11,"yes",Count(Type)>=Amount -> "Ok. Here is your gold.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You don't have one." +Topic=11,"yes",Amount>1 -> "You don't have that many." +Topic=11 -> "Well, obviously not." + +"mission",QuestValue(286)=3,QuestValue(287)=0 -> "So Baa'leal thinks you are up to do a mission for us? ...", + "I think he is getting old, entrusting human scum such as you are with an important mission like that. ...", + "Personally, I don't understand why you haven't been slaughtered right at the gates. ...", + "Anyway. Are you prepared to embark on a dangerous mission for us?", Topic=2 +"baa'leal",QuestValue(286)=3,QuestValue(287)=0 -> * + +Topic=2,"yes" -> "All right then, human. Have you ever heard of the 'Tears of Daraman'? ...", + "They are precious gemstones made of some unknown blue mineral and possess enormous magical power. ...", + "If you want to learn more about these gemstones don't forget to visit our library. ...", + "Anyway, one of them is enough to create thousands of our mighty djinn blades. ...", + "Unfortunately my last gemstone broke and therefore I'm not able to create new blades anymore. ...", + "To my knowledge there is only one place where you can find these gemstones - I know for a fact that the Marid have at least one of them. ...", + "Well... to cut a long story short, your mission is to sneak into Ashta'daramai and to steal it. ...", + "Needless to say, the Marid won't be too eager to part with it. Try not to get killed until you have delivered the stone to me.", SetQuestValue(287,1) +Topic=2 -> "Then not." + +"mission",QuestValue(287)>0,QuestValue(287)<3 -> "Did you find the tear of Daraman?", Topic=3 +"gem",QuestValue(287)>0,QuestValue(287)<3 -> * +"tear",QuestValue(287)>0,QuestValue(287)<3 -> * + +Topic=3,"yes",QuestValue(287)=2,Count(3233)>0 -> "So you have made it? You have really managed to steal a Tear of Daraman? ...", + "Amazing how you humans are just impossible to get rid of. Incidentally, you have this character trait in common with many insects and with other vermin. ...", + "Nevermind. I hate to say it, but it you have done us a favour, human. That gemstone will serve us well. ...", + "Baa'leal, wants you to talk to Malor concerning some new mission. ...", + "Looks like you have managed to extended your life expectancy - for just a bit longer.", Amount=1, Delete(3233), SetQuestValue(287,3) +Topic=3 -> "As I expected. You haven't got the stone. Shall I explain your mission again?", Topic=2 + +"mission",QuestValue(287)=3 -> "Don't forget to talk to Malor concerning your next mission." +"work",QuestValue(287)=3 -> * +} diff --git a/app/SabrehavenServer/data/npc/alexander.npc b/app/SabrehavenServer/data/npc/alexander.npc new file mode 100644 index 0000000..d540df1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/alexander.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alexander.npc: Datenbank fuer den Magiehaendler Alexander + +Name = "Alexander" +Outfit = (130,96-63-71-97-0) +Home = [33256,31839,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you." + +"bye" -> "See you.", Idle +"name" -> "I am Alexander." +"job" -> "I trade with runes and other magic items." +"time" -> "It's %T right now." +"king" -> "The king has not much interest in magic items as far as I know." +"tibianus" -> * +"army" -> "The army uses weapons and armor rather then items of magic." +"ferumbras" -> "A hero has to be well prepared to face this threat." +"excalibug" -> "Ah, I would trade a fortune for this fabulous item." +"thais" -> "I am glad the king founded this academy far away from the mundane troubles of Thais" +"tibia" -> "The world is filled with wonderous places and items." +"carlin" -> "I heard it's a city of druids." +"edron" -> "In our town, science and arts are thriving." +"news" -> "Ask for news and rumors in the tavern." +"rumors" -> * + +"offer" -> "I'm selling runes, life rings, wands, rods and crystal balls." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." + +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 + +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"life","ring" -> Type=3052, Amount=1, Price=900, "Do you want to buy a life ring for %P gold?", Topic=1 +"crystal","ball" -> Type=3076, Amount=1, Price=530, "Do you want to buy a crystal ball for %P gold?", Topic=1 + +%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=900*%1, "Do you want to buy %A life rings for %P gold?", Topic=1 +%1,1<%1,"crystal","ball" -> Type=3076, Amount=%1, Price=530*%1, "Do you want to buy %A crystal balls for %P gold?", Topic=1 + +"sell","life","crystal" -> Type=3061, Amount=1, Price=85, "Do you want to sell a life crystal for %P gold?", Topic=2 +"sell","mind","stone" -> Type=3062, Amount=1, Price=170, "Do you want to sell a mind stone for %P gold?", Topic=2 +"sell","crystal","ball" -> Type=3076, Amount=1, Price=190, "Do you want to sell a crystal ball for %P gold?", Topic=2 + +"sell",%1,1<%1,"life","crystal" -> Type=3061, Amount=%1, Price=85*%1, "Do you want to sell %A life crystals for %P gold?", Topic=2 +"sell",%1,1<%1,"mind","stone" -> Type=3062, Amount=%1, Price=170*%1, "Do you want to sell %A mind stones for %P gold?", Topic=2 +"sell",%1,1<%1,"crystal","ball" -> Type=3076, Amount=%1, Price=190*%1, "Do you want to sell %A crystal balls for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +@"gen-t-runes-prem-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/alia.npc b/app/SabrehavenServer/data/npc/alia.npc new file mode 100644 index 0000000..9d2235c --- /dev/null +++ b/app/SabrehavenServer/data/npc/alia.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alia.npc: Datenbank für die Priesterin Alia + +Name = "Alia" +Outfit = (138,96-95-0-95-0) +Home = [32360,31785,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Welcome to the temple of Carlin." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Please return if you are heavily wounded or poisoned. I can heal you anytime." + +"bye" -> "May the gods be with you, %N!", Idle +"farewell" -> * +"job" -> "I'm a nun, serving the gods of Tibia in this temple. I also heal wounded adventurers." +"name" -> "My name is Alia." +"tibia" -> "That's where we are. The world Tibia." +"god" -> "They created Tibia and all life on it." +"ferumbras" -> "Don't mention this name here." +"excalibug" -> "Sorry, I can't help you with that." +"ghostlands" -> "Uh, don't ask. Thats a place even the brave women of carlin don't dare to explore them!!!" + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." +} diff --git a/app/SabrehavenServer/data/npc/allen.npc b/app/SabrehavenServer/data/npc/allen.npc new file mode 100644 index 0000000..753e3c3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/allen.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# allen.npc: Möbelverkäufer Allen in Venore + +Name = "Allen" +Outfit = (128,76-43-38-76-0) +Home = [32991,32062,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Plank and Treasurechest Market, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Allen Richardson. I am the owner of this market." +"job" -> "I run this market and sell furniture." +"time" -> "It is %T. Too bad we run out of cuckoo clocks." +"news" -> "Sorry, no time to chat, let's trade." + +"offer" -> "At this counter you can buy chairs. What do you need?" +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-chairs-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/alwin.npc b/app/SabrehavenServer/data/npc/alwin.npc new file mode 100644 index 0000000..22edbc2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/alwin.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alwin.npc: Datenbank für eine Stadtwache in Venore + +Name = "Alwin" +Outfit = (131,113-113-113-115-0) +Home = [32875,32125,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/amanda.npc b/app/SabrehavenServer/data/npc/amanda.npc new file mode 100644 index 0000000..89075f0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/amanda.npc @@ -0,0 +1,97 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# amanda.npc: Datenbank für die Nonne Amanda + +Name = "Amanda" +Outfit = (138,96-95-0-95-0) +Home = [33222,31814,8] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the temple of Banor's blood %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "If you are heavily wounded or poisoned, feel free to return for a healing." + +"bye" -> "Farewell, %N!", Idle +"job" -> "I am a humble nun." +"name" -> "I am sister Amanda." +"tibia" -> "That's our world." +"god" -> "They created the world and all life on it." +"king" -> "Our king is a religious man. A shining example." +"tibianus" -> * +"army" -> "Our army lives to the ideals of Banor." +"banor" -> * +"ferumbras" -> "He is a pawn of evil." +"excalibug" -> "Only a being loyal to Banor will wield this blade." +"news" -> "Sorry, I rarely have time to chat." +"time" -> "Now, it is %T." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"stake",QuestValue(17576)=6,Count(5941)<=0 -> "I think you have forgotten to bring your stake, my child." +"stake",QuestValue(17576)=6 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Wicked curses shall be broken'. Now, bring your stake to Kasmir in Darashia for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,7) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, my child." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=7 -> "You should visit Kasmir in Darashia now, my child." +"stake",QuestValue(17576)>7 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That's a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." + +} diff --git a/app/SabrehavenServer/data/npc/amber.npc b/app/SabrehavenServer/data/npc/amber.npc new file mode 100644 index 0000000..64ba853 --- /dev/null +++ b/app/SabrehavenServer/data/npc/amber.npc @@ -0,0 +1,93 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# amber.npc: Datenbank für die Abenteurerin Amber + +Name = "Amber" +Outfit = (136,59-113-132-76-1) +Home = [32103,32182,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh hello, nice to see you %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I'm already talking to someone." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"farewell" -> * +"how","are","you" -> "I am recovering from a sea journey." +"job" -> "I explore and seek adventure." +"explore" -> "I have been almost everywhere in Tibia." +"adventure" -> "I fought fierce monsters, climbed the highest mountains, and crossed the sea on a raft." +"sea" -> "My trip over the sea was horrible. The weather was bad, the waves high and my raft quite simple." +"time" -> "Sorry, I lost my watch in a storm." +"help" -> "I can't help you much beyond information." +"information" -> "Just ask and I'll try to answer." +"dungeon" -> "I have not had the time to explore the dungeons of this isle, but I have seen two big caves in the east, and there is a ruined tower in the northwest." +"sewer" -> "I like sewers. I made my very first battle experience in the Thais sewers. The small sewersystem of Rookgaard has some nasty rats to fight." +"assistant" -> "I have a job of great responsibility. Mostly I keep annoying persons away from my boss." +"monster" -> "Oh, I fought orcs, cyclopses, minotaurs, and even green dragons." +"cyclops" -> "Horrible monsters they are." +"minotaur" -> * +"dragon" -> * +"raft" -> "I left my raft at the south eastern shore. I forgot my private notebook on it. If you could return it to me I would be very grateful." +"quest" -> * +"mission" -> * +"seymour" -> "I think this poor guy was a bad choice as the head of the academy." +"academy" -> "A fine institution, but it needs definitely more funds from the king." +"king" -> "King Tibianus is the ruler of Thais." +"thais" -> "A fine city, but the king has some problems enforcing the law." +"cipfried" -> "A gentle person. You should visit him, if you have problems." +"dallheim" -> "An extraordinary warrior. He's the first and last line of defense of Rookgaard." +"hyacinth" -> "Hyacinth is a great healer. He lives somewhere hidden on this isle." +"willie" -> "He's funny in his own, gruffy way." +"obi" -> "He's a funny little man." +"weapon" -> "The best weapons on this isle are just toothpicks, compared with the weapons warriors of the mainland wield." +"magic" -> "You can learn spells only in the guildhalls of the mainland." +"tibia" -> "I try to explore each spot of Tibia, and one day I will succeed." +"castle" -> "If you travel to Thais, you really should visit the marvelous castle." + +"book" -> Type=2821, Amount=1, "Do you bring me my notebook?", Topic=1 +"notebook" -> * +Topic=1,"yes",Count(Type)>=Amount -> "Excellent. Here, take this short sword, that might serve you well.", Delete(Type), Create(3294) +Topic=1,"yes" -> "Hm, you don't have it." +Topic=1 -> "Too bad." + +"orcish" -> "I speak some orcish words, not much though, just 'yes' and 'no' and such basic.", Topic=2 +"language" -> * +"prisoner" -> * +"orc" -> "Not the nicest guys you can encounter. I had some clashes with them and was prisoner of the orcs for some months." +Topic=2,"yes" -> "It's 'mok' in orcish. I help you more about that if you have some food." +Topic=2,"no" -> "In orcish that's 'burp'. I help you more about that if you have some food." + +"food" -> "My favorite dish is salmon. Oh please, bring me some of it." +"salmon" -> Type=3579, Amount=1, "Yeah! If you give me some salmon I will tell you more about the orcish language.", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Thank you. Orcs call arrows 'pixo'.", Delete(Type) +Topic=3,"yes" -> "You don't have one!" +Topic=3 -> "Ok, then I don't tell you more about the orcish language." + +"addon",QuestValue(18502)=0,premium -> "Ah, you noticed my new accessory? Sorry, this one is not for sale. It's handmade from rare minotaur leather.", Topic=4 +"backpack",QuestValue(18502)=0,premium -> * +"addon",QuestValue(18502)=0 -> "Ah, you noticed my new accessory? Sorry, this one is not for sale." +"backpack",QuestValue(18502)=0 -> * +Topic=4,"minotaur","leather" -> "Well, if you really like this backpack, I could make one for you, but minotaur leather is hard to come by these days. Are you willing to put some work into this?", Topic=5 +Topic=5,"yes" -> "Alright then, if you bring me 100 pieces of fine minotaur leather I will see what I can do for you. You probably have to kill really many minotaurs though...", + "so good luck!", SetQuestValue(18502,1), SetQuestValue(17594,1) + +"addon",QuestValue(18502)=1 -> Type=5878, Amount=100, "Ah, right, almost forgot about the backpack! Have you brought me 100 pieces of minotaur leather as requested?", Topic=6 +"backpack",QuestValue(18502)=1 -> * +Topic=6,"yes",Count(Type)>=Amount -> "Great! Alright, I need a while to finish this backpack for you. Come ask me later, okay?", Delete(Type), SetExpiringQuestValue(18503, 7200000), SetQuestValue(18502,2) +Topic=6,"yes" -> "You don't have that many!" +Topic=6 -> "Too bad." + +"addon",ExpiringQuestValue(18503)>0 -> "Please be patient! I am still working on the backpack details. Come back later, okey?" +"backpack",ExpiringQuestValue(18503)>0 -> * + +"addon",ExpiringQuestValue(18503)<0,QuestValue(18502)=2 -> "Just in time! Your backpack is finished. Here you go, I hope you like it.", SetQuestValue(18502,3), AddOutfitAddon(136,1), AddOutfitAddon(128,1), EffectOpp(13) +"backpack",ExpiringQuestValue(18503)<0,QuestValue(18502)=2 -> * + +"addon",QuestValue(18502)=3 -> "Oh, you also have a nice backpack just like me!" +"backpack",QuestValue(18502)=3 -> * +} diff --git a/app/SabrehavenServer/data/npc/anerui.npc b/app/SabrehavenServer/data/npc/anerui.npc new file mode 100644 index 0000000..c2099ed --- /dev/null +++ b/app/SabrehavenServer/data/npc/anerui.npc @@ -0,0 +1,77 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# anerui.npc: Datenbank für die Jägerin Anerui + +Name = "Anerui" +Outfit = (63,0-0-0-0-0) +Home = [32669,31659,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"name" -> "I am Anerui Mourningleaf." +"job" -> "I am the mistress of the hunt. At this place you may buy the food our hunts provide." +"time" -> "Watch the sky, it will tell you." + +"carlin" -> "Carlin needs our protection and resources. Of course they will turn on us as soon as they feel strong enough." +"thais" -> "Thais is to far away to prove a threat but also is of little help if problems should occur." +"venore" -> "Venore profits greatly from the trade with Ab'Denriel. I see those traders as leeches that suck away our resources." +"roderick" -> "He is our contact person to the thaian kingdom and a necessary evil." +"olrik" -> "He would love to be an elf but still is more kind of a pale orc." + +"hunter" -> "Hunters live a life of freedom and closeness to nature, unlike a simple farmer or bugherder." +"hunt" -> "Hunting is an art, practiced too often by diletantes. Every fool with a bow or a spear considers himself a hunter." +"game" -> * +"prey" -> * +"forest" -> "The forests are the gardens of life. Nature provides enough for everyone's need, but not enough for everyone's greed." +"nature" -> "Nature is not a friend but an unforgiving teacher, and the lessons we have to learn are endless." +"teacher" -> "Most lessons nature teaches are about life and death." +"life" -> "Life and death are significant parts of the balance." +"death" -> * +"balance" -> "The balance of nature, of course. It's everywhere, so don't ask but observe and learn." +"bugherder" -> "Well, a person who herds bugs of course." +"bugs" -> "The bugs provide us with chitin for equipment, bugmilk, and bugmeat." +"bugmilk" -> "It's delicious. Brasith sells it in his store." +"bow" -> "Bow, arrow, and spear are the hunters' best friends. In the northeast of the town one of us may sell such tools." +"arrow" -> * +"spear" -> * +"elf" -> "That is the race to which I belong." +"elves" -> * +"dwarf" -> "I will never understand these little people of the mountains." +"human" -> "The humans are a loud and ugly race. They lack any grace and are more kin to the orcs then to us." +"troll" -> "I despise their presence in our town, but it may be a necessary evil." +"cenath" -> "The magic they wield is all that matters to them." +"kuridai" -> "The Kuridai are too agressive not only to people but also to the enviroment. They lack any understanding of the balance that we know as nature." +"deraisim" -> "We try to live in harmony with the forces of nature, may they be living or unliving." +"abdaisim" -> "The Abdaisim are our brothers and sisters in spirit. We stay in contact with them, exchanging news and items." +"teshial" -> "If they ever existed they are gone now." +"ferumbras" -> "The defiler. I will not talk about him." +"crunor" -> "I guess it's a human god for the human sight of nature. I have not much knowledge of this entity." + +"offer" -> "I sell meat and ham." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> * + +"meat" -> Type=3577, Amount=1, Price=4, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=6, "Do you want to buy ham for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=4*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=6*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/app/SabrehavenServer/data/npc/aneus.npc b/app/SabrehavenServer/data/npc/aneus.npc new file mode 100644 index 0000000..7d7c241 --- /dev/null +++ b/app/SabrehavenServer/data/npc/aneus.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aneus.npc: Datenbank für den Geschichtenerzähler Aneus (Fields) + +Name = "Aneus" +Outfit = (129,0-50-58-116-0) +Home = [32426,31666,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings adventurer %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and take care!" + +"bye" -> "Good bye and take care of you!", Idle +"farewell" -> * +"name" -> "My name is Aneus, the storyteller." +"bruno" -> "I don't know much about him. I only know that he is selling fish in the village." +"marlene" -> "A lovely woman. But I give you a hint: Better keep away from her. *grin*" +"graubart" -> "I don't know much about him. But he sails much and has seen nearly the whole world." +"job" -> "I'm a storyteller." +"storyteller",! -> "Well, if you wish I can tell you the story about this place here. The story about the Fields of Glory!" + +"story",! -> "Ok, sit down and listen. Back in the early days, one of the ancestors of our king Tibianus III wanted to build the best city in whole of Tibia.", Topic=2 +"fields","of","glory",! -> "Ok, sit down and listen. Back in the early days, one of the ancestors of our king Tibianus III wanted to build the best city in whole of Tibia.", Topic=2 + +Topic=2,"ancestor",! -> "Please forgive me. I forgot his name. I'm not that young anymore.", Topic=2 +Topic=2,"city",! -> "The works on this new city began and the king sent his best soldiers to protect the workers from orcs and to make them work harder.", Topic=3 + +Topic=3,"soldier",! -> "It was the elite of the whole army. They were called the Red Legion (also known as the Bloody Legion).", Topic=3 +Topic=3,"orc" -> "The orcs attacked the workers from time to time and so they disturbed the works on the city.", Topic=4 +Topic=3,"work","harder",! -> "The soldiers treated them like slaves.", Topic=4 + +Topic=4,"slave",! -> "You dont know what a slave is? I really hope that you will never have to make this experience.", Topic=3 +Topic=4,"works",! -> "The development of the city was fine. Also a giant castle was build northeast of the city. ...", + "But more and more workers started to rebel because of the bad conditions.", Topic=5 + +Topic=5,"rebel",! -> "All rebels were brought to the giant castle. Guarded by the Red Legion, they had to work and live in even worse conditions. ...", + "Also some friends of the king's sister were brought there.", Topic=6 + +Topic=6,"friends",! -> "The king's sister was pretty upset about the situation there but her brother didn't want to do anything about this matter. ...", + "So she made a plan to destroy the Red Legion for their cruelty forever.", Topic=7 + +Topic=7,"cruelty",! -> "The soldiers treated the workers like slaves.", Topic=7 +Topic=7,"plan",! -> "She ordered her loyal druids and hunters to disguise themselves as orcs from the near island and to attack the Red Legion by night over and over again.", Topic=8 + +Topic=8,"island",! -> "The General of the Red Legion became very angry about these attacks and after some months he stroke back!", Topic=9 +Topic=8,"attack",! -> * + +Topic=9,"stroke",! -> "Most of the Red Legion went to the island by night. The orcs were not prepared and the Red Legion killed hundreds of orcs with nearly no loss. ...", + "After they were satisfied they walked back to the castle.", Topic=10 + +Topic=10,"back",! -> "It is said that the orcish shamans cursed the Red Legion. ...", + "Nobody knows. But one third of the soldiers died by a disease on the way back. ...", + "And the orcs wanted to take revenge, and after some days they stroke back! ...", + "The orcs and many allied cyclopses and minotaurs from all over Tibia came to avenge their friends, and they killed nearly all workers and soldiers in the castle. ...", + "The help of the king's sister came too late.", Topic=11 + +Topic=10,"walk",! -> "It is said that the orcish shamans cursed the Red Legion. ...", + "Nobody knows. But one third of the soldiers died by a disease on the way back. ...", + "And the orcs wanted to take revenge, and after some days they stroke back! ...", + "The orcs and many allied cyclopses and minotaurs from all over Tibia came to avenge their friends, and they killed nearly all workers and soldiers in the castle. ...", + "The help of the king's sister came too late.", Topic=11 + +Topic=11,"help",! -> "She tried to rescue the workers but it was too late. The orcs started immediately to attack her troops, too. ...", + "Her royal troops went back to the city. A trick saved the city from destruction.", Topic=12 + +Topic=12,"trick" -> "They used the same trick as against the Red Legion and the orcs started to fight their non-orcish-allies. ...", + "After a bloody long fight the orcs went back to their cities. The city of Carlin was rescued. ...", + "Since then, a woman has always been ruling over Carlin and this statue was made to remind us of their great tactics against the orcs and the Red Legion. ...", + "So that was the story of Carlin and these Fields of Glory. I hope you liked it. *smiles*" + +Topic=12,"destruction" -> "They used the same trick as against the Red Legion and the orcs started to fight their non-orcish-allies. ...", + "After a bloody long fight the orcs went back to their cities. The city of Carlin was rescued. ...", + "Since then, a woman has always been ruling over Carlin and this statue was made to remind us of their great tactics against the orcs and the Red Legion. ...", + "So that was the story of Carlin and these Fields of Glory. I hope you liked it. *smiles*" +} diff --git a/app/SabrehavenServer/data/npc/angelina.npc b/app/SabrehavenServer/data/npc/angelina.npc new file mode 100644 index 0000000..1ad4aed --- /dev/null +++ b/app/SabrehavenServer/data/npc/angelina.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angelina.npc: Datenbank für die gefangene der dunklen mönche Angelina +Name = "Angelina" +Outfit = (136,57-79-98-95-0) +Home = [32635,32402,10] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",QuestValue(17549)=0,! -> "The gods must be praised that I am finally saved. I do not have many worldly possessions, but please accept a small reward, do you?", Topic=2 +ADDRESS,"hi$",QuestValue(17549)=0,! -> * +ADDRESS,"hiho$",QuestValue(17549)=0,! -> * +ADDRESS,"hello$",! -> "The gods must be praised that I am finally saved." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods bless you." + +"bye" -> "May the gods bless you.", Idle +"farewell" -> * +"job" -> "I am a priestess and I travelled here to learn about that order of the humble path I heard about. ...","But when I started my investigations, this false monk Lorbas thought that I was suspicious and so he ordered his minions to take me as prisoner." +"prisoner" -> "I think Lorbas liked the idea to 'convert' me to their twisted cult and saw it as a test for their leaders. Now that the magic symbols are turned off, I will gather my strength within some hours and teleport to safety." +"humble","path" -> "There are no records about the foundation of this order, and it is unknown where its 'monks' come from. Yet, travellers told us that they are living near the remains of the dark cathedral." +"monk" -> "I learnt that these monks are impostors that use false promises to lure unwary ones into the arms of their strange cult which seems to have more political than religious agendas." +"cult" -> "The cult is secretly looking for the unsatisfied, disgrunteled and poor. Its members promise such sad individuals wealth, revenge and a cause. ...", "They lure them into the cells of their cult. Here they learn how to undermine the authorities of their cities. They are trained as thieves, spies and smugglers first. ...", "Those who prove themselves as the most promising candidates are recruited to a special hidden circle. There they learn the dark arts of poisoning and murder, or elocution and agitation to become assassins and recruiters for the cult. ...","I know nothing about their agenda but I am quite sure there has to be some higher power behind all of this." +"power" -> "I have no idea who is the mastermind behind all this, but it seems too big and too well organised to be the work of only a handful of false monks." +"cathedral" -> "The cathedral was meant to be a centre of piety and believe. A prayer to the gods that had become solid. ...", + "The construction works started at the height of the Order of the Nightmare Knights, right after they had won a major battle near the place where the cathedral was to be built. ...", + "The cathedral was meant to become a monument of the victory of good over evil. ...", + "Sadly it was just not meant to be. ...", + "As the cathedral was nearly finished, most of the monks had already moved in and even a small town for all the workers and suppliers had established itself. ...", + "But then the structure was struck by an earthquake and the work of two generations was destroyed. ...", + "Later the dwarven constructors explained that this was caused by volcanic activities and a massive cave-in. ...", + "Since the gods did not interfere and the setting was close to the notorious Pits of Inferno, it was assumed that this was the work of secret demonic powers." + +"king" -> "The king is a wise ruler but his realm is large and we all need to work hard to make the world a better place." +"venore" -> "Sadly the trade barons care more about wealth than the gods." +"thais" -> "Many see Thais as a fallen city but it is only the loudness of an ugly minority that gives people this impression." +"carlin" -> "The druids have their own way to interpret the gods' will and this has to be respected." +"edron" -> "The downfall of some of the most noble knights there should serve us as a warning to stay on guard for the evil that wants to lure us on the wrong path." +"gods" -> "I would love to discuss the teachings of the gods with you but this is neither the time nor the place." + +"tibia" -> "We all have to help to make this world a better place." + +"kazordoon" -> "The dwarves carry bitterness and pain in their souls. But it is them that have forgotten about the gods and not the other way around." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The elves have lost their balance and identity. In this unstable state they can easily be misled or might draw the wrong conclusions." +"elves" -> * +"elfs" -> * +"darama" -> "A far away continent that will widen our view of the wonders the gods provide us with." +"darashia" -> "I know only little about the teachings of Daraman but as far as I heard they concentrate too much on the single individual instead on the world as a whole." +"ankrahmun" -> "This city is the best example where godless philosophies might lead to." +"ferumbras" -> "He is only one of the many servants of the evil. Eventually he will fall but there will be others to take his place." +"excalibug" -> "One day this weapon will be unearthed and then it will be wielded against the servants of the evil." +"assassin" -> "The assassins are the eyes and the long arm of this damnable cult. They eliminate the enemies and those who found out too much about their plans. Be aware of that and always watch your back." +"dark","monk" -> "The dark monks are the teachers and seducers of this cult. They work covertly in the cities and train thieves and assassins in the underground base here." +"teleport" -> "I am still gathering my strength for a teleport home, but some power already has returned. Do you wish to be teleported out of this cell?",topic=1 +"safety" -> * +"help" -> * +"escape" -> * +"out" -> * +"door" -> * +Topic=1,"yes" -> "So be it!", Idle, EffectOpp(11), Teleport(32626,32402,10), EffectOpp(11) +Topic=1 -> "As you wish." + +Topic=2,"yes" -> "I will tell you a small secret now. My friend Lynda in Thais can create a blessed wand. Greet her from me, maybe she will aid you.", SetQuestValue(17549,1), SetQuestValue(17594,1) +Topic=2 -> "As you wish." + +} + diff --git a/app/SabrehavenServer/data/npc/angus.npc b/app/SabrehavenServer/data/npc/angus.npc new file mode 100644 index 0000000..7c65a26 --- /dev/null +++ b/app/SabrehavenServer/data/npc/angus.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angus: Datenbank für den Teamassitstenten der explorers society Angus + +Name = "Angus" +Outfit = (133,57-113-95-113-0) +Home = [32670,32730,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, what can I do for you?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "Good bye.", Idle +"farewell" -> * + +################ Später ab hier besser bd nutzen +@"explorer.ndb" + +"mission",QuestValue(300)=12,QuestValue(320)<1 -> "With the objects you've provided our researchers will make steady progress. Still we are missing some test results from fellow explorers ...", "Please travel to our base in Northport and ask them to mail us their latest research reports. Then return here and ask about new missions.",SetQuestValue(320,1) + + +##### +"research","reports",QuestValue(320)=2 -> "Oh, yes! Tell our fellow explorer that the papers are in the mail already.",SetQuestValue(320,4) +"mission",QuestValue(320)=2 -> * + +##### +"mission",QuestValue(320)=3 -> "The reports from Northport have already arrived here and our progress is astonishing. We think it is possible to create an astral bridge between our bases. Are you interested to assist us with this?",topic=33 + +##### +"no",topic=33 -> "Perhaps you are interested some other time." +"yes",topic=33 -> "Good, just take this spectral essence and use it on the strange carving in this building as well as on the corresponding tile in our base at Northport ...", "As soon as you have charged the portal tiles that way, report about the spectral portals.", Create(4840),SetQuestValue(320,5) + +##### topic 34 verwendet + +} + + + diff --git a/app/SabrehavenServer/data/npc/apparition.npc b/app/SabrehavenServer/data/npc/apparition.npc new file mode 100644 index 0000000..32a88fd --- /dev/null +++ b/app/SabrehavenServer/data/npc/apparition.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "An Apparition" +Outfit = (48,0-0-0-0-0) +Home = [32204,31788,5] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/ariella.npc b/app/SabrehavenServer/data/npc/ariella.npc new file mode 100644 index 0000000..bd920f3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ariella.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Ariella" +Outfit = (155,115-3-1-76-2) +Home = [32336,32593,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there %N, and welcome to my tavern." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle + +"drinks" -> "Well, we usually drink around here, but right now we're running dry. However, I sell juice squeezers to make fruit juice." +"tavern" -> "I can offer you food and drinks. I also offer juice squeezers." +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have cheese, ham and meat as well as a variety of fruits." +"fruits" -> "I have bananas, apples, oranges, strawberries, melons, pumpkin, blueberries, mangoes and pears, sweety." + +"banana" -> Type=3587, Amount=1, Price=5, "Do you want to buy a banana for %P gold?", Topic=1 +"blueberry" -> Type=3588, Amount=1, Price=1, "Do you want to buy blueberry for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"juice","squeezer" -> Type=5865, Amount=1, Price=100, "Do you want to buy a juice squeezer for %P gold?", Topic=1 +"mango" -> Type=5096, Amount=1, Price=10, "Do you want to buy a mango for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy a meat for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=10, "Do you want to buy a melon for %P gold?", Topic=1 +"orange" -> Type=3586, Amount=1, Price=10, "Do you want to buy a orange for %P gold?", Topic=1 +"pear" -> Type=3584, Amount=1, Price=5, "Do you want to buy a pear for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"red apple" -> Type=3585, Amount=1, Price=5, "Do you want to buy a red apple for %P gold?", Topic=1 +"strawberry" -> Type=3591, Amount=1, Price=2, "Do you want to buy a strawberry for %P gold?", Topic=1 + +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=5*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"blueberr" -> Type=3588, Amount=%1, Price=1*%1, "Do you want to buy %A blueberries for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"juice","squeezer" -> Type=5865, Amount=%1, Price=100*%1, "Do you want to buy %A juice squeezers for %P gold?", Topic=1 +%1,1<%1,"mango" -> Type=5096, Amount=%1, Price=10*%1, "Do you want to buy %A mangos for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meats for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=10*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=10*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"pear" -> Type=3584, Amount=%1, Price=5*%1, "Do you want to buy %A pears for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"red apple" -> Type=3585, Amount=%1, Price=5*%1, "Do you want to buy %A red apples for %P gold?", Topic=1 +%1,1<%1,"strawberr" -> Type=3591, Amount=%1, Price=2*%1, "Do you want to buy %A strawberries for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +# Meriana_Quest +"mission",QuestValue(17520)=1,QuestValue(17521)=0 -> "You know, we have plenty of rum here but we lack some basic food. Especially food that easily becomes mouldy is a problem. Bring me 100 breads and you will help me a lot.", SetQuestValue(17521,1) +"task",QuestValue(17520)=1,QuestValue(17521)=0 -> * + +"mission",QuestValue(17521)=1 -> Type=3600, Amount=100, "Are you here to bring me the 100 pieces of bread that I requested?", Topic=2 +"task",QuestValue(17521)=1 -> * +"bread",QuestValue(17521)=1 -> * +Topic=2,"yes",Count(Type)>=Amount -> "What a joy. At least for a few days adequate supply is ensured.", Delete(Type), SetQuestValue(17521,2) +Topic=2,"yes" -> "Sorry, you do not have so many." +Topic=2 -> "Maybe another time." + +"mission",QuestValue(17521)=2 -> "The sailors always tell tales about the famous beer of Carlin. You must know, alcohol is forbidden in that city. ...", + "The beer is served in a secret whisper bar anyway. Bring me a sample of the whisper beer, NOT the usual beer but whisper beer. I hope you are listening.", SetQuestValue(17521,3) +"task",QuestValue(17521)=2 -> * + +"mission",QuestValue(17521)=3 -> Type=6106, Amount=1, "Did you get a sample of the whisper beer from Carlin?", Topic=3 +"task",QuestValue(17521)=3 -> * +"beer",QuestValue(17521)=3 -> * +Topic=3,"yes",Count(Type)>=Amount -> "Thank you very much. I will test this beauty in privacy.", Delete(Type), SetQuestValue(17521,4) +Topic=3,"yes" -> "Sorry, you do not have it." +Topic=3 -> "Maybe another time." + +# Pirate_Outfit_Quest +"addon",QuestValue(17520)<12 -> "You mean my hat? Well, first you have to earn our trust." +"outfit",QuestValue(17520)<12 -> * +"addon",QuestValue(17568)<2,QuestValue(17520)=12 -> "You mean my hat? Well, I might have another one just like that, but I won't simply give it away, even if you earned our trust. You'd have to fulfil a task first." +"outfit",QuestValue(17568)<2,QuestValue(17520)=12 -> * +"addon",QuestValue(17568)=2 -> "You have my respect. You more than deserve the hat." +"outfit",QuestValue(17568)=2 -> * + +"task",QuestValue(17568)=0,QuestValue(17520)=12 -> "Are you up to the task which I'm going to give you and willing to prove you're worthy of wearing such a hat?", Topic=4 +"mission",QuestValue(17568)=0,QuestValue(17520)=12 -> * +Topic=4,"yes" -> "Alright, listen closely. There are four pirate leaders who have been troubling us for a long time now. ...", + "They often lead raids on Liberty Bay and wreck havoc in the settlement - and afterwards, the blame is put on us. ...", + "Their names are 'Lethal Lissy', 'Ron the Ripper', 'Brutus Bloodbeard' and 'Deadeye Devious'. ...", + "If you can find and kill them all, be sure to retrieve an item from them as proof that you killed them. ...", + "Bring me the shirt of Lissy, the sabre of Ron, the hat of Brutus and the eye patch of Deadeye and you will be rewarded. ...", + "Have you understood everything I told you and are willing to handle this task?", Topic=5 +Topic=4 -> "Maybe another time." +Topic=5,"yes" -> "Good! Come back to me once you have all four items and ask me about that task.", SetQuestValue(17568,1) +Topic=5 -> "Maybe another time." + +"mission",QuestValue(17568)=1 -> "Your task is to bring me the shirt of the Lethal Lissy, the sabre of Ron the Ripper, the hat of Brutus Bloodbeard and the eye patch of Deadeye Devious. Did you succeed?", Topic=6 +"task",QuestValue(17568)=1 -> * +Topic=6,"yes",Count(6100)>=1,Count(6102)>=1,Count(6101)>=1,Count(6099)>=1 -> "INCREDIBLE! You have found all four of them! %N, you have my respect. You more than deserve this hat. There you go.", DeleteAmount(6100,1), DeleteAmount(6102,1), DeleteAmount(6101,1), DeleteAmount(6099,1), SetQuestValue(17568,2), AddOutfitAddon(155,2), AddOutfitAddon(151,2), EffectOpp(13) +Topic=6,"yes" -> "You don't have all four with you." +Topic=6 -> "Maybe another time." + +"mission" -> "Sorry, I don't have any missions for you." +"task" -> * +} diff --git a/app/SabrehavenServer/data/npc/arito.npc b/app/SabrehavenServer/data/npc/arito.npc new file mode 100644 index 0000000..3cd562f --- /dev/null +++ b/app/SabrehavenServer/data/npc/arito.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# arito.npc: Datenbank fuer den Wirt Arito + +Name = "Arito" +Outfit = (132,59-74-62-115-0) +Home = [33069,32886,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","frodo",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"hi$","frodo",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please show some patience, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please visit us again." + +"bye" -> "Do visit us again.", Idle +"farewell" -> * +"job" -> "I am the owner of this tavern." +"tavern" -> "This tavern is called the 'Old Scarab's Shell'." + +"name" -> "My name is Arito." +"time" -> "It is exactly %T." +"pharaoh" -> "Blessed be our saviour." +"tibianus" -> "A foolish king who resides over foolish mortals." + +"army" -> "Our army is strong and unyielding." +"ferumbras" -> "This servant of evil won't even dare to enter our city and to call the wrath of our pharaoh upon him." +"arena" -> "In the arena life challenges death. Death will be victorious in the end, but in the meantime there is much for the living to learn in preparation." +"excalibug" -> "Our pharaoh does not have any use for such a weapon. Powerful though it may be, it is nothing compared to his divine power." +"thais" -> "Thais is the capital of an insolent realm. Its people embrace life without understanding the alternative." +"tibia" -> "Why, this is our world of course." +"carlin" -> "Carlin is the twin sister of Thais. Another city that has not found the true path yet." + +"news" -> "I've heard some blasphemous adventurers have excavated one of the ancient burial sites in the desert." +"rumors" -> * + +"darama" -> "This is our continent. Ankrahmun is its biggest and most marvelous city." +"darashia" -> "A city of the lost." +"daraman" -> "I know little about his heretic teachings." +"ankrahmun" -> "Our city is a marvel. It is the envy of the whole world." +"city" -> * + +"pharaoh" -> "Our pharaoh is our father, shepherd and teacher." +"arkhothep" -> * +"mortality" -> "Mortality keeps us from finding our way to ascension." + +"ascension" -> "For us mortals ascension is but a distant dream." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is what constitutes our self." +"Akh" -> "The Akh is our body, both in death and in life." + +"undead" -> "Undeath is a blessing." +"undeath" -> * +"Rah" -> "The Rah is our lifeforce. It is the source of our inner light." +"uthun" -> "The Uthun is what we learn and remember." +"mourn" -> "Mortality is a curse. That is why mortals have to be mourned." + +"arena" -> "The arena is located close to the centre of Ankrahmun." +"palace" -> "The residence of our beloved pharaoh can be found to the south of the arena." +"temple" -> "The temple is to the east, not far from the shore." + +"buy" -> "I can offer you bread, cheese, ham or meat." +"offer" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Looking for food? I have lemonade, wine, water, bread, cheese, ham, meat and fish." + +"bread" -> Type=3600, Amount=1, Price=8, "Would you like to buy bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=12, "Would you like to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=10, "Would you like to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=16, "Would you like to buy a ham for %P gold?", Topic=1 +"fish" -> Type=3578, Amount=1, Price=6, "Would you like to buy a fish for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=8*%1, "Would you like to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=12*%1, "Would you like to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=10*%1, "Would you like to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=16*%1, "Would you like to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=6*%1, "Would you like to buy %A fishes for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=3, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=4, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=2, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +} diff --git a/app/SabrehavenServer/data/npc/arkhothep.npc b/app/SabrehavenServer/data/npc/arkhothep.npc new file mode 100644 index 0000000..4bd2f89 --- /dev/null +++ b/app/SabrehavenServer/data/npc/arkhothep.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Arkhothep.npc: Datenbank für den Pharao von Ankrahmun + +Name = "Arkhothep" +Outfit = (91,0-0-0-0-0) +Home = [33150,32842,4] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> Idle +ADDRESS,"hi$",! -> Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/arnold.npc b/app/SabrehavenServer/data/npc/arnold.npc new file mode 100644 index 0000000..84f8489 --- /dev/null +++ b/app/SabrehavenServer/data/npc/arnold.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# arnold.npc: Datenbank für eine Stadtwache in Venore + +Name = "Arnold" +Outfit = (131,113-113-113-115-0) +Home = [32945,32070,6] +Radius = 6 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/aruda.npc b/app/SabrehavenServer/data/npc/aruda.npc new file mode 100644 index 0000000..894e972 --- /dev/null +++ b/app/SabrehavenServer/data/npc/aruda.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aruda.npc: Datenbank fuer die Diebin Aruda + +Name = "Aruda" +Outfit = (140,77-83-79-95-0) +Home = [32368,32215,7] +Radius = 99 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Oh, hello, handsome! It's a pleasure to meet you %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Oh, hello %N, your hair looks great! Who did it for you?", Topic=1 +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please be nice and wait a minute. I'll be right with you %N.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you soon." + +"bye" -> "Good bye. I really hope we'll talk again soon.", Idle +"farewell" -> * +"how","are","you" -> "Thank you very much. How kind of you to care about me. I am fine, thank you.", Price=5, Topic=2 +"sell" -> "Sorry, I have nothing to sell.",Price=5, Topic=2 +"job" -> "I do some work now and then. Nothing unusual, though.",Price=5, Topic=2 +"news" -> "You should ask Oswald about news. He loves them." +"name",male -> "I am a little sad, that you seem to have forgotten me, handsome. I am Aruda.",Price=5, Topic=2 +"name",female -> "I am Aruda.",Price=5, Topic=2 +"aruda",male -> "Oh, I like it, how you say my name.",Price=5, Topic=2 +"aruda",female -> "Yes, that's me!",Price=5, Topic=2 +"time" -> Type=2906, Amount=1, "Please don't be so rude to look for the time if you are talking to me.", Topic=3 +"help" -> "I am deeply sorry, I can't help you.",Price=5, Topic=2 +"monster" -> "UH! What a terrifying topic. Please let us speak about something more pleasant, I am a weak and small woman after all.", Price=5, Topic=2 +"dungeon" -> * +"sewer" -> "What gives you the impression, I am the kind of women, you find in sewers?", Price=5, Topic=2 +"god" -> "You should ask about that in one of the temples.", Price=5, Topic=2 +"king" -> "The king, that lives in this fascinating castle? I think he does look kind of cute in his luxurious robes, doesn't he?", Price=10, Topic=2 +"sam",male -> "He is soooo strong! What muscles! What a body! On the other hand, compared to you he looks quite puny.", Price=5, Topic=2 +"sam" -> "He is soooo strong! What muscles! What a body! Did you ask him for a date?", Price=5, Topic=2 +"benjamin" -> "He is a little simple minded but always nice and well dressed.", Price=5, Topic=2 +"gorn" -> "He should really sell some stylish gowns or something like that. We Tibians never get some clothing of the latest fashion. It's a shame.", Price=5, Topic=2 +"quentin" -> "I don't understand this lonely monks. I love company too much to become one. He, he, he!", Price=5, Topic=2 +"bozo" -> "Oh, isn't he funny? I could listen to him the whole day.", Price=5, Topic=2 +"oswald" -> "As far as I know, he is working in the castle." +"rumour" -> "I am a little shy and so don't hear many rumors", Price=5, Topic=2 +"rumor" -> * +"gossip" -> * +"fuck",male -> "Oh, you little devil, stop talking like that! ", Price=20, Topic=2 +"kiss",male -> * +"fuck",female -> "Uhm, let us change the subject, please.", Price=20, Topic=2 +"weapon" -> "I know so little about weapons, so tell me something about weapons, please.", Price=5, Topic=2 +"magic" -> "I believe that love is stronger then all magic, don't you agree?", Price=5, Topic=2 +"thief" -> "Oh, sorry, I have to hurry, bye!", Idle +"theft" -> * +"tibia" -> "I would like to visit the beach more often, but I guess it's too dangerous.", Price=5, Topic=2 +"castle" -> "I love this castle! It's so beautiful.", Price=5, Topic=2 +"muriel" -> "Powerful sorcerers frighten me a little.", Price=5, Topic=2 +"elane" -> "I personally think it's inappropriate for a woman to become a warrior, what do you think about that?", Price=5, Topic=2 +"marvik" -> "Druids seldom visit a town, what do you know about druids?", Price=5, Topic=2 +"gregor" -> "I like brave fighters like him.", Price=5, Topic=2 +"noodles" -> "Oh, he is sooooo cute!", Price=5, Topic=2 +"dog" -> "I like dogs, the little ones at least. Do you like dogs, too?", Price=5, Topic=2 +"poodle" -> * +"excalibug" -> "Oh, I am just a girl and know nothing about magic swords and such things.", Price=10, Topic=2 +"partos" -> "I ... don't know someone named like that.", topic=4 +"yenny" -> "Yenny? I know no Yenny, nor have I ever used that name! You have mistook me with someone else.", Idle + +Topic=1 -> "I would never have guessed that." +Topic=2,CountMoney>=Price -> "Oh, sorry, I was distracted, what did you say?", DeleteMoney +Topic=2,CountMoney "Oh, I just remember I have some work to do, sorry. Bye!", Idle +Topic=3,Count(Type)>=Amount -> "Take some time to talk to me!", Delete(Type) +Topic=4,"spouse" -> "Well ... I might have known him a little .. but there was nothing serious.", Topic=5 +Topic=4,"girlfriend" -> * +Topic=5,"fruit" -> "I remember that grapes were his favourites. He was almost addicted to them." +} diff --git a/app/SabrehavenServer/data/npc/ashtamor.npc b/app/SabrehavenServer/data/npc/ashtamor.npc new file mode 100644 index 0000000..5dcaea5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ashtamor.npc @@ -0,0 +1,56 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ashtamor.npc: Krematoriumsbesitzer Ashtamor + +Name = "Ashtamor" +Outfit = (130,19-114-76-114-0) +Home = [32958,32088,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N, wanderer between the worlds." +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "The time comes for everyone, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you again ... sooner or later, more or less alive." + +"bye" -> "See you again ... sooner or later, more or less alive.", Idle +"job" -> "I consider myself as a guide, a guardian over the souls who transcend the border to another world." +"crematory" -> "Such an ugly word for this wonderful place. It is a door, a portal to a better world than this one is." +"name" -> "What is a name worth in your eyes? And more important: Does the choice of your name decide your further fate? Perhaps we will never know." +"time" -> "It's %T now, but the true question is: How much time is left?" +"fire" -> "The purging force of the fire ... after having been purified, the freed souls will depart with the smoke." +"soul" -> "The essence of life. Source of your very self. While the body is in space and time, the soul exists in time only." +"body" -> "Is the mind an emination of body, or the body an invention by the mind?" +"death" -> "What else does it mean than the loss of your weak physical shell? And isn't the true power in the universe rather mental than physical?" +"venore" -> "You come to this world naked, and leave it this way, so there's no need to hold back your money, especially not in a place like Venore." +"king" -> "Kings, queens ... I've seen them come and go. Everything fades, even the glory and wealth of the richest." +"monster" -> "Oh yes, monsters can grant you a passage to the afterlife also, but it's not a comfortable trip. " +"thanks" -> "'Thank you' ... Words I rarely here these days. Tell me when I might be of service again, %N." +"thank","you" -> * +"help" -> "What help might I offer you except guidance? Would you like me to help you transcend the border to the afterlife?", Topic=1 + +Topic=1,"yes" -> "Are you sure? You might not be able to come back, consider that.", Topic=2 +Topic=1,"no" -> "Come to me when you have changed your mind, wanderer." +Topic=2,"yes" -> EffectOpp(16), EffectMe(14), "Hmm... seems you are not ready yet to let go." +Topic=2,"no" -> "Come to me when you have changed your mind, wanderer." + +"buy" -> "I am offering vases and amphoras, the perfect vessel for dusty remains of whatever sort." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"vase" -> Type=2876, Amount=1, Price=3, "Do you want to buy a vase for %P gold?", Topic=3 +"amphora" -> Type=2893, Amount=1, Price=4, "Do you want to buy an amphora for %P gold?", Topic=3 + +%1,1<%1,"vase" -> Type=2876, Amount=%1, Price=3*%1, "Do you want to buy %A vases for %P gold?", Topic=3 +%1,1<%1,"amphora" -> Type=2893, Amount=%1, Price=4*%1, "Do you want to buy %A amphoras for %P gold?", Topic=3 + + +Topic=3,"yes",CountMoney>=Price -> "May it be of good use to you. ", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Sorry, that's not even enough for a shard of my quality wares." +Topic=3 -> "Do you really want to leave the choice of your future vessel to chance?" +} diff --git a/app/SabrehavenServer/data/npc/asima.npc b/app/SabrehavenServer/data/npc/asima.npc new file mode 100644 index 0000000..578f0fe --- /dev/null +++ b/app/SabrehavenServer/data/npc/asima.npc @@ -0,0 +1,81 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# asima.npc: Datenbank fuer die Magiebedarfshändlerin Asima + +Name = "Asima" +Outfit = (138,97-70-94-76-0) +Home = [33220,32404,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Asima, student of arcane magic and right hand of Shalmar." +"job" -> "Shalmar's ears and eyesight have gotten really bad lately. I'm helping him with his magic store so he can focus on teaching spells." +"time" -> "It's %T right now." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=5 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=5 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=4 +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=5 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=5 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=4 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy %A spellbooks for %P gold?", Topic=4 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=6 +"vial" -> * +"flask" -> * + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=6,"yes" -> "You don't have any empty vials." +Topic=6 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/asrak.npc b/app/SabrehavenServer/data/npc/asrak.npc new file mode 100644 index 0000000..ef6eb2c --- /dev/null +++ b/app/SabrehavenServer/data/npc/asrak.npc @@ -0,0 +1,135 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# asrak.npc: Datenbank für den Minotaurenklingenmeister Asrak + +Name = "Asrak" +Outfit = (29,0-0-0-0-0) +Home = [32932,32074,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "I welcome you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait in patience, young %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your path be as straight as an arrow." + +"bye" -> "May your path be as straight as an arrow.", Idle +"farewell" -> * +"job" -> "I am the overseer of the pits and the trainer of the gladiators." +"shop" -> * +"name" -> "I am known as Asrak the Ironhoof." +"time" -> "It is %T." +"king" -> "I pledge no allegiance to any king, be it human or minotaurean." +"tibianus" -> * +"venore" -> "The city pays me well and those undisciplined gladiators need my skills and guidance badly." +"gladiator" -> "Those wannabe fighters are weak and most of them are unable to comprehend a higher concept like the Mooh'Tah." +"trainer" -> * +"minotaur" -> "In the ancient wars we lost much because of the rage. The one good thing is we lost our trust in the gods, too." +"gods" -> "By them we were imbued with the rage that almost costed our existence. By them we were used as pawns in wars that were not ours." +"mintwallin" -> "The city is only a shadow of what we could have accomplished without that curse of rage that the gods bestowed upon us." +"rage" -> "Rage is the legacy of Blog, the beast. To overcome it is our primal goal. The Mooh'Tah is our only hope of salvation and perfection." +"guidance" -> "Like all true minotaurean blademasters I am a warrior-philosopher of the Mooh'Tah." +"mooh'tah" -> "The Mooh'Tah teaches us control. It provides you with weapon, armor, and shield. It teaches you harmony and focus." +"harmony" -> "There is an elegant harmony in every thing done right. If you feel the harmony of an action you can sing its song." +"sing" -> "Each harmonic action has it own song. If you can sing it, you are in harmony with that action. This is where the minotaurean battlesongs come from." +"song" -> * +"battlesongs" -> "Each Mooh'Tah master focuses his skills on the harmony of battle. He is one with the song that he's singing with his voice or at least his heart." +"mooh'tah","master" -> "Mooh'Tah masters are the epitome of the minotaurean warrior-philosophers. Full in control, free of rage, focused in perfect harmony with their actions." +"warrior-philosopher" -> * +"general" -> "Your human generals are like their warriors. They lack the focus to be a true warrior." +"army" -> "Your human army might be big, but without skills. They are only sheep to be slaughtered." +"ferumbras" -> "To rely on magic is like to cheat fate. All cheaters will find their just punishment one day, and so will he." +"excalibug" -> "If it's truly a weapon to slay gods it might be worth to be sought for." +"news" -> "Focus on your own life, not on that of others." +"help" -> "I teach worthy warriors the way of the knight." +"monster" -> "Inferior creatures of rage, driven by their primitive urges. Only worthy to be noticed to test one's skills." +"dungeon" -> "The dungeons of your desires and fears are the only ones you really should fear and those you really have to conquer." +"thanks" -> "I hope you learned something." +"thank","you" -> * + +"offer" -> "I offer you the teachings of knighthood and the way of the paladin." +"training" -> * +"do","you","sell" -> "I am not a merchant, but a warrior." +"do","you","have" -> * +"weapon" -> "Make your will your weapon, and your enemies will perish." +"armor" -> "Courage is the only armor that shields you against rage and fear, the greatest dangers you will have to face." +"shield" -> "Your confidence shall be your shield. Nothing can penetrate that defence. No emotion will let you lose your focus." + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +Paladin,"spell" -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell" -> "Sorry, I only teach spells to knights and paladins." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level would you like to learn a spell?", Topic=2 +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "May your path be as straight as an arrow.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 + +Topic=3,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=3,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "May your path be as straight as an arrow.", Idle + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=3 +Topic=3,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 + +Topic=3 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=3 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=4 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=4 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=4 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "Return when you have enough gold." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/atrad.npc b/app/SabrehavenServer/data/npc/atrad.npc new file mode 100644 index 0000000..7b91794 --- /dev/null +++ b/app/SabrehavenServer/data/npc/atrad.npc @@ -0,0 +1,42 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Atrad" +Outfit = (152,77-113-132-94-3) +Home = [32077,32533,9] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",Burning>0 -> "Hehe. That's a good show, %N, with all the pyro effects. You got my attention. For a minute or so." +ADDRESS,"hi$",Burning>0 -> * +ADDRESS,"hello$",Burning=0 -> "What the hell are you doing here? Get out, you're not hot enough for my taste.", Idle +ADDRESS,"hi$",Burning=0 -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye, %N!", Idle +"farewell" -> * + +"addon",QuestValue(17562)<17 -> "You don't look like the one for who I should answer about my assassin katana." +"outfit",QuestValue(17562)<17 -> * + +"addon",QuestValue(17562)=17 -> "You managed to deceive Erayo? Impressive. Well, I guess, since you have come that far, I might as well give you a task, too, eh?", Topic=1 +"outfit",QuestValue(17562)=17 -> * +Topic=1,"yes" -> "Okay, listen. I don't have a list of stupid objects, I just want two things. A behemoth claw and a nose ring. Got that?", Topic=2 +Topic=1 -> "Maybe another time." +Topic=2,"yes" -> "Good. Come back when you have BOTH. Should be clear where to get a behemoth claw from. There's a horned fox who wears a nose ring. Good luck.", SetQuestValue(17562,18) +Topic=2 -> "Maybe another time." + +"ring",QuestValue(17562)=18,Count(5930)>=1,Count(5804)>=1 -> "I see you brought my stuff. Good. I'll keep my promise: Here's a katana in return.", DeleteAmount(5930, 1), DeleteAmount(5804, 1), SetQuestValue(17562,19), AddOutfitAddon(152,2), AddOutfitAddon(156,2), EffectOpp(13) +"mission",QuestValue(17562)=18,Count(5930)>=1,Count(5804)>=1 -> * +"task",QuestValue(17562)=18,Count(5930)>=1,Count(5804)>=1 -> * + +"ring",QuestValue(17562)=18 -> "I told you to come back when you have BOTH. A behemoth claw and a nose ring." +"mission",QuestValue(17562)=18 -> * +"task",QuestValue(17562)=18 -> * + +"mission",QuestValue(17562)=19 -> "I don't have any taks for you right now." +"task",QuestValue(17562)=19 -> * +} diff --git a/app/SabrehavenServer/data/npc/avar.npc b/app/SabrehavenServer/data/npc/avar.npc new file mode 100644 index 0000000..0b2e420 --- /dev/null +++ b/app/SabrehavenServer/data/npc/avar.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# avar.npc: Datenbank für den Abenteurer Avar Tar + +Name = "Avar Tar" +Outfit = (73,0-0-0-0-0) +Home = [33250,31764,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveler %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Only one chat at the same time, sorry." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"name" -> "I am Avar Tar, slayer of monsters, saviour of princesses, and defender of the weak." +"job" -> "I am a professional hero." +"time" -> "It's %T right now." +"king" -> "I am on a quest for the Thaian king ... as usual, of course." +"tibianus" -> * +"quest" -> * +"army" -> "Where the army fails a hero like me is needed." +"ferumbras" -> "I fought him serveral times, sometimes he killed me, sometimes I killed him, I would say we are even right now, but I am getting better and more powerful each day." +"excalibug" -> "I am sure it's hidden in a vault of the Nightmare Knights beneath the Plains of Havoc. I plan an expedition to go there and rout out the Ruthless Seven, but I have to save the world first." +"thais" -> "If I had the time I would restore peace in this once proud city, but there's too much to do before I can start that quest." +"tibia" -> "I've seen it all and done it all ... at least twice." +"carlin" -> "I saved the women there once or twice." +"news" -> "There is a great evil lurking beneath this isle ... and beneath the Plains of Havoc and in the ancient necropolis and beneath the Ghostlands ... well everywhere basically." +"rumors" -> * +"tibianus","talon" -> "I have looted plenty of them. However, it looks like this is not yours level business." +"tibianus","talon", level>100 -> "I think you already know about the demons. However I know what you didn't knew about them!" +} diff --git a/app/SabrehavenServer/data/npc/azil.npc b/app/SabrehavenServer/data/npc/azil.npc new file mode 100644 index 0000000..42dff33 --- /dev/null +++ b/app/SabrehavenServer/data/npc/azil.npc @@ -0,0 +1,105 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Azil.npc: Datenbank für den Rüstungshändler Azil + +Name = "Azil" +Outfit = (129,95-10-12-119-0) +Home = [33217,32431,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted %N! See the armors: harder than the scales of a dragon, lighter than a feather." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, o honoured customer.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, honoured customer. It was a pleasure to talk to you." + +"bye" -> "Good bye. Come back soon.", Idle +"job" -> "I sell various kinds of masterly crafted armor. The wares I offer are as numerous as the sand of the desert." +"shop" -> * +"name" -> "My name is Azil Ibn Izal." +"time" -> "It's %T right now, o honoured one." +"help" -> "I sell and buy armor, helmets, and shields." +"drefia" -> "O brave one! Before you go there, please make sure that you buy the best armor you can afford." +"thanks" -> "You are welcome, o richest of the wealthiest." +"thank","you" -> * + +"buy" -> "So, what do you need? I sell armor, helmets, shields, and trousers." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are armor, helmets, trousers, and shields." +"weapon" -> "You see me sad, but you have to ask another tradesman for that." +"helmet" -> "I am selling leather helmets, chain helmets, brass helmets, and viking helmets. What do you want?" +"armor" -> "I am selling leather armor, chain armor, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, brass shields, and plate shields. What do you want?" +"trousers" -> "I am selling chain legs and brass legs. What do you need?" +"legs" -> * + +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "Do you want to buy brass legs for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "Do you want to buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "Do you want to buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "Do you want to buy %A brass legs for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "Do you want to sell plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Oh thank you, most generous one. Here are your wares.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, but your purse is as empty as the eye socket of a ghoul." +Topic=1 -> "Maybe we can trade another day." + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your money. It was a pleasure to deal with you.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you don't own one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe we can trade another day." +} diff --git a/app/SabrehavenServer/data/npc/baaleal.npc b/app/SabrehavenServer/data/npc/baaleal.npc new file mode 100644 index 0000000..f549d8d --- /dev/null +++ b/app/SabrehavenServer/data/npc/baaleal.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# baaleal.npc: Datenbank für den Efreetgeneral Baa'leal + +Name = "Baa'leal" +Outfit = (51,0-0-0-0-0) +Home = [33048,32620,4] +Radius = 2 + +Behaviour = { + +ADDRESS,QuestValue(285)=0,"djanni'hah$",! -> "You know the code human! Very well then... What do you want, %N?",SetQuestValue(285,1) +ADDRESS,QuestValue(285)=0,! -> "A human! TAKE THIS!",SetQuestValue(285,1), Burning(150,4), EffectOpp(5), EffectMe(8),Idle + +ADDRESS,"hello$",QuestValue(278)=3,! -> "You are still alive, %N? Well, what do you want?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=3,! -> "Can't you see I am already talking to somebody here, %N? You civilians don't understand the concept of discipline at all, do you!", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP + +VANISH -> "Hail Malor!" + +"bye" -> "Stand down, soldier!", Idle +"farewell" -> * +"name" -> "I'm general Baa'leal. What do you want in Mal'ouquah?" +"general" -> * +"baa'leal" -> "That is GENERAL Baa'leal for you, human." +"job" -> "I am commander-in-chief of the armed forces of the UDLA, all branches of service. ...", + "Hence I'm responsible for all operations in the enemy's territory." +"udla" -> "Yes. The United Djinn Liberation Army. ...", + "The title has been given to our valiant armed forces in order to stress both the revolutionary focus of our agenda and the universalist nature of our political approach. ...", + "Don't ask me what that means. Wasn't my idea." + +"ubaid",QuestValue(286)=0,! -> "Ubaid told you to speak with me? Hmmm... maybe there is something you could help us with. Are you interested, human?",Topic=1 +"work",QuestValue(286)=0,! -> * +"operation",QuestValue(286)=0,! -> "Each mission and operation is a crucial step towards our victory! ...", + "Now that we speak of it ...", + "Since you are no djinn, there is something you could help us with. Are you interested, human?",Topic=1 +"mission",QuestValue(286)=0,! -> * + +Topic=1,"yes" -> "Well ... All right. You may only be a human, but you do seem to have the right spirit. ...", + "Listen! Since our base of operations is set in this isolated spot we depend on supplies from outside. These supplies are crucial for us to win the war. ...", + "Unfortunately, it has happened that some of our supplies have disappeared on their way to this fortress. At first we thought it was the Marid, but intelligence reports suggest a different explanation. ...", + "We now believe that a human was behind the theft! ...", + "His identity is still unknown but we have been told that the thief fled to the human settlement called Carlin. I want you to find him and report back to me. Nobody messes with the Efreet and lives to tell the tale! ...", + "Now go! Travel to the northern city Carlin! Keep your eyes open and look around for something that might give you a clue!", SetQuestValue(286,1) +Topic=1 -> "After all, you're just a human." + +"operation",QuestValue(286)>0,QuestValue(286)<3 -> "Did you find the thief of our supplies?", Topic=2 +"mission",QuestValue(286)>0,QuestValue(286)<3 -> * +"work",QuestValue(286)>0,QuestValue(286)<3 -> * +"thief",QuestValue(286)>0,QuestValue(286)<3 -> * + +Topic=2,"yes" -> "Finally! What is his name then?", Topic=3 +Topic=2 -> "Then go to Carlin and search for him! Look for something that might give you a clue!" + +Topic=3,"partos",QuestValue(286)=2,! -> "You found the thief! Excellent work, soldier! You are doing well - for a human, that is. Here - take this as a reward. ...", + "Since you have proven to be a capable soldier, we have another mission for you. ...", + "If you are interested go to Alesar and ask him about it.", Amount=6, Create(3035), SetQuestValue(286,3) +Topic=3,! -> "Hmmm... I don't think so. Return to Carlin and continue your search!" + +"operation",QuestValue(286)=3 -> "Did you already talk to Alesar? He has another mission for you!" +"mission",QuestValue(286)=3 -> * + +"mal'ouquah" -> "At the moment Mal'ouquah is our headquarter. However, I am already working on a cunning plan to move our base of operations deep into the enemy's territory." +"ashta'daramai" -> "Ashta'daramai is the enemy's base of operations. I am looking forward to the moment when we raise our flag there!" +"gabel" -> "He is weak. Much too weak to be our leader." +"king" -> "The UDLA does not serve a king because there isn't any. Of course, that is bound to change." +"djinn" -> "We are a race of warriors! We Efreets are destined to rule and to conquer." +"efreet" -> "We are the true djinn! We do not live in denial of our true nature like those damn liberals, the Marid." +"marid" -> "Nothing but a bunch of mealy-mouthed, mollycoddled wimps and milksops the lot of them. They may be superior in numbers, but we will win anyway because of our superior strategic thinking." +"malor" -> "Hail to our great leader!" +"human" -> "No offence, but your race is weak. You lack both the physical strength and the true warrior spirit. And worst of all, you have no strategic thinking." +"zathroth" -> "I understand he created us. Must have been a great general." +"tibia" -> "It is our mission to achieve total and decisive dominion of this world within two years. Well perhaps ... three. Always be realistic, that's what I say." +"daraman" -> "Damn that liberal peacenik, that treacherous mealy-mouthed double-faced good-for-nothing surrender monkey! ...", + "He has infected this proud people's minds with his peace-for-all blabber." +"darashia" -> "The humans living in the northern deserts used to be nomads. Even though they are just humans they used to be respectable fighters. ...", + "However, now they are living in this city they have grown fat and decadent. They will be easy prey." +"scarab" -> "Impressive animals. I have this idea of training them as battle steeds. Imagine this: Djinns mounted on scarabs! With a battalion of those I would crush the enemy in the blink of an eye!" +"edron" -> "They say the humans have built some big cities over there. I am looking forward to see them burn." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That old city has some impressive defensive structures. But I swear I will bring it down one day... I have a cunning plan already! ...", + "I am thinking of a huge wooden camel." +"pharaoh" -> "Ankrahmun's pharaoh apparently believes himself to be some sort of god. Ah well. A solid blow with my scimitar will bring him back to earth soon enough!" +"palace" -> "I suppose the palace is where the pharaoh resides. I have a distinct feeling I shall see it burn rather soon." +"ascension" -> "Apparently, ascension is what the followers of the pharaoh are after. No idea what exactly that is, though." +"rah" -> "Spare me that pseudo-theological hogwash." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "It was an excellent idea to build our headquarter in the mountains of kha'zeel. Easy to defend, you know. Too bad the enemy had the same idea." +"kha'labal" -> "Kha'labal? Yes, it was me who devastated it. Couldn't leave it to the enemy, you see? We had to destroy it in order to save it!" +"war" -> "War is the father of things, and I live and breathe it. Ok, it's a tad bit silly that we are forced to fight against our own kind, but as a good soldier I will do my duty! ...", + "And if I hear anybody talking about 'peace' he will be court-martialled and summarily executed! Or vice versa!" +"melchior" -> "Melchior! I remember that greedy little civilian. I would have court-martialled him, but I suppose it is just as well the way it is." +"alesar" -> "Ah yes, Alesar! Excellent smith, that man!" +"fa'hradin" -> "He is Gabel's lieutenant and confidant. He is a powerful wizard, one has to admit that - and that's the only reason he is still alive. Without all his magical mumbo jumbo we would have long since won this war." +"lamp" -> "We sleep in those lamps. I like them - they are small and functional. We do not need cozy beds and fluffy duvets like decadent humans." + + + + + +#"mission", Questvalue(###)=### -> "A volunteer, hm? Well ... All right. You may only be a human, but you do seem to have the right spirit, and I like that. That's what we need around here!","Listen. Since our base of operations is set in this isolated spot we depend on supplies from outside. These supplies are crucial for us to win the war.","Unfortunately, it has happened that some of our supplies have disappeared on their way to this fortress. At first we thought it was the Marid, but intelligence reports suggest a different explanation. We now believe that a group of humans was behind the theft.","Unfortunately, we do not have much further specific information. All we know is that the thieves' hideout is somewhere in a northern city. However, you are a human, so you might stand a good chance to find those thieving jerks. I want them punished. Nobody messes with the Efreet and lives to tell the tale!","Now go! Travel to the northern cities! Look around for something that might give you a clue!", SetQuestValue +#"mission", Questvalue(###)=### -> "You found the thieves? Excellent work, soldier! You are doing well - for a human, that is. Here - take this. Since you are not a regular member of the UDLA you have deserve some compensation.", Create (###,###), SetQuestValue(###,###) +#"mission", Questvalue(###)=### -> "Still feeling adventurous, are we? Well, as I matter of fact there is something else you could do for us. Something very special.","Listen: We have sent a spy Ashta'Daramai, our enemy's fortress. He was on a mission to steal certain documents for us. Unfortunately, we have lost contact with him.","We must find out what happened to him and, more importantly, to the documents. Go to the to the fortress and try to find him. Above all, find the documents and bring them to me.","A word of advice: Our spy entered the fortress through a network of underground tunnels. Perhaps you should sneak into Ashta'Daramai using the same route. That way he should be easy to find.","Go now and don't come back without the documents.", SetQuestValue (###,###) +#"mission", Questvalue(###)=### -> "Well, blast my buttocks with a blunderbuss###! The documents! Outstanding work, soldier! Malor will be pleased!","It appears you do deserve our trust after all. Tell you what, soldier - I will order that stubborn Alesar to fully cooperate with you. He's got some pretty nice items to sell. He won't like it, of course, but I'm sure he will do as he is told. He is a namby-pamby civilian after all.","Oh, and ask him about a mission, too. I think he and Malor were talking about a plan.", SetQuestValue(###,###) + +} diff --git a/app/SabrehavenServer/data/npc/bambi.npc b/app/SabrehavenServer/data/npc/bambi.npc new file mode 100644 index 0000000..88b9b32 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bambi.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bambi.npc: Datenbank für die Wächterin Bambi Bonecrusher (Carlin) + +Name = "Bambi Bonecrusher" +Outfit = (139,96-19-68-95-0) +Home = [32341,31749,7] +Radius = 2 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/app/SabrehavenServer/data/npc/bansheequeen.npc b/app/SabrehavenServer/data/npc/bansheequeen.npc new file mode 100644 index 0000000..85404c8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bansheequeen.npc @@ -0,0 +1,100 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bansheequeen.npc: Datenbank für die Bansheequeen + +Name = "The Queen of the Banshee" +Outfit = (78,0-0-0-0-0) +Home = [32260,31863,14] +Radius = 6 + +Behaviour = { +ADDRESS,"hello$",QuestValue(17561)>4,! -> "Be greeted, dear visitor. Ahhh... I can sense darkness inside your soul... are you a follower of Zathroth?" +ADDRESS,"hi$",QuestValue(17561)>4,! -> * +ADDRESS,"hello$",! -> "Be greeted, dear visitor. Come and stay ... a while." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait as patiently as death is waiting for you!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes, flee from death. But know it shall be always one step behind you." + + +"bye" -> "We will meet again.", Idle +"farewell" -> * + +"name" -> "It hurts me to even think about my mortal past. Its long lost and forgotten. So don't ask me about it!" +"job" -> "It is my curse to be the eternal guardian of this ancient place." +"place" -> "It served as a temple, a source of power and ... as a sender for an ancient race in time long gone by and forgotten." +"race" -> "The race that built this edifice came to this place from the stars. They ran from an enemy even more horrible than even themselves. But they carried the seed of their own destruction in them." +"seed" -> "This ancient race was annihilated by its own doings, that's all I know. Aeons have passed since then, but the sheer presence of this complex is still defiling and desecrating this area." +"destruction" -> * +"complex" -> "Its constructors were too strange for you or even me to understand. We cannot know what this ... thing they have built was supposed to be good for. All I can feel is a constant twisting and binding of souls, though that is probably only a side-effect." +"ghostlands" -> "The place you know as the Ghostlands had a different name once ... and many names thereafter. Too many for me to remember them all." +"banshee" -> "They are my maidens. They give me comfort in my eternal vigil over the last seal." +"seal" -> "I am the guardian of the SEVENTH and final seal. The seal to open the last door before ... but perhaps it is better you see it with your own eyes." +"guardian" -> * +"seventh",level<60,! -> "You are not experienced enough to master the challenges ahead or to receive knowledge about the seventh seal. Go and learn more before asking me again." +"seventh",level>59,! -> "If you have passed the first six seals and entered the blue fires that lead to the chamber of the seal you might receive my kiss ... It will open the last seal. Do you think you are ready?", topic=2 +"last" -> * + +"kiss",PZBlock,! -> "You have spilled too much blood recently and the dead are hungry for your soul. Perhaps return when you regained you inner balance." + +"kiss",topic=8 , QuestValue(11) < 1 -> "Are you prepared to receive my kiss, even though this will mean that your death as well as a part of your soul will forever belong to me, my dear?", Topic=1 + +"kiss", QuestValue(11) > 0 -> "You have already received my kiss. You should know better then to ask for it." +"kiss" -> "To receive my kiss you have to pass all other seals first." +"yes",topic=1 -> "So be it! Hmmmmmm...",SetQuestValue(11,1),SetQuestValue(12,QuestValue(12)+1),Teleport(32202,31812,8), EffectOpp(14) +"no",topic=1 -> "Perhaps it is the better choice for you, my dear." + +"yes",topic=2,QuestValue(4)=1 -> "Yessss, I can sense you have passed the seal of sacrifice. Have you passed any other seal yet?", topic=3 +"yes",topic=2,QuestValue(4)<1 -> "You have not passed the seal of sacrifice yet. Return to me when you are better prepared." +"no",topic=2 -> "Then try to be better prepared next time we meet." + +"yes",topic=3,QuestValue(5)=1 -> "I sense you have passed the hidden seal as well. Have you passed any other seal yet?", topic=4 +"yes",topic=3,QuestValue(5)<1 -> "You have not found the hidden seal yet. Return when you are better prepared." +"no",topic=3 -> "Then try to be better prepared next time we meet." + + +"yes",topic=4,QuestValue(6)=1 -> "Oh yes, you have braved the plagueseal. Have you passed any other seal yet?", topic=5 +"yes",topic=4,QuestValue(6)<1 -> "You have not faced the plagueseal yet. Return to me when you are better prepared." +"no",topic=4 -> "Then try to be better prepared next time we meet." + + +"yes",topic=5,QuestValue(7)=1 -> "Ah, I can sense the power of the seal of demonrage burning in your heart. Have you passed any other seal yet?", topic=6 +"yes",topic=5,QuestValue(7)<1 -> "You are not filled with the fury of the imprisoned demon. Return when you are better prepared." +"no",topic=5 -> "Then try to be better prepared next time we meet." + +"yes",topic=6,QuestValue(9)=1 -> "So, you have managed to pass the seal of the true path. Have you passed any other seal yet?", topic=7 +"yes",topic=6,QuestValue(9)<1 -> "You have not found your true path yet. Return when you are better prepared." +"no",topic=6 -> "Then try to be better prepared next time we meet." + +"yes",topic=7,QuestValue(10)=1 -> "I see! You have mastered the seal of logic. You have made the sacrifice, you have seen the unseen, you possess fortitude, you have filled yourself with power and found your path. You may ask me for my kiss now.", topic=8 +"yes",topic=7,QuestValue(10)<1 -> "You have not found your true path yet. Return to meh when you are better prepared." +"no",topic=7 -> "Then try to be better prepared next time we meet." + + + +"spectral","dress" -> "Your wish for a spectral dress is silly. Allthough I will grant you the permission to take one. My maidens left one in a box in a room, directly south of here.",SetQuestValue(327,1) + +"mission",QuestValue(17561)=5 -> "Say... I have been longing for something for an eternity now... if you help me retrieve it, I will reward you. Do you consent to this arrangement?", Topic=9 +"task",QuestValue(17561)=5 -> * +"outfit",QuestValue(17561)=5 -> * +"addon",QuestValue(17561)=5 -> * +Topic=9,"yes" -> "Listen... there are no blooming flowers down here and the only smell present is that of death and decay. ...", + "I wish that I could breathe the lovely smell of beautiful flowers just one more time, especially those which elves cultivate. ...", + "Could you please bring me 50 holy orchids?", Topic=10 +Topic=9 -> "Maybe next time we meet." +Topic=10,"yes" -> "Thank you. I will wait for your return.", SetQuestValue(17561,6) +Topic=10 -> "Maybe next time we meet." + +"holy","orchid",QuestValue(17561)=6 -> Type=5922, Amount=50, "Have you really brought me 50 holy orchids?", Topic=11 +"mission",QuestValue(17561)=6 -> * +"task",QuestValue(17561)=6 -> * +Topic=11,"yes",Count(Type)>=Amount -> "Thank you! You have no idea what that means to me. As promised, here is your reward... as a follower of Zathroth, I hope that you will like this accessory.", Delete(Type), SetQuestValue(17561,7), AddOutfitAddon(149,1), AddOutfitAddon(145,1), EffectOpp(13) +Topic=11,"yes" -> "You don't have it. Return when you are better prepared." +Topic=11 -> "Maybe next time we meet." + +"mission",QuestValue(17561)=7 -> "%N follower of Zathroth I have no more rewards for you." +"task",QuestValue(17561)=7 -> * +"outfit",QuestValue(17561)=7 -> * +"addon",QuestValue(17561)=7 -> * +} diff --git a/app/SabrehavenServer/data/npc/barbara.npc b/app/SabrehavenServer/data/npc/barbara.npc new file mode 100644 index 0000000..acce486 --- /dev/null +++ b/app/SabrehavenServer/data/npc/barbara.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# barbara.npc: Datenbank für die Wachfrau Barbara + +Name = "Barbara" +Outfit = (139,78-52-64-115-0) +Home = [32320,31752,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hail$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"salutations$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","queen",! -> "Wait for your audience %N!" +BUSY,"hail$","queen",! -> "Wait for your audience %N!" +BUSY,"salutations$","queen",! -> "Wait for your audience %N!" +BUSY,"hi$","queen",! -> "Wait for your audience %N!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/app/SabrehavenServer/data/npc/barney.npc b/app/SabrehavenServer/data/npc/barney.npc new file mode 100644 index 0000000..39907c1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/barney.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Barney" +Outfit = (128,20-39-115-114-0) +Home = [32141,32928,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/bashira.npc b/app/SabrehavenServer/data/npc/bashira.npc new file mode 100644 index 0000000..5e1239f --- /dev/null +++ b/app/SabrehavenServer/data/npc/bashira.npc @@ -0,0 +1,126 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bashira.npc: Datenbank fuer die Haendlerin Bashira (Elfenstadt) + +Name = "Bashira" +Outfit = (144,78-62-97-76-0) +Home = [32669,31655,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait one moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I sell various equipment and buy some stuff." +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, parchments, documents, watches, various sources of light, fishing rods and sixpacks of worms." +"goods" -> * +"light" -> "I sell torches, candelabra, and oil." +"name" -> "I am Bashira Darkmark." + +"carlin" -> "Carlin has some capable fighters, allthough they lack the grace of an elf." +"thais" -> "The people of thais boast about their mighty kingdom, but eventually their short lives will doom everything they buld." +"venore" -> "Their merchants have no patience and all to fast they loose their masks of friedlyness." +"roderick" -> "His presence here is a waste of space and talking to or even about him a waste of time." +"olrik" -> "He is quite amusing for a human." + +"elves" -> "That's our race." +"dwarfs" -> "They have some talent in mining and smithing." +"humans" -> "They have nothing to give us." +"troll" -> "They are lazy and clumsy. We should use dwarfs instead." + +"cenath" -> "Their magic is almost as impressive as their egos." +"kuridai" -> "Without us and our tools nothing would work in this town." +"deraisim" -> "Useless leafeaters." +"abdaisim" -> "They left; perhaps we should do that, too." +"teshial" -> "A stupid Cenath-myth." +"ferumbras" -> "He may scare the treedwellers or the big-mouthes above, but not the Kuridai." +"crunor" -> "One god of many. They are all alike and of no use." + +"time" -> "Buy a watch." +"food" -> "I am not dealing with food." + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"bag" -> Type=2857, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2865, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=2 +"waterskin" -> Type=2901, Data=1, Amount=1, Price=10, "Do you want to buy a waterskin for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"bucket" -> Type=2873, Data=0, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Data=0, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelab" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2857, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2865, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=2 +%1,1<%1,"waterskin" -> Type=2901, Data=1, Amount=%1, Price=10*%1, "Do you want to buy %A water skins for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Data=0, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Data=0, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have so much money." +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Here it is. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "You don't have so much money." +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you don't have one." +Topic=3 -> "Maybe next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep Tibia litter free." + +"buy" -> "I have shovels, picks, scythes, bags, ropes, backpacks, plates, scrolls, watches, some lightsources, and other stuff." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"stuff" -> "Water hoses, pitchforks, presents, buckets, bottles, and the like." +} diff --git a/app/SabrehavenServer/data/npc/basilisk.npc b/app/SabrehavenServer/data/npc/basilisk.npc new file mode 100644 index 0000000..10393bc --- /dev/null +++ b/app/SabrehavenServer/data/npc/basilisk.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# basilisk.npc: Datenbank für den Basilisken + +Name = "Basilisk" +Outfit = (28,0-0-0-0-0) +Home = [32641,31943,15] +Radius = 2 + +Behaviour = { +ADDRESS,"a",! -> EffectMe(9), Idle +ADDRESS,"b",! -> * +ADDRESS,"c",! -> * +ADDRESS,"d",! -> * +ADDRESS,"e",! -> * + +ADDRESS,"f",! -> EffectMe(13), Idle +ADDRESS,"g",! -> * +ADDRESS,"h",! -> * + +ADDRESS,"i",! -> EffectMe(14), Idle +ADDRESS,"j",! -> * +ADDRESS,"k",! -> * +ADDRESS,"l",! -> * +ADDRESS,"m",! -> * + +ADDRESS,"n",! -> EffectMe(15), Idle +ADDRESS,"o",! -> * +ADDRESS,"p",! -> * +ADDRESS,"r",! -> * +ADDRESS,"s",! -> * + +ADDRESS,"t",! -> EffectMe(17), Idle +ADDRESS,"u",! -> * +ADDRESS,"v",! -> * +ADDRESS,"w",! -> * +ADDRESS,"y",! -> * + +ADDRESS,! -> Idle + +BUSY,! -> NOP + +VANISH,! -> NOP +} diff --git a/app/SabrehavenServer/data/npc/baxter.npc b/app/SabrehavenServer/data/npc/baxter.npc new file mode 100644 index 0000000..c87b507 --- /dev/null +++ b/app/SabrehavenServer/data/npc/baxter.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# baxter.npc: Datenbank für den Burgwächter Baxter + +Name = "Baxter" +Outfit = (131,96-29-29-115-0) +Home = [32322,32188,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "LONG LIVE KING TIBIANUS!" +ADDRESS,"hi$",! -> "LONG LIVE KING TIBIANUS!" +ADDRESS,! -> Idle +BUSY,"hello$" -> "Cant you see I am busy, eh?!." +BUSY,"hi$" -> "Cant you see I am busy, eh?!." +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "It is rumoured that Ferumbras is planning a new attack on town." +"how","are","you"-> "I am healthy and vigilant." +"sell" -> "Visit Tibia's shopkeepers to buy their fine wares." +"king" -> "King Tibianus III is our wise and just leader!" +"leader" -> * +"job" -> "I am a proud member of the king's army. It is my duty to guard the castle." +"army" -> "Our brave army, which protects our city, consists of three battlegroups." +"guard" -> * +"battlegroup" -> "There are the dogs of war, the red guards, and the silver guards." +"castle" -> "His Royal Highness ordered the castle to be open for all his subjects." +"subject" -> "We all live under the benevolent guidance of our king." +"dogs","of","war"-> "They are our main army." +"red","guard" -> "They are our special forces. Some serve as cityguards, others as secret police." +"secret","police"-> "Ask a higher offical about that." +"silver","guard" -> "The best sorcerers, paladins, knights, or druids of our forces are choosen to serve as silver guards. They are the bodyguards of the king." +"city" -> "Now that the king returned, we will clean the city from all scum." +"scum" -> "To much scum roams our streets in our days, the red guards will take care of them." +"stutch" -> "He is soldier in the silver guard." +"harsky" -> "He is soldier in the silver guard." +"bozo" -> "The royal jester. I dont think he is funny." +"sam" -> "He is a fine blacksmith. Almost all our weapons are made by him." +"gorn" -> "An old friend of mine. He was once a great warrior and adventurer, now he is running a shop." +"benjamin" -> "He was one of the king's best generals, now he is a bit ...uhm... forgetful." +"excalibug" -> "Gorn and I searched for this weapon in the darkest corners of each dungeon, but found nothing." +"partos" -> "He was wanted for a long time and got caught stealing some time ago.", Topic=1 +Topic=1,"fruit" -> "I understand he was stealing some fruit, he is obsessed with, and got incautious." +Topic=1 -> "What has this to do with this Partos guy?" +"chester" -> "This man is paranoid, but I guess that is useful in his job." +"tbi" -> "There is almost nothing known about that organization." +"work" -> "We have a rat problem in the sewers. In the name of our glorious king I am paying 1 blinking piece of gold for every freshly killed rat you bring to me." +"mission" -> * +"quest" -> * +"rat" -> Type=3994, Amount=1, Price=1, "Do you bring a freshly killed rat for a bounty of %P gold?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=1*%1, "Do you want to deliver me %A rats for a bounty of %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your reward. You will become a great warrior some day.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Look like it wasn't as dead as you thought ... it's gone." +Topic=2 -> "Come on. Don't waste my time with your jests." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/app/SabrehavenServer/data/npc/beatrice.npc b/app/SabrehavenServer/data/npc/beatrice.npc new file mode 100644 index 0000000..86045b1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/beatrice.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# beatrice.npc: Datenbank fuer die Ausruestungshaendlerin Beatrice + +Name = "Beatrice" +Outfit = (136,96-102-69-95-0) +Home = [33214,31803,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hiho, and ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You're next, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"name" -> "I am called Beatrice." +"job" -> "My job is to sell all kind of useful equipment." +"time" -> "It's %T right now." +"king" -> "I have seen him once. What a handsome man he is." +"tibianus" -> * +"army" -> "I supply them with some basic stuff." +"ferumbras" -> "I vaguely remember that name." +"excalibug" -> "A myth like the screwdriver of Kurik or the endless vial of manafluid." +"thais" -> "We are no longer in need to be supplied from there." +"tibia" -> "I don't like travelling much. I prefer to live in the safety of our city." +"carlin" -> "Though they rebelled against our king it's said that the city is very lovely." +"edron" -> "It's the best place to live at." +"news" -> "There are always rumors about the dangers in the far north of Edron." +"rumors" -> * + +"offer" -> "My inventory is large, just have a look at the blackboard." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2861, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2869, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy a watch for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"lamp" -> Type=2914, Amount=1, Price=8, "Do you want to buy a lamp for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"basket" -> Type=2855, Amount=1, Price=6, "Do you want to buy a basket for %P gold?", Topic=1 +"trap" -> Type=3481, Amount=1, Price=280, "Do you want to buy a trap for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2861, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2869, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"lamp" -> Type=2914, Amount=%1, Price=8*%1, "Do you want to buy %A lamps for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"basket" -> Type=2855, Amount=%1, Price=6*%1, "Do you want to buy %A baskets for %P gold?", Topic=1 +%1,1<%1,"trap" -> Type=3481, Amount=%1, Price=280*%1, "Do you want to buy %A traps for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 + + +"sell","watch" -> Type=2906, Amount=1, Price=6, "Do you want to sell a watch for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=15, "Do you want to sell a rope for %P gold?", Topic=2 +"sell","scythe" -> Type=3453, Amount=1, Price=12, "Do you want to sell a scythe for %P gold?", Topic=2 +"sell","pick" -> Type=3456, Amount=1, Price=15, "Do you want to sell a pick for %P gold?", Topic=2 +"sell","shovel" -> Type=3457, Amount=1, Price=8, "Do you want to sell a shovel for %P gold?", Topic=2 +"sell","mirror" -> Type=3463, Amount=1, Price=10, "Do you want to sell a mirror for %P gold?", Topic=2 +"sell","rod" -> Type=3483, Amount=1, Price=40, "Do you want to sell a fishing rod for %P gold?", Topic=2 +"sell","inkwell" -> Type=3509, Amount=1, Price=8, "Do you want to sell an inkwell for %P gold?", Topic=2 +"sell","sickle" -> Type=3293, Amount=1, Price=3, "Do you want to sell a sickle for %P gold?", Topic=2 +"sell","crowbar" -> Type=3304, Amount=1, Price=50, "Do you want to sell a crowbar for %P gold?", Topic=2 +"sell","trap" -> Type=3481, Amount=1, Price=75, "Do you want to sell a trap for %P gold?", Topic=2 + +"sell",%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=6*%1, "Do you want to sell %A watches for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=15*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 +"sell",%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to sell %A scythes for %P gold?", Topic=2 +"sell",%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=15*%1, "Do you want to sell %A picks for %P gold?", Topic=2 +"sell",%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=8*%1, "Do you want to sell %A shovels for %P gold?", Topic=2 +"sell",%1,1<%1,"mirror" -> Type=3463, Amount=%1, Price=10*%1, "Do you want to sell %A mirrors for %P gold?", Topic=2 +"sell",%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=40*%1, "Do you want to sell %A fishing rods for %P gold?", Topic=2 +"sell",%1,1<%1,"inkwell" -> Type=3509, Amount=%1, Price=8*%1, "Do you want to sell %A inkwells for %P gold?", Topic=2 +"sell",%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=3*%1, "Do you want to sell %A sickles for %P gold?", Topic=2 +"sell",%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=50*%1, "Do you want to sell %A crowbars for %P gold?", Topic=2 +"sell",%1,1<%1,"trap" -> Type=3481, Amount=%1, Price=75*%1, "Do you want to sell %A traps for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/beholder.npc b/app/SabrehavenServer/data/npc/beholder.npc new file mode 100644 index 0000000..7cfa538 --- /dev/null +++ b/app/SabrehavenServer/data/npc/beholder.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# beholder.npc: Datenbank für den Bibliotheksbeholder (Elfenstadt) + +Name = "A Wrinkled Beholder" +Outfit = (17,0-0-0-0-0) +Home = [32788,31690,13] +Radius = 10 + +Behaviour = { +ADDRESS,"hello$",! -> "What is this? An optically challenged entity called %N. How fascinating!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait. I will eat you later, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Strange entity. I will record this encounter." + +"bye" -> "Wait right there. I will eat you after writing down what I found out.", Idle +"farewell" -> * +"job" -> "I am the great librarian." +"name" -> "I am 486486 and NOT 'Blinky' as some people called me ... before they died." +"blinky" -> "How interesting you are that stupid. Let me apply this on you and see how long you last", Burning(10,25), EffectOpp(5), EffectMe(8) +"tibia" -> "It's 1, not 'Tibia', silly." +"ab'dendriel" -> "I heard that elves moved in upstairs." +"elves" -> "These fools and their superstitious life cult don't understand anything of importance." +"humans" -> "Good tools to work with ... After their death, that is." +"orcs" -> "Noisy pests." +"minotaurs" -> "Their mages are so close to the truth. Closer then they know and closer then it's good for them." +"god" -> "They will mourn the day they abandoned us." +"death" -> "Yes, yes, I will kill you soon enough, now let me continue my investigation on you." +"numbers" -> "Numbers are essential. They are the secret behind the scenes. If you are a master of mathematics you are a master over life and death." +"library" -> "It's a fine library, isn't it?" +"books" -> "Our books are written in 469, of course you can't understand them." +"469" -> "The language of my kind. Superior to any other language and only to be spoken by entities with enough eyes to blink it." +"cyclops" -> "Uglyness incarnate. One eye! Imagine that! Horrible!" +"excalibug" -> "Only inferior species need weapons." +} diff --git a/app/SabrehavenServer/data/npc/benjamin.npc b/app/SabrehavenServer/data/npc/benjamin.npc new file mode 100644 index 0000000..9800d77 --- /dev/null +++ b/app/SabrehavenServer/data/npc/benjamin.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# benjamin.npc: Datenbank für den Postmann Benjamin + +Name = "Benjamin" +Outfit = (128,116-79-117-76-0) +Home = [32350,32219,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello. How may I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking to a customer, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was a pleasure to help you %N." + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + + +"kevin" -> "That name sounds familiar... who might that be..." +"postner" -> * +"postmasters","guild" -> "Hm, I think I heard about that guild... oh wait, I am a member!" +"join" -> "Uh... oh... Uhm... Join what?" +"headquarter" -> "Its just... I mean... there was that road, oh yes, its that house at that road." + + +"measurements",QuestValue(234)>0,QuestValue(236)<1 -> "Oh they dont change that much since in the old days as... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(236,1) + +"job" -> "I am working here at the post office. If you have questions about the Royal Tibia Mail System or the depots ask me." +"office" -> "I am always in my office. You are welcome at any time." +"name" -> "My name is Benjamin." +"time" -> "Now it's %T. Maybe you want to buy a watch?" +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all tibian citizens. Hail our king!" +"king" -> "Oops, the king? I... can't remember his name..." +"tibianus" -> "Ah, King Tibianus, our wise ruler. He is sick for some time, isn't he?" +"quentin" -> "Ooooh, nice man, visits me often... I think." +"lynda" -> "She is SO pretty!" +"harkath" -> "Oh, young Harkath will be a fine warrior some day." +"army" -> "TO THE ARMS! MAN THE WALLS! FERUMBRAS IS NEAR!", Idle +"ferumbras" -> * +"general" -> * +"sam" -> "Ham? No thanks, I ate fish already." +"frodo" -> "Frodo... Frodo... ? Uhm... isn't that the man that brings me food at lunchtime?" +"gorn" -> "He sells equipment." +"elane" -> "Oh, she lives next door. I think she's a dentist, I sometimes hear some cries." +"muriel" -> "This Muriel has a lot of correspondence." +"gregor" -> "Never heared of him." +"marvik" -> "He is always talking of healing me but I am fine... I fear he is a little nuts, poor man." +"bozo" -> "He hangs around here quite often. He claimes, I inspire him." +"baxter" -> "This naughty child, always stealing apples!" +"sherry" -> "I don't drink alcohol while on duty." +"lugri" -> "NO! NO! NO! GO AWAY!.", Idle +"excalibug" -> "I can't remember that someone named like that lives here." +"news" -> "Sorry, I don't read the letters we transmit." +"thais" -> "This is the town you are currently in." +"carlin" -> "You can sent letters and parcels to Carlin." +"xodet" -> "The young sorcerer is a good businessman." +"quero" -> "I love his music! He is my best friend and I visit him as often as I can." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/berenice.npc b/app/SabrehavenServer/data/npc/berenice.npc new file mode 100644 index 0000000..79641cf --- /dev/null +++ b/app/SabrehavenServer/data/npc/berenice.npc @@ -0,0 +1,66 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Berenice" +Outfit = (140,5-87-104-106-0) +Home = [32359,32812,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, what can I do for you? If you're interested in the explorer society, just ask me." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the local representative of the explorer society in Liberty Bay." +"me" -> * +"explorer society" -> "Our noble society is dedicated to explore the unknown. No location is too remote for our members to travel there ...", + "No beast is too wild to be hunted. No treasure buried too deep to be unearthed ...", + "Only the most dedicated and fearless adventurers may join our ranks." +"rank",QuestValue(300)>0,QuestValue(300)<4 -> "You are a novice of the explorer society." +"rank",QuestValue(300)>3,QuestValue(300)<7 -> "You are a journeyman of the explorer society." +"rank",QuestValue(300)>6,QuestValue(300)<9 -> "You are a relic hunter of the explorer society." +"rank",QuestValue(300)>8 -> "You are an explorer of the explorer society." +"liberty bay" -> "For an outpost it is quite well organised and comfortable. Uhm ... not that a true explorer would give much about comfort." +"outpost" -> "Sadly the governor is too occupied with other issues, so his support of our noble efforts is only nominal. However, our own resources are large enough to maintain our researches here on our own." +"governor" -> * +"bases" -> "Currently we maintain public bases in Port Hope and Northport. We have also established this humble outpost here." +"researches" -> "The local flora and fauna hold surely some surprises for us. Also the history of the isles is worth some closer investigation. Finally there is the unknown sea and its depths that hide secrets yet to be discovered." +"pirates" -> "Pirates are a pain. Constantly we lose supplies and important papers, that document our progress here, due to their inroads on our ships. The military really has to eradicate this pest once and for all." +"quaras" -> "The quara as a race are a phenomenon! There are either different types of quara or they are able to reshape and upgrade their bodies in some way ...", + "To discover the secret magic behind such techniques would be invaluable." +"voodoo" -> "The magic of this isle has its twists. That was the easy part for our mages to find out. The more difficult part will be to figure out why it is that way and what it means for users of magic and for ordinary people ...", + "In the light of this knowledge this 'voodoo' is worth some closer researches." +"cult" -> "Sadly the town is full of superstitious rumours. Although we appreciate folklore now and then, we first have to accomplish the scientific part of investigations ...", + "So we have no time for rumours about a hidden cult that probably does not exist at all. ...", + "But if you should find anything that proves their existence, I'll buy it from you." +"superstitious" -> "That is one of the things we have to explore after all." +"sugar" -> "It is astonishing how fertile the soil here seems to be for sugar canes. There is something special about the isles." +"plantations" -> "Well, there are some plantations in the western part of the isle." +"rum" -> "I send some rum to the other bases of our society each month. We like to taste exotic beverages now and then ...", + "That is one of the things we have to explore after all." +"venore" -> "Venore is a centre of commerce. It has only little to offer to our efforts to explore and to gain further knowledge ...", + "Sometimes though, the generous tradesmen sponsor some of our missions and now and then we can sell some of our knowledge or recovered artefacts to them." +"thais" -> "Thais is a great city, but not such a centre of learning and knowledge like Edron." +"king" -> "The king is a great supporter and a honourary member of our society." +"djinn" -> "Djinns are an extremely interesting topic. Luckily you have just found an expert in djinn lore! To keep matters short, let me only tell you the most notable facts: ...", + "Have you known that there are green and blue djinns? Well that's because there are no less than two types of djinns ...", + " Which does mean that some are green and others are blue! ...", + " I can not stress enough the relevance of the fact that djinns have no need for shoes at all. That leads to astounding facts like ...", + " Without shoes you'd think a society would concentrate on some kinds of gloves, but no! ...", + " Which leads to a completely shoeless society that is divided in two colours! ...", + "I see the implication of this fact makes you sweat and your face turn greenish, at last someone who can comprehend the significance of my studies! ...", + "If you ever want to hear more about djinns, just ask me!" +"ferumbras" -> "This evil wizard once maintained a centre of power here on those isles. After his demise, the Edron academy sealed that place and is extremely secretive about it ...", + "Although it is understandable, it is still an unacceptable hindrance to free research." +"Admiral Wyrmslicer" -> "The admiral is of little help to us. Instead of providing us troops for protection, he is more concerned that we could draw the attention of imagined enemies on the settlement or destroy something." +"Charlotta" -> "That old woman was of some help in cataloguing the local fauna. Still I think she withholds some vital knowledge about local plants." +"Eleonore" -> "This young lady has probably more interest in make-up than in education." +"Theodore Loveless" -> "Mr. Loveless is a man of education and style. He certainly knows how to run festivities of all kinds." +"Chondur" -> "Chondur ... Chondur ... I really don't know where to put that name. Some local celebrity I guess." +"Raymond Striker" -> "This Striker is one of the most dangerous pirates. The society has put an additional bounty on his head." +} diff --git a/app/SabrehavenServer/data/npc/bertram.npc b/app/SabrehavenServer/data/npc/bertram.npc new file mode 100644 index 0000000..92f0340 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bertram.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Bertram" +Outfit = (132,57-114-0-114-0) +Home = [32355,32797,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye, %N.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/bezil.npc b/app/SabrehavenServer/data/npc/bezil.npc new file mode 100644 index 0000000..f12b748 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bezil.npc @@ -0,0 +1,114 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bezil.npc: Datenbank für die Händlerin Bezil + +Name = "Bezil" +Outfit = (160,116-79-117-57-0) +Home = [32657,31909,9] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$","bezil",! -> "Hiho, Bezil at your service, %N." +ADDRESS,"hi$","bezil",! -> * +ADDRESS,"hiho$","bezil",! -> * +ADDRESS,"hello$",! -> "Are you talking to me, %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","bezil",! -> "Hey, I am busy. I'll be with you in a minute, %N.", Queue +BUSY,"hi$","bezil",! -> * +BUSY,"hiho$","bezil",! -> * +BUSY,"hello$",! -> "Are you talking to me, %N?", Idle +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"hello$","nezil" -> "Good bye.", Idle +"hi$","nezil" -> * +"hiho$","nezil" -> * +"bye" -> * +"farewell" -> * +"job" -> "We sell equipment of all kinds. Is there anything you need?" +"equipment" -> "We sell shovels, picks, scythes, bags, ropes, backpacks, cups, scrolls, documents, parchments, and watches. We also sell lightsources." +"goods" -> * +"light" -> "We sell torches, candlesticks, candelabra, and oil." +"name" -> "I am Bezil Coinbiter, daughter of Earth, of the Molten Rocks. I and my bro' Nezil are selling stuff, ye' know?" +"nezil" -> "He's my bro'." +"time" -> "I think it's about %T. If you'd bought a watch you'd know for sure." +"food" -> "Sorry, visit the Jolly Axeman Tavern for that." + +"goods" -> "Let me see ... we have shovels, picks, scythes, bags, ropes, backpacks, scrolls, watches, some lightsources, fishing rods, sixpacks of worms and other stuff." +"stuff" -> "Oh, things like crowbars, water hoses, presents, buckets, bottles, and the like." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2862, Amount=1, Price=4, "Do you wanna buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you wanna buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you wanna buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you wanna buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=40, "Do you wanna buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you wanna buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you wanna buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you wanna buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you wanna buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you wanna buy a dwarfensteel crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you wanna buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you wanna buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you wanna buy a bottle for %P gold?", Topic=1 +"water","hose" -> Type=2901, Data=1, Amount=1, Price=10, "Do you wanna buy a water hose for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you wanna buy oil for %P gold?", Topic=2 + + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2862, Amount=%1, Price=4*%1, "Do you wanna buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you wanna buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you wanna buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2870, Amount=%1, Price=10*%1, "Do you wanna buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=40*%1, "Do you wanna buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you wanna buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you wanna buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you wanna buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you wanna buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you wanna buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you wanna buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you wanna buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you wanna buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Data=1, Amount=%1, Price=10*%1, "Do you wanna buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here, catch it!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Nice joke, pauper!" +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Nice joke, pauper!" +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you are not having one." +Topic=3 -> "Maybe next time." + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." +} diff --git a/app/SabrehavenServer/data/npc/bigben.npc b/app/SabrehavenServer/data/npc/bigben.npc new file mode 100644 index 0000000..6133559 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bigben.npc @@ -0,0 +1,90 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bigben.npc: Datenbank für den Zyklopenschmied Ben (Elfenstadt) + +Name = "A Sweaty Cyclops" +Outfit = (22,0-0-0-0-0) +Home = [32697,31674,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hum Humm! Welcume lil' %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N waits. Me talks.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hum Humm." + +"bye" -> "Good bye lil' one.", Idle +"farewell" -> * +"job" -> "I am smith." +"name" -> "I called Bencthyclthrtrprr by me people. Lil' ones me call Big Ben." + +"tibia" -> "One day I'll go and look." +"ab'dendriel" -> "Me parents live here before town was. Me not care about lil' ones." +"big","old" -> "Mountain in south. Lil' lil' ones living there." +"elves" -> "Me not fight them, they not fight me." +"humans" -> "Always asking me for stuff they can't afford." +"orcs" -> "Silly ones. Not talk much. Always screaming and hitting." +"minotaurs" -> "They were friend with me parents. Long before elves here, they often made visit. No longer come here." +"dwarfs" -> "Lil' lil' ones are so fun. We often chat." +"lil","lil" -> * +"god" -> "You shut up. Me not want to hear." +"smith" -> "Working steel is my profession." +"steel" -> "Manny kinds of. Like Mesh Kaha Rogh, Za'Kalortith, Uth'Byth, Uth'Morc, Uth'Amon, Uth'Maer, Uth'Doon, and Zatragil" + +"Mesh","Kaha","Rogh" -> "Steel that is singing when forged. No one knows where find today." +"Za'Kalortith" -> "It's evil. Demon iron is. No good cyclops goes where you can find and need evil flame to melt." +"Uth'Byth" -> "Not good to make stuff off. Bad steel it is. But eating magic, so useful is." +"Uth'Morc" -> "Lil' ones it thieves' steel call sometimes. It's dark and making not much noise." +"Uth'Amon" -> "Brigthsteel is. Much art made with it. Sorcerers to lazy and afraid to enchant much." +"Uth'Maer" -> "Heartiron from heart of big old mountain, found very deep. Lil' lil ones fiercely defend. Not wanting to have it used for stuff but holy stuff." +"Uth'Doon" -> "It's high steel called. Only lil' lil' ones know how make." +"Zatragil" -> "Most ancients use dream silver for different stuff. Now ancients most gone. Most not know about." + +"Teshial" -> "Is one of elven family or such thing. Me not understand lil' ones and their busisness." +"Deraisim" -> * +"Cenath" -> * +"Kuridai" -> * + +"cyclops" -> "Me people not live here much. Most are far away." +"excalibug" -> "Me wish I could make weapon like it." + +"uth'kean" -> Type=3381, Amount=1, "Very noble. Shiny. Me like. But breaks so fast. Me can make from shiny armour. Lil' one want to trade?", Topic=1 +Topic=1,"yes",QuestValue(17500)<2,! -> "Wait. Me work no cheap is. Do favour for me first, yes?", Topic=2 +Topic=1,"yes",Count(Type)>=Amount -> "Cling clang!", Delete(Type), Type=5887, Amount=1, Create(Type) +Topic=1,"yes" -> "You not have stuff me want for." +Topic=1 -> "Silly lil' one you are." + +"uth'lokr" -> Type=3416, Amount=1, "Firy steel it is. Need green ones' breath to melt. Or red even better. Me can make from shield. Lil' one want to trade?", Topic=4 +Topic=4,"yes",QuestValue(17500)<2,! -> "Wait. Me work no cheap is. Do favour for me first, yes?", Topic=2 +Topic=4,"yes",Count(Type)>=Amount -> "Cling clang!", Delete(Type), Type=5889, Amount=1, Create(Type) +Topic=4,"yes" -> "You not have stuff me want for." +Topic=4 -> "Silly lil' one you are." + +"za'ralator" -> Type=3356, Amount=1, "Hellsteel is. Cursed and evil. Dangerous to work with. Me can make from evil helmet. Lil' one want to trade?", Topic=5 +Topic=5,"yes",QuestValue(17500)<2,! -> "Wait. Me work no cheap is. Do favour for me first, yes?", Topic=2 +Topic=5,"yes",Count(Type)>=Amount -> "Cling clang!", Delete(Type), Type=5888, Amount=1, Create(Type) +Topic=5,"yes" -> "You not have stuff me want for." +Topic=5 -> "Silly lil' one you are." + +"uth'prta" -> Type=3281, Amount=1, "Good iron is. Me friends use it much for fight. Me can make from weapon. Lil' one want to trade?", Topic=6 +Topic=6,"yes",QuestValue(17500)<2,! -> "Wait. Me work no cheap is. Do favour for me first, yes?", Topic=2 +Topic=6,"yes",Count(Type)>=Amount -> "Cling clang!", Delete(Type), Type=5892, Amount=1, Create(Type) +Topic=6,"yes" -> "You not have stuff me want for." +Topic=6 -> "Silly lil' one you are." + +"soul","orb",ClientVersion>=790 -> "Uh. Me can make some nasty lil' bolt from soul orbs. Lil' one want to trade all?", Topic=7 +Topic=7,"yes",QuestValue(17500)<2,! -> "Wait. Me work no cheap is. Do favour for me first, yes?", Topic=2 +Topic=7,"yes",Count(5944)>=1 -> "Cling clang!", Amount=Count(5944)*3, DeleteAmount(5944, Count(5944)), Type=6528, Create(Type) +Topic=7,"yes" -> "You not have stuff me want for." +Topic=7 -> "Silly lil' one you are." + +Topic=2,"yes" -> "Me need gift for woman. We dance, so me want to give her bast skirt. But she big is. So I need many to make big one. Bring three okay? Me wait.", SetQuestValue(17500,1), SetQuestValue(17595,1) + +"bast skirt",QuestValue(17500)=1 -> Type=3560, Amount=3, "Lil' one bring three bast skirts?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Good good! Woman happy will be. Now me happy too and help you.", Delete(Type), SetQuestValue(17500,2) +Topic=3,"yes" -> "You not have stuff me want for." +Topic=3 -> "Silly lil' one you are." + +} diff --git a/app/SabrehavenServer/data/npc/billy.npc b/app/SabrehavenServer/data/npc/billy.npc new file mode 100644 index 0000000..7036bee --- /dev/null +++ b/app/SabrehavenServer/data/npc/billy.npc @@ -0,0 +1,98 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# billy.npc: Datenbank fuer den Farmer Billy + +Name = "Billy" +Outfit = (128,58-63-58-115-0) +Home = [32037,32205,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Howdy %N." +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "You did not pay your tax. Get lost!", Idle +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Can't you see i am talking? Wait!", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "You did not pay your tax. Get lost!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "YOU RUDE $§&$" + +"bye" -> "Bye.", Idle +"farewell" -> "Farewell.", Idle +"how","are","you" -> "I think, I'm fine." +"job" -> "I am farmer and a cook." +"cook" -> "I am the best cook around. You can sell me most types of food." +"willie" -> "Don't listen to that old wannabe, I'm the best cook around." +"recipe" -> "I would love to try a pancake. But I lack a decent pan. If you get me one, I will reward you." +"name" -> "Billy." +"time" -> "I came here to have some peace and leisure so leave me alone with 'time'." +"help" -> "Can't help you, sorry. I'm a cook, not a priest." +"monster" -> "Don't be afraid, in the town you should be save." +"dungeon" -> "You'll find a lot of dungeons if you look around." +"sewer" -> "The local sewers are invested by rats, fresh rats give a good stew, you can sell them to me." +"god" -> "I am the god of cooking, indeed!" +"king" -> "The king and his tax collectors are far away. You'll meet them soon enough." +"obi" -> "I like him, we usualy have a drink or two once a week and share storys about Willie." +"seymour" -> "I don't like his headmaster behaviour. Then again, he IS a headmaster after all." +"dallheim" -> "One of the kings best men, here to protect us." +"cipfried" -> "He never leaves this temple and only has time to care about those new arivals." +"amber" -> "Shes pretty indeed! I wonder if she likes bearded men." +"weapon" -> "Ask one of the shopkeepers. They make a fortune here with all those wannabe heroes." +"magic" -> "I can spell but know no spell." +"spell" -> * +"tibia" -> "There is so much to be explored! Better hurry to get to the continent!" + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +"sell","bread" -> Type=3600, Amount=1, Price=1, "So, you want to sell a bread? Hmm, I give you %P gold, ok?", Topic=2 +"sell","cheese" -> Type=3607, Amount=1, Price=2, "So, you want to sell a cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","salmon" -> Type=3579, Amount=1, Price=2, "So, you want to sell a salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with this stinking &*#@@!" +"sell","cherry" -> Type=3590, Amount=1, Price=1, "So, you want to sell a cherry? Hmm, I give you %P gold, ok?", Topic=2 + +"sell",%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=1*%1, "So, you want to sell %A breads? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=2*%1, "So, you want to sell %A cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A hams? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=2*%1, "So, you want to sell %A salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"fish" -> "Go away with this stinking &*#@@!" +"sell",%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "So, you want to sell %A cherries? Hmm, I give you %P gold, ok?", Topic=2 + +"rat" -> "So you bring me a fresh rat for my famous stew?", Type=3994, Amount=1, Price=2, Topic=2 +"sell","rat" -> "So you bring me a fresh rat for my famous stew?", Type=3994, Amount=1, Price=2, Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "So you bring me %A fresh rats for my famous stew?", Topic=2 + +"sell" -> "I sell various kinds of food." +"buy" -> "I buy food of most kind. Since I am a great cook I need much of it." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2,"no" -> "Then not." + +"pan" -> Type=3466, Amount=1, "Have you found a pan for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "A pan! At last! Take this in case you eat something my cousin has cooked.", Delete(Type),Amount=1, Data=1, Create(3153) +Topic=3,"yes" -> "Hey! You don't have it!" +Topic=3,"no" -> "$&*@!" +Topic=3 -> * +} diff --git a/app/SabrehavenServer/data/npc/blindorc.npc b/app/SabrehavenServer/data/npc/blindorc.npc new file mode 100644 index 0000000..602af39 --- /dev/null +++ b/app/SabrehavenServer/data/npc/blindorc.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blindorc.npc: Datenbank fuer den blinden Orkschmied + +Name = "Blind Orc" +Outfit = (5,0-0-0-0-0) +Home = [32102,32130,4] +Radius = 1 + +Behaviour = { +ADDRESS,"charach",! -> "Ikem Charach maruk." +ADDRESS,! -> "Buta humak!", Idle +BUSY,"charach",! -> "Ikem napak aluk." +BUSY,! -> NOP +VANISH,! -> "Futchi." + +"futchi" -> "Futchi!", Idle +"ikem","goshak" -> "Ikem pashak porak, bata, dora. Ba goshak maruk?" + +# verkauft SABRE, SHORT SWORD, SWORD, HATCHET +"goshak","porak" -> "Ikem pashak charcha, burka, burka bata, hakhak. Ba goshak maruk?" +"goshak","charcha" -> Type=3273, Amount=1, Price=25, "Maruk goshak ta?", Topic=1 +"goshak","burka" -> Type=3294, Amount=1, Price=30, "Maruk goshak ta?", Topic=1 +"goshak","burka","bata" -> Type=3264, Amount=1, Price=85, "Maruk goshak ta?", Topic=1 +"goshak","hakhak" -> Type=3276, Amount=1, Price=85, "Maruk goshak ta?", Topic=1 + +# verkauft LEATHER ARMOR, STUDDED ARMOR, STUDDED HELMET +"goshak","bata" -> "Ikem pashak aka bora, tulak bora, grofa. Ba goshak maruk?" +"goshak","bora" -> Type=3361, Amount=1, Price=25, "Maruk goshak ta?", Topic=1 +"goshak","tulak","bora" -> Type=3378, Amount=1, Price=90, "Maruk goshak ta?", Topic=1 +"goshak","grofa" -> Type=3376, Amount=1, Price=60, "Maruk goshak ta?", Topic=1 + +# verkauft BRASS SHIELD +"goshak","dora" -> "Ikem pashak donga. Ba goshak maruk?" +"goshak","donga" -> Type=3411, Amount=1, Price=65, "Maruk goshak ta?", Topic=1 + +# verkauft BOGEN, PFEILE +"goshak","batuk" -> Type=3350, Amount=1, Price=400, "Ahhhh, maruk goshak batuk?", Topic=1 +"goshak","pixo" -> Type=3447, Amount=10,Price=30, "Maruk goshak tefar pixo ul batuk?", Topic=1 + +Topic=1,"mok",CountMoney>=Price -> "Maruk rambo zambo!", DeleteMoney, Create(Type) +Topic=1,"mok" -> "Maruk nixda!" +Topic=1,"burp" -> "Buta maruk klamuk!" +Topic=1 -> * +} diff --git a/app/SabrehavenServer/data/npc/blindprophet.npc b/app/SabrehavenServer/data/npc/blindprophet.npc new file mode 100644 index 0000000..70bf18c --- /dev/null +++ b/app/SabrehavenServer/data/npc/blindprophet.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blindprophet.npc: Datenbank für den blinden affenpropheten + +Name = "The Blind Prophet" +Outfit = (117,0-0-0-0-0) +Home = [33022,32604,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",QuestValue(293)>14,! -> "Be greeted, friend of the apes." +ADDRESS,"hi$",QuestValue(293)>14,! -> * + +ADDRESS,"hello$",! -> "You not should be here! You go! You go!", Idle +ADDRESS,"hi$",! -> * + +ADDRESS,"hello$",! -> "Be greeted, friend of the apes." +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Sorry, I am busy." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"name" -> "Me put name away name long ago. Now only blind prophet of ape people are." +"job" -> "Me prophet and guardian is." +"prophet" -> "Me is who in dreams speak to holy banana. Me divine the will of banana." +"guardian" -> "Me guard the forbidden land behind the great palisade. If any want to enter, he must ask me for transport." +"forbidden" -> * + +"transport",QuestValue(293)>14 -> "You want me to transport you to forbidden land?", topic=1 +"transport",QuestValue(293)<15 -> "No!" +"yes",topic=1,PZBlock,! -> "Anger of battle is burning in you! First calm down." +"yes",topic=1 -> "Take care!", Teleport(33026,32580,6), EffectOpp(11) +"no" ,topic=1 -> "Wise decision maybe." + +"Hairycles" -> "Good ape he is. Has to work hard to make other apes listen but you helped a lot." +"excalibug" -> "Me not know. Me seldom have visions of not banana related objects." +"bong" -> "Our holy ancestor he is. Big as mountain. Lizards say they built palisade to keep him but we not believe ...", "We think Bong palisade built to have peace from pesky lizards. We respect peace of Bong, keep people away from forbidden land." +"ape" -> "Our people a lot to learn have. One day we might live in peace with you hairless apes, who knows." +"port", "hope" -> "Hairless apes strange people are. " +"lizard" -> "The lizards evil and vengeful are. Ape people on guard must be." +"hair" -> "Me visions show hair in the far north west of forbidden land. Near coast look for signs of Bongs presence." +} diff --git a/app/SabrehavenServer/data/npc/blood.npc b/app/SabrehavenServer/data/npc/blood.npc new file mode 100644 index 0000000..a9a2eb7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/blood.npc @@ -0,0 +1,83 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blood.npc: Datenbank für General Harkath Bloodblade + +Name = "Harkath Bloodblade" +Outfit = (131,76-38-38-76-0) +Home = [32342,32184,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$","general",! -> "Salutations, commoner %N!" +ADDRESS,"salutations$","general",! -> "Salutations, commoner %N!!" +ADDRESS,"hello$",! -> "Address me properly %N!", Idle +ADDRESS,"hi$",! -> "Address me properly %N!", Idle +ADDRESS,"hail$",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,"salutations$",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE! I am busy!" +BUSY,"hi$",! -> "SILENCE! I am busy!" +BUSY,"hail$",! -> "SILENCE! I am busy!" +BUSY,"salutations$",! -> "SILENCE! I am busy!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "No news are good news." +"king" -> "HAIL TO KING TIBIANUS, OUR WISE LEADER!" +"leader" -> "King Tibianus III is our wise and just leader." +"job",female -> "My Lady, I am the general of the king's army." +"job",male -> "I am the general of the king's army." +"how","are","you"-> "I am in perfect condition, commoner." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects our city. I divided it into three battlegroups." +"guard" -> * +"general" -> "It is my duty to lead the armed forces of our beloved city into battle against our enemies." +"enemies" -> "Evil has many faces. The servants of evil cannot always be recognized as easily as Ferumbras, for instance." +"enemy" -> * +"battlegroup" -> "The battlegroups are the 'dogs of war', the 'red guards', and the 'silver guards'." +"castle" -> "The castle is prepared to withstand any direct assault." +"subject" -> "We all live under the rule of our beloved king." +"dogs","of","war"-> "They are our main army." +"red","guard" -> "They are our special forces. Some serve as city guards, others as secret police." +"secret","police"-> "The branch of the red guard that serves as secret police is known as the TBI." +"tbi$" -> "The Tibian Bureau of Investigation. Kind of secret police. I don't bother much about such things, I prefer my fights eye to eye." +"chester" -> "I don't know much about him. He is a very secretive person." +"silver","guard" -> "The best sorcerers, paladins, knights, and druids of our forces are chosen to serve as silver guards. They are the bodyguards of the king." +"city" -> "The rapid growth of the city makes it hard to patrol and vulnerable to attacks." +"scum" -> "We will eliminate all resistance against law and order!" +"stutch" -> "He is one of our best men and serves in the silver guard." +"harsky" -> * +"bozo" -> "I hardly know him." +"sam" -> "Sam is responsible to supply our troops with weapons and armor." +"weapon" -> * +"armor" -> * +"elane" -> "AH! WHAT A WOMAN!" +"gorn" -> "He was an adventurer once. He was a fine fighter but lacked the discipline to serve in our army." +"benjamin" -> "He was the king's general before I was promoted. Poor guy, lost his mind in a battle against the evil Ferumbras." +"ferumbras" -> "He is allied with evil itself! Each time we kill him he returns to take revenge." +"join" -> "Join what?" +"join","army" -> "Sorry, we don't recruit today. Perhaps you can join by doing a quest for the king." +"quest" -> "Sometimes the king calls for heroes. Keep eyes and ears open! I also heared Baxter has some work for young adventurers." +"mission" -> * +"god" -> "I whorship Banor, the first warrior!" +"banor" -> "He is the idol of all knights and paladins." +"zathroth" -> "Don't mention the dark one!" +"brog" -> "The orcs, trolls, and cyclopses sacrificed more than one of my men to Brog, the raging one." +"monster" -> "They seldom dare to attack the city itself." +"excalibug" -> "In the legends it is told, that this weapon made its wielder able to fight the mightiest demons hand to hand." +"rebellion" -> "Ask Chester of the T.B.I. about that." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +Topic=2 -> "You should be careful with your words!" +} diff --git a/app/SabrehavenServer/data/npc/blossom.npc b/app/SabrehavenServer/data/npc/blossom.npc new file mode 100644 index 0000000..bdd5d0a --- /dev/null +++ b/app/SabrehavenServer/data/npc/blossom.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blossom.npc: Datenbank für die Wächterin Blossom Bonecrusher + +Name = "Blossom Bonecrusher" +Outfit = (139,96-19-63-95-0) +Home = [32390,31787,7] +Radius = 3 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/app/SabrehavenServer/data/npc/bonifacius.npc b/app/SabrehavenServer/data/npc/bonifacius.npc new file mode 100644 index 0000000..9d07d82 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bonifacius.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bonifacius.npc: Datenbank für den Lebensmittelhändler Bonifacius + +Name = "Bonifacius" +Outfit = (128,59-82-58-95-0) +Home = [33165,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Thousands greetings, %N. How may I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am deeply sorry, I am busy right now. I'll tell you when I'm done %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods bless your travels." + +"bye" -> "May the gods bless your travels.", Idle +"name" -> "My name is Bonifacius." +"job" -> "I sell delicious food. May I be at your service?" +"time" -> "It is %T right now." +"king" -> "Our wise king, Tibianus, be praised!" +"tibianus" -> * +"army" -> "I am glad about their healthy appetite." +"ferumbras" -> "Is that a new, exotic vegetable?" +"excalibug" -> "Uh, I hate bugs of all kind." +"thais" -> "We recive food from thais with every arriving ship." +"tibia" -> "The world provides us with all kinds of delicious food." +"carlin" -> "We do not buy any wares there. Our food is of high quality, Thaian origin." +"edron" -> "Our climate is quite rough, so we can only grow wheat here, but no fruits." +"news" -> "I heard the corn prices in Thais are going to be increased." +"rumors" -> * + +"buy" -> "I can offer you meat, salmons, fruits, cookies, rolls, eggs, and cheese." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, pumpkins and melons. What do you want?" + +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=4, "Do you want to buy a salmon for %P gold?", Topic=1 +"orange" -> Type=3586, Amount=1, Price=5, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you want to buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you want to buy a roll for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=4*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=5*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you want to buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, I'm sorry, but I can't give you credit." +Topic=1 -> "Don't you like my wares?." +} diff --git a/app/SabrehavenServer/data/npc/boozer.npc b/app/SabrehavenServer/data/npc/boozer.npc new file mode 100644 index 0000000..5995ae3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/boozer.npc @@ -0,0 +1,72 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# boozer.npc: Datenbank für den Wirt Boozer + +Name = "Boozer" +Outfit = (128,76-20-116-76-0) +Home = [32921,32068,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Hard Rock Racing Track, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "You'll be back." + +"bye" -> "You'll be back.", Idle +"job" -> "I am the bartender here at the racing track." +"tavern" -> * +"frodo" -> "I heard about his tiny tavern in Thais." +"name" -> "Just call me Boozer. Everyone does that." +"time",male -> "No clue, boy." +"time",female -> "No clue, girl." +"king" -> "The king is far away, so who cares?" +"tibianus" -> * +"army" -> "Good customers." +"ferumbras" -> "Guess he'd be bad news for business." +"excalibug" -> "Heard about it now and then. Then again I also hear there a bogeyman somewhere in the swamps." +"bogeyman" -> "Just a tale to scare the kids." +"thais" -> "If you like that Thais that much just go there." +"tibia" -> "People from all over Tibia come here to buy, sell, gamble, and get drunk until they puke." +"carlin" -> "Heard about that women there. Must visit that wenches someday." +"amazon" -> "I guess they just have not met the right man yet." +"news" -> "The swampelves, down at Shadowthorn, are up to some trouble again." +"rumors" -> * +"swampelves" -> "Some elves gone evil so to say. They now live in a small village to the south called Shadowthorn. No big deal. Who cares about some carrot-eating musicians at all?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here is what you ordered.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have the gold. If we were gambling I'd call you a cheater ... and you know what happens to cheaters, don't you?" +Topic=1 -> "Then not, fine with me." + + +"buy" -> "I can offer you food and drinks. Get anything else somewhere else and don't bother me." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "So you are looking for food? We have cookies, bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +} diff --git a/app/SabrehavenServer/data/npc/boques.npc b/app/SabrehavenServer/data/npc/boques.npc new file mode 100644 index 0000000..4e6681a --- /dev/null +++ b/app/SabrehavenServer/data/npc/boques.npc @@ -0,0 +1,96 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# boques.npc: Datenbank für den Djinnkoch Bo'ques + +Name = "Bo'ques" +Outfit = (80,0-0-0-0-0) +Home = [33101,32520,5] +Radius = 2 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Hey! A human! What are you doing in my kitchen, %N?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Whoa. Do I look as if I had two heads? Only one at a time, %N!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Now, where was I?" + +"bye" -> "Goodbye. I am sure you will come back for more. They all do.", Idle +"farewell" -> * + +"name" -> "My name is Bo'ques. Perhaps you know my name from a restaurant guide." +"bo'ques" -> "You want Bo'ques? Well, you have found him, I'd say." +"job" -> "I'm preparing the food for all djinn in Ashta'daramai. ...", + "Therefore I'm what is commonly called a cook, although I do not like that word too much. It is vulgar. I prefer to call myself 'chef'." +"cook" -> * +"chef" -> "Chef sounds nice, doesn't it? Well... I must admit I do not really know what it means, but it certainly sounds classy." +"food" -> "I know many recipes for preparing the finest food on Darama and maybe even whole Tibia!" + +"king" -> "Gabel used to be king, you know. I must confess I miss those days a bit because I was allowed to carry the title of his royal majesty's personal cook. Ah, those were the days." +"gabel" -> "He is my boss. A most loyal customer and a real con... conni... well, a man of taste, at any rate. His favourite dish is Scarabée au Vin served with onions and rice." +"connoisseur" -> "Yes! That's it! I have always trouble with pronouncing that damn word. A conno... conni... ah, hang it all!" +"djinn" -> "That is our race. It has seen better days, you know. ...", + "It would have been better for us all if more djinn would share my interest in cooking. But no! Bashing each others' heads in is the only thing they are good at! Vandals and trogo ...trogli ... and cavemen, that's what they are!" +"efreet" -> "A bunch of ignorants and primitives, that's what they are. You should see the things they eat! ...", + "You know they serve ketchup with just about every kind of meal! Ketchup! Oh, those barbarians." +"marid" -> "That is us - the loyalists who have have remained faithful to Gabel and to good cooking." +"malor" -> "That accursed traitor! I think there will never be peace until he is completely vanquished. If only he would allow me to cook for him. I would fix him a dinner he would never forget." +"mal'ouquah" -> "Ah yes. The efreets' notorious fortress. I have never been there. That is no place for an artist such as myself." +"ashta'daramai" -> "That is our little fortress - our home. Nice, isn't it? I find it inspirational, although I find the culinary facilities could do with some improvements." +"human" -> "I totally agree with Gabel that djinn and humans can learn from each other. ...", + "Take cooking, for example. It has such a long tradition among humans - even I could still learn a thing or two from the famous cooks at king Tibianus' court!" +"zathroth" -> "That is a sad story, and like most djinn I dislike talking about it. Let's put it this way. Once there was a great cook who worked hard to prepare the finest meal of his life. ...", + "But when he found that the product of his efforts did not meet his expectations he just ditched it even though it was wonderfully unique in its own special way. ...", + "You know what I think? I think Zathroth was a bad cook." +"tibia" -> "It may be that this world is wide and full of adventure, but to be honest I am not at all keen to see it myself. A comfortable lamp to sleep in and a well equipped kitchen is all I need." +"daraman" -> "Ah yes. That human WAS special, believe me. Did you know I talked to him myself, back in those days? In fact, I even had an argument with him because he dared to insult my work! He drove me mad when he called me a self-indulgent glutton. ...", + "But you know, eventually we came to respect each other! He taught me to stress quality rather than quantity, and he came to appreciate my 'Chili con Cobra'. Today I know that having met him was a major step forward in my development as a culinary artist." +"darashia" -> "I have heard good things about this place. I understand the Caliph is a true gourmet. People who eat good food can't be bad, that's what I say." +"scarab" -> "Ah yes. I like them well. Especially with a good sauce or in a stew. But they have to be young! Have you tried ancient scarab? Their meat is impossible to chew unless you have teeth made of titanium." +"edron" -> "Ah, the northern cities. One day I will start an extensive culinary expedition there. I have this dream of writing some sort of culinary guide, you know. Isn't that a great idea?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "No djinn who is in his right state of mind would want to go there? What for? The land is ruled by an undead nut case, and from what I have heard his subjects are no better." +"pharaoh" -> "Apparently he is an undead! And what's worse, he actually chose that fate for himself! Undead! Imagine that! Never sleep, never laugh, and worst of all: Never eat! What a crackpot!" +"palace" -> "Who would like to live in a palace if there is never the delicious smell of freshly prepared food! I would not want to live there. Not for love nor for money." +"ascension" -> "As far as I know that is one of the pharaoh's crazy ideas. Just a load of baloney." +"rah" -> "Hm. Is that some exotic spice? Hang on, I know! It is a kind of lizard stew - right?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "These mountains are a nice place to live in, but food-wise they are pretty lousy. We basically import everything we eat from the lowlands, trading them for magic trinkets and for gold. ...", + "The only plants that grow well in these mountains are potatoes, and they are not really my idea of Haute Cuisine." +"kha'labal" -> "Such a shame about that land. It wasn't always a desert, you know. That land was garden, a veritable paradise. Just the thought of the fruit that used to grow there makes my mouth water . Well, guess who messed it up." +"war" -> "I have never been much of a warrior, but I will storm into battle swinging my meat cleaver if necessary. We simply must win this war!" +"melchior" -> "Ah yes, the trader - right? I remember him. He used to travel the mountains with his mule. A tough haggler and a real skinflint, he was. I thought he had fallen down a cliff with all his money." +"alesar" -> "Ah - that guy. You probably don't know it, but nobody around here likes to hear that name. It brings back painful memories, you know. His betrayal was such a heavy blow to us. I think I will never understand what made him do it? It is a mystery." +"lamp" -> "You would not believe it, but those lamps are actually quite comfy. And on top of that they are immensely practical! Did you ever try to stash one of your beds into your pocket?" +"fa'hradin" -> "That djinn is so engrossed in his work! I constantly have to remind him to eat because if I didn't he would simply forget. Forgetting to eat! Can you imagine that?" +"djema" -> "Djema is a nice girl, but she eats so little. It's frustrating, really. Humans and their little stomachs!" + + +"recipe",QuestValue(280)=0 -> "My collection of recipes is almost complete. There are only but a few that are missing. ...", + "Hmmm... now that we talk about it. There is something you could help me with. Are you interested?", Topic=2 +"mission",QuestValue(280)=0 -> * +Topic=2,"yes" -> "Fine! Even though I know so many recipes, I'm looking for the description of some dwarven meals. ...", + "So, if you could bring me a cookbook of the dwarven kitchen I will reward you well.", SetQuestValue (280, 1) +Topic=2 -> "Well, too bad." + +"book",QuestValue(280)=1 -> "Do you have the cookbook of the dwarven kitchen with you? Can I have it?", Topic=1 +"cookbook",QuestValue(280)=1 -> * +"book",QuestValue(280)=2 -> "Thanks again, for bringing me that book!" +"cookbook",QuestValue(280)=2 -> * + +Topic=1,"yes",Count(3234)=1,! -> "The book! You have it! Let me see! ...", + "Dragon Egg Omelette, Dwarven beer sauce... it's all there. This is great! Here is your well-deserved reward. ...", + "Incidentally, I have talked to Fa'hradin about you during dinner. I think he might have some work for you. Why don't you talk to him about it?", Amount=1, Delete(3234), Amount=3, Create(3029), SetQuestValue(280,2) +Topic=1 -> "Too bad. I must have this book." +} diff --git a/app/SabrehavenServer/data/npc/borkas.npc b/app/SabrehavenServer/data/npc/borkas.npc new file mode 100644 index 0000000..9078099 --- /dev/null +++ b/app/SabrehavenServer/data/npc/borkas.npc @@ -0,0 +1,39 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# borkas.npc: Möbelverkäufer Borkas in Venore + +Name = "Borkas" +Outfit = (128,77-43-38-76-0) +Home = [32992,32068,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hey %N, what'cha want?" +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, %N. Would ya mind not interruptin'? Thanks.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, sod off..." + +"bye" -> "Thanks and see ya.", Idle +"farewell" -> * +"job" -> "I'm into sellin' furniture. My grandfather was in that business, then my father, and so am I." +"shop" -> * +"name" -> "I'm Borkas Flersson, but let's not waste precious tradin' time with smalltalk." +"time" -> "Time is %T now." +"thanks" -> "No prob." +"thank","you" -> * +"allen" -> "Hes my boss but he likes to be one of us and sells some of his wares personally." +"richardson" -> * + +"offer" -> "I'm selling containers here." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/bozo.npc b/app/SabrehavenServer/data/npc/bozo.npc new file mode 100644 index 0000000..b4a141a --- /dev/null +++ b/app/SabrehavenServer/data/npc/bozo.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bozo.npc: Datenbank für den Hofnarren Bozo + +Name = "Bozo" +Outfit = (128,86-93-82-79-0) +Home = [32313,32183,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",female,! -> "Hello, hello, hello, little lady %N!" +ADDRESS,"hi$",female,! -> "Hello, hello, hello, little lady %N!" +ADDRESS,"hello$",male,! -> "Hi there, how's it hanging, %N!" +ADDRESS,"hi$",male,! -> "Hi there, how's it hanging, %N!" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait and listen to my jokes, I am sooooo funny!" +BUSY,"hi$",! -> "Wait and listen to my jokes, I am sooooo funny!" +BUSY,! -> NOP +VANISH,male,! -> "How did he do that??" +VANISH,! -> "Women! They all do that to me!" + +"bye" -> "Remember: A joke a day keeps the ghouls away!", Idle +"farewell" -> * +"job" -> Price=50, "I am the royal jes ... uhm ... the royal tax-collector! Do you want to pay your taxes?", Topic=1 +"news" -> "I know the newest jokes in tibia." +"how","are","you"-> "Thank you, I'm fine, the gods are with me." +"sell" -> "Sell? Hmm, I know a little about magic and by chance I can sell you a truly unusual weapon." +"durin" -> "Isn't he the author of the book 'fun with demons'?" +"stephan" -> "He is kind of a father figure to me. Of course he denies all kinship to me." +"steve" -> "He's a smart one. I heared he hid in a foreign country as the first bugs showed up." +"name" -> "My name is Bozo. But it's more than a name, it's a lifestyle to me!" +"time" -> "Since you met me it is happy hour for you." +"help" -> "I am a jester, not a doctor!" +"jester" -> "Do you wish to join the fools' guild?", Topic=6 +"fool" -> "Do you wish to join the fools' guild?", Topic=6 +"joke" -> "I know some 'monstrous' jokes!" +"idiot" -> "To me it's just a profession, but for you it's a state of mind!" +"wish" -> "If you have a wish to CIP just write a letter and place it in a dustbin of your choice." +"excalibug" -> "I am not foolish enough to believe in the existence of this weapon." +"wallcarving" -> "Oh, I saw some demoncarvings in the dungeons as I hid there after a little joke on old Stutch." +"demoncarving" -> "Yes, they showed demons, seven actually, dancing around a sword! In a flaming pit of some kind." +"flaming","pit" -> "Ah, don't ask me! Usually mages and mystics know more about such stuff." + +"monster" -> "I know a lot of monster jokes. Just tell me a monster's name, come on." +"demon" -> "Why are the experienced heroes quicker than others? ... The demons love fast food!" +"ghoul" -> "Where do the ghouls buy their robes? ... In a Boooohtique!" +"dragon" -> "Why do dragons breathe fire? ... They ate too many sorcerers in chili sauce!" +"skeleton" -> "Why do skeletons flee if wounded? ... They are so spineless!" +"orc" -> "Why do orcs have green skin? ... They ate at Frodo's!" +"cyclops" -> "How many eyes does a cyclops have? ... One for each IQ point of their opponents!" +"beholder" -> "Why are beholders so ugly? ... Because their mom and dad were beholders, too!" +"rat" -> "Why does the rat have a wooden leg? ... Because it is a former pirate!" +"spider" -> "Why did the spider cross the road? ... Because it ... oh you already know this one!?" +"troll" -> "Why do trolls live underground? ... Because on the ground there are so many PKs!" +"wolf" -> "Why do the wolves howl? ... Hey, if you're online that long you can't help but behave that way!" +"mino" -> "What do all little minotaurs want to become when they are grown-ups? ... Cowboys, of course!" +"dungeon" -> "If you are a bad jester you get a chance to visit them now and then." +"sewer" -> "Good place for picking up apples and women." +"oswald" -> "If you believe half the rumours he's spreading, you are going to get in a lot of trouble." +"update" -> "Hey! I am supposed to make the jokes here!" +"god" -> "I better make no jokes about THIS matter." +"king" -> "Nah, no jests about His Royal Highness." +"sam" -> "Did you know that he now sells a 'power axe of doom'? Run and buy it, he has only three in store." +"benjamin" -> "He would make a fine jester, too." +"gorn" -> "He sells spell scrolls each day at midnight, but you have to address him that very second." +"quentin" -> "He's my baby brother. If you tell him I sent you, he will grant you an extra spell or two." +"bozo" -> "Thats me: Bozo, the jester!" +"weapon" -> Type=3473, Amount=1, Price=250, "Do you want to buy a 'mace of the fury' for 250 gold?", Topic=3 +"magic" -> Price=200, "I actually know some spells! Do you want to learn how to 'lessen your load' for %P gold?", Topic=2 +"spell" -> Price=200, "I actually know some spells! Do you want to learn how to 'lessen your load' for %P gold?", Topic=2 +"tibia" -> "I rarely leave the castle. It's a real stress to be popular like me." +"castle" -> "The castle is my home. A place fit for a jester and all other fools. Feel welcome." +"muriel" -> "Better don't mess with sorcerers!" +"elane" -> "She's pretty but has a kind of too burning affection for my taste." +"marvik" -> "Humourless old guy! Once turned me into a frog for painting his distasteful cave in pink." +"gregor" -> "A man of steel, with a stomach of wax. Never offer him a beer!" +"paladin",Paladin-> "I wanted to become a paladin, too, but I was overqualified!" +"paladin" -> "They are the king's favourites, because they know how to 'bow'." +"sorcerer",Sorcerer-> "I wanted to become a sorcerer, too, but I was overqualified!" +"sorcerer" -> "The good thing about them is that they can't be at two places at the same time." +"druid",Druid -> "I wanted to become a Druid, too, but I was overqualified!" +"druid" -> "If you are in Druidville, do as the rabbits do." +"knight",Knight -> "I wanted to become a knight, too, but I was overqualified!" +"knight" -> "Did you notice that old knights have their scars just on their backs?" +"noodles" -> "Hey, the little one is almost as funny as me!" +"dog" -> "Are we talking about Noodles?" +"poodle" -> "Are we talking about Noodles?" +"guild" -> "Since the first guild showed up there's a great demand for jesters and fools to join them." +"necromant","nectar" -> "Peeew! That sounds disgusting! Are you a cook at Frodo's?" +"necromant" -> "Don't feed the necromants." +"lady",male -> "Well, you don't behave ladylike just because you dress like one!" +"lady",female -> "Has any man said to you that you're not only beautiful but also intelligent?", Topic=5 +"kiss",male -> "Uh, go away!", Idle +"kiss",female -> "Do you want to kiss me?", Topic=4 +"hugo" -> "I had a cousin named like that." +"cousin" -> "He died some years ago." + +Topic=1,"yes",CountMoney>=Price -> "Thank you very much. I will have a drink or two on your health!", DeleteMoney +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Well, perhaps later." +Topic=2,"yes",CountMoney>=Price -> "Here you are, I already lessened your load.", DeleteMoney +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "You don't know what offer you have passed!" +Topic=3,"yes",CountMoney>=Price -> "And here it is, it suits you well!", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "You dont know what offer you have passed!" +Topic=4,"yes" -> "Uh, oh! ... I am seeing stars!", EffectMe(13) +Topic=4 -> "Pah, I didn't want to kiss you anyway!" +Topic=5,"yes" -> "This is a world of fantasy and full of surprises!" +Topic=5 -> "Well, think about it!" +Topic=6,"yes" -> "Sorry, you already are a member." +Topic=6 -> "Well, you are already a member anyway." +} diff --git a/app/SabrehavenServer/data/npc/braden.npc b/app/SabrehavenServer/data/npc/braden.npc new file mode 100644 index 0000000..fd418e8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/braden.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Braden" +Outfit = (128,58-20-116-95-0) +Home = [32277,32707,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye, %N.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/bradford.npc b/app/SabrehavenServer/data/npc/bradford.npc new file mode 100644 index 0000000..db37100 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bradford.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Bradford" +Outfit = (128,38-93-82-116-2) +Home = [32299,32837,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh, just when I wanted to tell him about that treasure." + +"bye" -> "Farewell, %N.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/brasith.npc b/app/SabrehavenServer/data/npc/brasith.npc new file mode 100644 index 0000000..a1d29ea --- /dev/null +++ b/app/SabrehavenServer/data/npc/brasith.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brasith.npc: Datenbank für den Obsthändler Brasith + +Name = "Brasith" +Outfit = (144,60-94-58-76-0) +Home = [32692,31589,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "I am Brasith Seedsinger." +"job" -> "You may buy all the things we grow or gather at this place." +"time" -> "Sorry, I can't help you." + +"carlin" -> "The humans of Carlin at least ry to live in harmony with nature." +"thais" -> "I heared only terrible storys about that city." +"venore" -> "Their traders seem suspiciously freindly. I don't trust them." +"roderick" -> "His house is an impurity in our city in unity with nature." +"olrik" -> "This poor humans seems to think he might become one of us by spendig time with us." + +"elf" -> "Our race lacks unity, which is a very sad thing. And the differences we have will grow and grow until eventually there is no race left." +"elves" -> * +"dwarf" -> "They work the earth and claim knowledge about it, but they know only about minerals, not about the life it stands for." +"human" -> "They are so many, so planless, so divided. They have choosen a path I do not want for my own race" +"troll" -> "I don't claim to understand this creatures but sometimes they are more close to the roots than we are." +"cenath" -> "The Cenath forgot as many as they learned. I doubt they find the wisdom they are looking for without the things they neglected in their pursuit of knowledge." +"kuridai" -> "The Kuridai left the true path and can't see their error. Their way of living may have been suitable in the past, but if they don't come back to us, their path will lead into darkness." +"deraisim" -> "We have still much to learn but we are on the correct path at least." +"abdaisim" -> "The Abdaisim are true to the ways of our race, maybe even more close than we. But by abandoning the other elves they harm themselves more than they know." +"teshial" -> "They are lost, and if they still exist they are alone in the cold and the darkness." +"ferumbras" -> "He thinks that he is incredibly powerful, but his is only the mindless power of destruction." +"crunor" -> "We abandoned the gods a long time ago. A short time after they abandoned us." +"plant" -> "Life takes many forms. Plants are a very basic form of life. Its simplicity makes them close to the core of nature." +"tree" -> * +"forest" -> "The beauty of a forest is something easy to be missed by the unobservant." +"field" -> "With the growth of a community comes the need to 'use' nature rather then to 'flow' with nature. This is sad but necessary." + +"offer" -> "I sell corncobs, cherries, grapes, melons, pumpkins, bananas, strawberries, and carrots." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> * + +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"strawberry" -> Type=3591, Amount=1, Price=1, "Do you want to buy a strawberry for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"juice squeezer" -> Type=5865, Amount=1, Price=100, "Do you want to buy a juice squeezer for %P gold?", Topic=1 + +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"strawberries" -> Type=3591, Amount=%1, Price=1*%1, "Do you want to buy %A strawberries for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"juice squeezer" -> Type=5865, Amount=%1, Price=100*%1, "Do you want to buy %A juice squeezers for %P gold?", Topic=1 + +"bugmilk" -> Type=2875, Data=9, Amount=1, Price=15, "Do you want to buy a bottle of bugmilk for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/app/SabrehavenServer/data/npc/brengus.npc b/app/SabrehavenServer/data/npc/brengus.npc new file mode 100644 index 0000000..dbf942a --- /dev/null +++ b/app/SabrehavenServer/data/npc/brengus.npc @@ -0,0 +1,201 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Brengus.npc: Datenbank für den waffen und rüstungshändler Brengus + +Name = "Brengus" +Outfit = (132,79-57-57-95-0) +Home = [32634,32747,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait, I am busy right now", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a tradesman. I sell and buy weapons and armor." +"name" -> "My name is Brengus." +"time" -> "Sorry, my watch didn't take the moist air here too well." +"king" -> "This is the king's land. It was a wise decision to have us people from Venore rule this settlement." +"venore" -> "I miss my home like most of us here, but I have duties and responsibilities. After all, there is some meagre profit to earn here." +"thais" -> "A nice big city of course, but it lacks style and grandeur. Such qualities you will only find when you visit my hometown Venore." +"carlin" -> "I hope the king will take these rebelling women soon under Thaian guidance once again. I hate to see the profits wasted that could be earned there." +"edron" -> "A rich and lovely island. Sadly those knights kept our tradesmen out of business for some unknown reason. I am convinced after seeing our success with this colony here, the king will allow Venore to become more present over there too." +"jungle" -> "Of course there are problems. But problems are there to keep those out of business who are not prepared and diligent enough." + +"tibia" -> "It's a world full of possibilities." + +"kazordoon" -> "The dwarves of Kazordoon are stubborn people and it's hard to have dealings with them. But as often, the hardship is very rewarding for those who are able to handle them." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "It's complicated to negotiate with those elves but it is possible." +"elves" -> * +"elfs" -> * +"darama" -> "We have hardly scratched the surface of all the possibilities to gain profit that are hidden on this continent." +"darashia" -> "The sandwasp's honey is quite useful. But that's the only noteworthy thing about this unimportant desert hicktown." +"ankrahmun" -> "It's somewhat hard to evaluate if this city poses another threat or a new market. Only time can tell." +"ferumbras" -> "He is bad for business. The big trading houses of Venore have yet to decide what price they will put on his head." +"excalibug" -> "If you ever stumble upon that interesting piece of jewellery, contact me. I know somebody who would pay a decent amount of crystal to add it to his collection of curiosities." +"apes" -> "They are neither skilled in a craft nor do they know about the concept of trade. They constantly raid our colony to steal items." +"lizzard" -> "The lizzard folk is hostile to us but luckily they live far enough from here to be an immediate danger." +"dworcs" -> "They should be driven into the sea." + + + + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain and brass armors. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy some?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell a spear for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","templar","scytheblade" -> Type=3345, Amount=1, Price=200, "Do you want to sell a templar scytheblade for %P gold?", Topic=2 +"sell","ripper","lance" -> Type=3346, Amount=1, Price=500, "Do you want to sell a ripper lance for %P gold?", Topic=2 +"sell","hunting","spear" -> Type=3347, Amount=1, Price=250, "Do you want to sell a hunting spear for %P gold?", Topic=2 +"sell","banana","staff" -> Type=3348, Amount=1, Price=1000, "Do you want to sell a banana staff for %P gold?", Topic=2 + + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","tusk","shield" -> Type=3443, Amount=1, Price=850, "Do you want to sell a tusk shield for %P gold?", Topic=2 +"sell","sentinel","shield" -> Type=3444, Amount=1, Price=120, "Do you want to sell a sentinel shield for %P gold?", Topic=2 +"sell","salamander","shield" -> Type=3445, Amount=1, Price=280, "Do you want to sell a salamander shield for %P gold?", Topic=2 +"sell","tribal","mask" -> Type=3403, Amount=1, Price=250, "Do you want to sell a tribal mask for %P gold?", Topic=2 +"sell","leopard","armor" -> Type=3404, Amount=1, Price=300, "Do you want to sell a leopard armor for %P gold?", Topic=2 +"sell","horseman","helmet" -> Type=3405, Amount=1, Price=280, "Do you want to sell a horseman helmet for %P gold?", Topic=2 +"sell","feather","headdress" -> Type=3406, Amount=1, Price=850, "Do you want to sell a feather headdress for %P gold?", Topic=2 +"sell","crocodile","boots" -> Type=3556, Amount=1, Price=100, "Do you want to sell crocodile boots for %P gold?", Topic=2 +"sell","bast","skirt" -> Type=3560, Amount=1, Price=750, "Do you want to sell a bast skirt for %P gold?", Topic=2 +"sell","charmer","tiara" -> Type=3407, Amount=1, Price=900, "Do you want to sell a charmer's tiara for %P gold?", Topic=2 +"sell","beholder","helmet" -> Type=3408, Amount=1, Price=2200, "Do you want to sell a beholder helmet for %P gold?", Topic=2 + +"sell","tusk" -> "Sorry, I'm not interested in tusks, but you might want to offer them to Zaidal - as far as I know he uses them for making tables and chairs." +"sell",%1,1<%1,"tusk" -> "Sorry, I'm not interested in tusks, but you might want to offer them to Zaidal - as far as I know he uses them for making tables and chairs." + + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"templar","scytheblade" -> Type=3345, Amount=%1, Price=200*%1, "Do you want to sell %A templar scytheblades for %P gold?", Topic=2 +"sell",%1,1<%1,"ripper","lance" -> Type=3346, Amount=%1, Price=500*%1, "Do you want to sell %A ripper lances for %P gold?", Topic=2 +"sell",%1,1<%1,"hunting","spear" -> Type=3347, Amount=%1, Price=250*%1, "Do you want to sell %A hunting spears for %P gold?", Topic=2 +"sell",%1,1<%1,"banana","staff" -> Type=3348, Amount=%1, Price=1000*%1, "Do you want to sell %A banana staves for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"tusk","shield" -> Type=3443, Amount=%1, Price=850*%1, "Do you want to sell %A tusk shields for %P gold?", Topic=2 +"sell",%1,1<%1,"sentinel","shield" -> Type=3444, Amount=%1, Price=120*%1, "Do you want to sell %A sentinel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"salamander","shield" -> Type=3445, Amount=%1, Price=280*%1, "Do you want to sell %A salamander shields for %P gold?", Topic=2 +"sell",%1,1<%1,"tribal","mask" -> Type=3403, Amount=%1, Price=250*%1, "Do you want to sell %A tribal masks for %P gold?", Topic=2 +"sell",%1,1<%1,"leopard","armor" -> Type=3404, Amount=%1, Price=300*%1, "Do you want to sell %A leopard armors for %P gold?", Topic=2 +"sell",%1,1<%1,"horseman","helmet" -> Type=3405, Amount=%1, Price=280*%1, "Do you want to sell %A horseman helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"feather","headdress" -> Type=3406, Amount=%1, Price=850*%1, "Do you want to sell %A feather headdresses for %P gold?", Topic=2 +"sell",%1,1<%1,"crocodile","boots" -> Type=3556, Amount=%1, Price=100*%1, "Do you want to sell %A pairs of crocodile boots for %P gold?", Topic=2 +"sell",%1,1<%1,"bast","skirt" -> Type=3560, Amount=%1, Price=750*%1, "Do you want to sell %A bast skirts for %P gold?", Topic=2 +"sell",%1,1<%1,"charmer","tiara" -> Type=3407, Amount=%1, Price=900*%1, "Do you want to sell %A charmer's tiaras for %P gold?", Topic=2 +"sell",%1,1<%1,"beholder","helmet" -> Type=3408, Amount=%1, Price=2200*%1, "Do you want to sell %A beholder helmets for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/brewster.npc b/app/SabrehavenServer/data/npc/brewster.npc new file mode 100644 index 0000000..b5ec2f6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/brewster.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brewster.npc: Datenbank für den priester brewster + +Name = "Brewster" +Outfit = (133,57-115-115-95-0) +Home = [32595,32744,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "G...greetings ." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Uh? Gimme a break. As you can see there's another one first.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye ... and now I'll have a quick drink." + +"bye" -> "Exactly! ", Idle +"farewell" -> * +"job" -> "I am a priest. The worldly representative of the gods so to speak. Not that I would say such a thing of course. This would be vanity after all." +"name" -> "I am ... ah, yes, Brewster. That's me, my name I mean ." +"time" -> "Uhm ... Uh ... No idea, sorry." +"temple" -> "Hehe! Well if you call this hut a temple you are not a devoted churchgoer I guess. But never mind, I won't tell anyone and the gods know it anyway ... if they care." +"king" -> "Ah the king, how lucky he must be - being the ruler of this lovely little piece of dirt here. Hehe." +"venore" -> "Venore, Venore, city of splendour. Hm, the best thing about that city is its brewery." +"thais" -> "Thais!! My beloved hometown! Oh how I miss my good, old Thais." +"carlin" -> "Ha! That's probably even worse than this dump of a jungle here that they call a colony." +"edron" -> "They would never appoint a priest of such a low rank like me to Edron." +"jungle" -> "This jungle must be the way of the gods to give us mortals a taste of hell ." +"gods" -> "Oh come on, just leave me alone. Read a book to find out more." + +"tibia" -> "If Tibia is a fallen god, make your guess what bodypart you are on now. I have my assumptions ... but I won't tell. Hehe." + +"kazordoon" -> "The dwarves I met can't stop to praise the dwarven beer. That wakes the urge in me to ... uhm spread the word of our gods in that city of Kazordoon." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Was never there For all what I have heard it's not that much different from this ugly little settlement." +"elves" -> "After being in that jungle for a while, I can't trust people that love trees anymore." +"elfs" -> * +"darama" -> "The teachings of our temple counts little on this continent. I think it's a sign from the gods to abandon it. But why should anyone listen to poor old Brewster?" +"ankrahmun" -> "Just to think about this cursed town and its inhabitants makes me shiver. I better take a quick drink to forget about it." +"ferumbras" -> " Oh well, he is just that what I'd expect next in all my misery." +"excalibug" -> "Who knows if it is real or just some myth? And who cares at all?" + +"apes" -> "They don't believe me but I have seen them. There are pink apes! They come when I am sleeping and try to steal my beer and wine ." +"lizard" -> "They usually stay away from here so who cares?" +"dworcs" -> "Heard enough of them to dislike them." + +"cough", "syrup" -> "The only person who might have some cough syrup is this druid Ustan. You find him in the tavern. Hmmm the tavern ... " + + +"help",HP<40,! -> "You are hurt my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0,! -> "You are poisoned my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0,! -> "You are burning my child. I will help you.", Burning(0,0), EffectOpp(15) + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + + +"blessing",PvPEnforced,! -> "The vital force of this world is waning. There are no more blessings available on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your vital force is damaged. Each one of the five blessings will reduce this damage." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of Tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just tell me in which of the five blessings you are interested." + +"spiritual", QuestValue(104) > 0 -> "I see you have received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I can sense that the spark of the phoenix has already been given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin have provided you with the embrace of Tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of Tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you have received the blessing of the two suns in the suntower near Ab'Dendriel." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + +"wisdom", QuestValue(101) > 0 -> "I can sense you have already talked to the hermit Eremo on the isle of Cormaya and received this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"stake",QuestValue(17576)=9,Count(5941)<=0 -> "I think you have forgotten to bring your stake, pilgrim." +"stake",QuestValue(17576)=9 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Your hand shall be guided - your feet shall walk in harmony'. Now, bring your stake to Tyrias in Liberty Bay for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,10) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, pilgrim." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=10 -> "You should visit Tyrias in Liberty Bay now." +"stake",QuestValue(17576)>10 -> "You have already received my line of the prayer. Don't make me do more work than necessary!" +"stake" -> "A blessed stake? That's a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." +} diff --git a/app/SabrehavenServer/data/npc/briasol.npc b/app/SabrehavenServer/data/npc/briasol.npc new file mode 100644 index 0000000..c704d53 --- /dev/null +++ b/app/SabrehavenServer/data/npc/briasol.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# briasol.npc: Datenbank fuer den Juwelier Briasol (Elfenstadt) + +Name = "Briasol" +Outfit = (144,3-86-87-76-0) +Home = [32635,31667,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "In a few heartbeats I will have time for you %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am a jeweller and exchange money." +"name" -> "I am Briasol Crithanath." +"time" -> "I don't know the time, sorry. I do not care for this concept. Watches are your master, they tell you what to do and when." + +"elves" -> "Our lifespan is longer then that of other races. We should keep that in mind everytime." +"dwarfs" -> "They live that long and make not much out of it." +"humans" -> "I mourn them. As soon as you get to know one he's dead." +"troll" -> "We take care of them, give them shelter, and a reason to live." + +"carlin" -> "Carlin is a quite lovely city, given that its a city of humans." +"thais" -> "Thais has a high demand on the jewelry that I craft." +"venore" -> "The tradesmen of Venore offer high prices for my wares." +"roderick" -> "I have only little dealings with him." +"olrik" -> "I only talk to him when I send a parcel to one of my customers in a far away city. He seems friendly and is a bit eager to please." + +"cenath" -> "They are the ones responsible for most of the magic and the like in this town." +"kuridai" -> "Our caste are workers out of passion." +"deraisim" -> "They hunt for us and patrol the woods." +"abdaisim" -> "I don't know much about them." +"teshial" -> "They are lost in time." +"ferumbras" -> "He will be gone sooner or later." +"crunor" -> "Gods are eternal. They learn so much in their existence." +"excalibug" -> "It's a weapon of times long gone. It's lost for our time." +"news" -> "I know nothing of importance." + +"offer" -> "I can sell gems, pearls, and jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "I have white and black pearls for sale, but you also can sell me some." +"jewel" -> "You can purchase our fine dwarfish wares like gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "We don't trade with them." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=5 +"golden","amulet" -> Type=3013, Amount=1, Price=6600,"Do you want to buy a golden amulet for %P gold?", Topic=5 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560,"Do you want to buy a ruby necklace for %P gold?", Topic=5 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=5 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=5 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=5 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=5 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=5 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=5 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=5 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=5 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=5 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=5 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1,"Do you want to buy %A ruby necklaces for %P gold?", Topic=5 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=5 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=5 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=5 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=5 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=5 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=5 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=5 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=5 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=6 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=6 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=6 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=6 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=6 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=6 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=6 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=6 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=6 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=6 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=6 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=6 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=6 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=6 + +Topic=5,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you do not have one." +Topic=6,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=6 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/brodrosch.npc b/app/SabrehavenServer/data/npc/brodrosch.npc new file mode 100644 index 0000000..124f034 --- /dev/null +++ b/app/SabrehavenServer/data/npc/brodrosch.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brodrosch.npc: Datenbank für den Kapitän Brodrosch + +Name = "Brodrosch" +Outfit = (66,0-0-0-0-0) +Home = [32661,31957,15] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N! May Earth protect you, even whilst sailing!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","cormaya",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) +BUSY,"bring","me","to","cormaya",Premium,CountMoney>=160,! -> Price=160, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) +ADDRESS,"bring","me","to","cormaya",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) +ADDRESS,"bring","me","to","cormaya",Premium,CountMoney>=160,! -> Price=160, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) + +BUSY,"hello$",! -> "Shut up and wait like the rest, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, yeah, walk, it's cheaper." + +"bye" -> "Earth under your feet ... it's still better than lava.", Idle +"farewell" -> * +"job" -> "Look at my blackened beard? I'm the steamship captain!" +"work" -> * +"name" -> "I am Brodrosch Steamtrousers, son of the machine, of the Molten Rock." +"tibia" -> "Tibia? Just don't ask." +"ship" -> "This is a great ship. Ha! It works without wind but with fire, and it travels not on the ocean but beneath the earth!" +"steamship" -> * +"captain" -> "Of course, I am the captain. But I am also a technomancer." +"technomancer" -> "Being a technomancer is a privilege few dwarfs have. We form earth and fire through powerful technology into tools. Also, we are great inventors." +"inventors" -> "Yes. There could have been thousands of our inventions, if they wouldn't explode all the time..." +"inventions" -> * +"sell" -> "This is not a shop, damn it!" +"buy" -> * +"thais" -> "This is a steamship that travels only subterreneanly. No way to get on that risky ocean. Kazordoon - Cormaya only." +"ab'dendriel" -> * +"carlin" -> * +"venore" -> * +"senja" -> * +"folda" -> * +"vega" -> * +"ice","islands" -> * +"darashia" -> * +"darama" -> * +"kazordoon" -> "Hey, we ARE at Kazordoon! Must be the cavemadness..." +"beer" -> "Sometimes being drunk means seeing two rivers. I survive by steering right between them." +"dwarf" -> "Deep inside, we're all dwarfs." +"gurbasch" -> "Ah, my brother in Cormaya. He can take you back." + +"cormaya" -> Price=160, "So you want to go to Cormaya? %P gold?", Topic=1 +"passage" -> * + +"cormaya",QuestValue(250)>2 -> Price=150, "So you want to go to Cormaya? %P gold?", Topic=1 +"passage",QuestValue(250)>2 -> * + + + +# für post-quest + +Topic=1,"yes",Premium,QuestValue(227)=4,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3),SetQuestValue(227,5) + +Topic=1,"yes",Premium,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic=1,"yes" -> "You don't have enough money." +} diff --git a/app/SabrehavenServer/data/npc/bron.npc b/app/SabrehavenServer/data/npc/bron.npc new file mode 100644 index 0000000..cd1b551 --- /dev/null +++ b/app/SabrehavenServer/data/npc/bron.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Bron" +Outfit = (143,95-94-132-86-2) +Home = [32366,31628,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",QuestValue(17532)=6,! -> "Oh no! Was that really me? This is so embarassing, I have no idea what has gotten into me. Was that the fighting spirit you gave me?", SetQuestValue(17532,7), Topic=4 +ADDRESS,"hi$",QuestValue(17532)=6,! -> * +ADDRESS,"hello$",! -> "Welcome to my humble hut, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye, %N.", Idle +"farewell" -> * + +"violence",QuestValue(17532)=0 -> "Convincing Ajax that it is not always necessary to use brute force... this would be such an achievement. Definitely a hard task though. ...", + "Listen, I simply have to ask, maybe a stranger can influence him better than I can. Would you help me with my brother?", Topic=1 +Topic=1,"yes" -> "Really! That is such an incredibly nice offer! I already have a plan. You have to teach him that sometimes words are stronger than fists. ...", + "Maybe you can provoke him with something to get angry, like saying... 'MINE!' or something. But beware, I'm sure that he will try to hit you. ...", + "Don't do this if you feel weak or ill. He will probably want to make you leave by using violence, but just stay strong and refuse to give up. ...", + "If he should ask what else is necessary to make you leave, tell him to 'say please'. Afterwards, do leave and return to him one hour later. ...", + "This way he might learn that violence doesn't always help, but that a friendly word might just do the trick. ...", + "Have you understood everything I told you and are really willing to take this risk?", Topic=2 +Topic=1 -> "Maybe another time." +Topic=2,"yes" -> "You are indeed not only well educated, but also very courageous. I wish you good luck, you are my last hope.", SetQuestValue(17532,1), SetQuestValue(17594,1) +Topic=2 -> "Maybe another time." + +"Brother","is","right","Fist","not","always","good",QuestValue(17532)=3 -> "Oh! He really said that? I am so proud of you, %N. These are really good news. Everything would be great... if only there wasn't this person near my house.", SetQuestValue(17532,4) +"person",QuestValue(17532)=4 -> "This... person... makes me want to... say something bad... must... control myself. I really don't know what to do anymore....", + "I wonder if Ajax has an idea. Could you ask him about Gelagos?", Topic=3 +"gelagos",QuestValue(17532)=4 -> * +"gelagos" -> "This... person... makes me want to... say something bad... must... control myself." +Topic=3,"yes" -> "Again, I have to thank you for your selfless offer to help me. I hope that Ajax can come up with something, now that he has experienced the power of words.", SetQuestValue(17532,5) +Topic=3 -> "Maybe another time." + +"fighting","spirit",QuestValue(17532)=5,Count(5884)>=1 -> "Fighting spirit? What am I supposed to do with this fi... - oh! I feel strange... ME MIGHTY! ME WILL CHASE OFF ANNOYING KIDS!GROOOAARR!! RRRRRRRRRRRRAAAAAAAGE!!", DeleteAmount(5884, 1), SetQuestValue(17532,6), Idle +"fighting","spirit" -> "No, no, no. I don't need any fighting spirit never." + +Topic=4,"yes" -> "I'm impressed... I am sure this was Ajax' idea. I would love to give him a present, but if I leave my hut to gather ingredients, he will surely notice. ...", + "Would you maybe help me again, one last time, my friend? I assure you that your efforts will not be in vain.", Topic=5 +Topic=4 -> "Of course not you my educated friend. I can't believe how this happened. I would love to give Ajax a present, but if I leave my hut to gather ingredients, he will surely notice. ...", + "Would you maybe help me again, one last time, my friend? I assure you that your efforts will not be in vain.", Topic=5 + +"help",QuestValue(17532)=7 -> "Oh, you came to help me to make a present for Ajax?", Topic=5 +"present",QuestValue(17532)=7 -> * +Topic=5,"yes" -> "Great! You see, I really would love to sew a nice shirt for him. I just need a few things for that, so please listen closely: ...", + "He loves green and red, so I will need about 50 pieces of red cloth - like the material heroes make their capes of - and 50 pieces of the green cloth Djinns like. ...", + "Secondly, I need about 10 rolls of spider silk yarn. I think mermaids can yarn silk of large spiders to create a smooth thread. ...", + "The only remaining thing needed would be a bottle of warrior's sweat to spray it over the shirt... he just loves this smell. ...", + "Have you understood everything I told you and are willing to handle this task?", Topic=6 +Topic=5 -> "Hmmm. If you wish to help another time to make a present I will be waiting for you." +Topic=6,"yes" -> "Thank you, my friend! Come back to me once you have collected 50 pieces of red cloth and 50 pieces of green cloth.", SetQuestValue(17532,8) +Topic=6 -> "Hmmm. If you wish to help another time to make a present I will be waiting for you." +"cloth",QuestValue(17532)=8 -> "Have you really managed to fulfill the task and brought me 50 pieces of red cloth and 50 pieces of green cloth?", Topic=7 +Topic=7,"yes",Count(5911)>=50,Count(5910)>=50 -> "Terrific! I will start to trim it while you gather 10 rolls of spider silk. I'm sure that Ajax will love it.", DeleteAmount(5911,50), DeleteAmount(5910,50), SetQuestValue(17532,9) +Topic=7,"yes" -> "Sorry, you do not have so many." +Topic=7 -> "Maybe another time." + +"rolls","of","spider","silk",QuestValue(17532)=9 -> "Oh, did you bring 10 rolls of spider silk yarn for me?", Topic=8 +Topic=8,"yes",Count(5886)>=10 -> "I'm impressed! You really managed to get spider silk yarn for me! I will immediately start to work on this shirt. Please don't forget to bring me warrior's sweat!", DeleteAmount(5886,10), SetQuestValue(17532,10) +Topic=8,"yes" -> "Sorry, you do not have so many." +Topic=8 -> "Maybe another time." + +"sweat",QuestValue(17532)=10 -> "Were you able to get hold of a flask with pure warrior's sweat?", Topic=9 +Topic=9,"yes",Count(5885)>=1 -> "Good work, %N! Now I can finally finish this present for Ajax. Because you were such a great help, I have also a present for you. Will you accept it?", DeleteAmount(5885,1), SetQuestValue(17532,11), Topic=10 +Topic=9,"yes" -> "Sorry, you do not have it." +Topic=9 -> "Maybe another time." + +"present",QuestValue(17532)=11 -> "Because you were such a great help, I have also a present for you. Will you accept it?", Topic=10 +"help",QuestValue(17532)=11 -> * +Topic=10,"yes" -> "I have kept this traditional barbarian wig safe for many years now. It is now yours! I hope you will wear it proudly, friend.", AddOutfitAddon(147,2), AddOutfitAddon(143,2), SetQuestValue(17532,12), EffectOpp(13) +Topic=10 -> "Okey, ask for it when you feel ready, friend." + +"axe",QuestValue(17532)=17 -> "I know it is you who helped with the present for me from my brother Ajax. Thank you! Wear your axe proudly!" +"addon",QuestValue(17532)=17 -> * +"wig",QuestValue(17532)>11 -> "I hope you will wear the traditional barbarian wig proudly, friend." +"addon",QuestValue(17532)>11 -> * + +} diff --git a/app/SabrehavenServer/data/npc/bruno.npc b/app/SabrehavenServer/data/npc/bruno.npc new file mode 100644 index 0000000..0ce0a3b --- /dev/null +++ b/app/SabrehavenServer/data/npc/bruno.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bruno.npc: Der Fischverkäufer Bruno (Fields) + +Name = "Bruno" +Outfit = (128,113-10-95-95-0) +Home = [32486,31604,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoi, %N. You want to buy some fresh fish?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again!" + +"bye" -> "Good bye and come again!", Idle +"farewell" -> * +"name" -> "My name is Bruno." +"job" -> "My job is to catch fish and to sell them here." +"graubart" -> "I like this old salt. I learned much from him. Whatever. You like some fish? *grin*" +"marlene" -> "Ah yes, my lovely wife. God forgive her, but she can't stop talking. So my work is a great rest for my poor ears. *laughs loudly*" +"aneus" -> "Hmm, I don't know him very well. But he has a very nice story to tell." + +"do","you","sell" -> "Well, I sell freshly caught fish. You like some? Of course, you can buy more than one at once. *grin*" +"offer" -> * + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy a fresh fish for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fresh fishes for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "*grumble* Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/budrik.npc b/app/SabrehavenServer/data/npc/budrik.npc new file mode 100644 index 0000000..78c9379 --- /dev/null +++ b/app/SabrehavenServer/data/npc/budrik.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# budrik.npc: Datenbank für den Minenvorsteher Budrik + +Name = "Budrik" +Outfit = (160,94-76-58-95-0) +Home = [32524,31906,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, Hiho %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute %N!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"job" -> "I am the foreman of this mine." +"shop" -> * +"name" -> "My name is Budrik Deepdigger, son of Earth, from the Molten Rock." +"time" -> "Precisely %T, young one." +"help" -> "I am a miner, ask someone else." +"dwarfs" -> "We understand the ways of the earth like nobody else does." +"monster" -> "In the deeper mines we discover some nasty beasts now and then." +"dungeon" -> "This is no funhouse. Leave the miners and their drilling-worms alone and get out! We have already enough trouble without you." +"mines" -> * +"trouble" -> "The Horned Fox is leading his bandits in sneak attacks and raids on us." +"horned","fox" -> "A minotaur they threw out at Mintwallin. He must have some kind of hideout nearby." +"hideout" -> "The hideout of the Horned Fox is probably a dangerous if not lethal place for the unexperienced ones." +} diff --git a/app/SabrehavenServer/data/npc/bunny.npc b/app/SabrehavenServer/data/npc/bunny.npc new file mode 100644 index 0000000..416e3fb --- /dev/null +++ b/app/SabrehavenServer/data/npc/bunny.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bunny.npc: Datenbank für die Generalin Bunny Bonecrusher + +Name = "Bunny Bonecrusher" +Outfit = (139,96-3-79-115-0) +Home = [32315,31756,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hail","general",! -> "Salutations, commoner %N!" +ADDRESS,"salutations","general",! -> "Salutations, commoner %N!" +ADDRESS,"hello",! -> "Address me properly %N!", Idle +ADDRESS,"hi",! -> "Address me properly %N!", Idle +ADDRESS,"hail",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,"salutations",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE! I am busy!" +BUSY,"hi$",! -> "SILENCE! I am busy!" +BUSY,"hail$",! -> "SILENCE! I am busy!" +BUSY,"salutations$",! -> "SILENCE! I am busy!" +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"farewell" -> * +"news" -> "Our reports are only for internal use." +"report" -> * +"queen" -> "HAIL TO QUEEN ELOISE, OUR NOBLE LEADER!" +"leader" -> "Queen Eloise is a fine leader for our fair town, indeed!" +"job",female -> "I am the general of the queen's army! You really should consider to join, sister." +"job",male -> "I am the general of the queen's army and have not the time to explain this concept to you." +"how","are","you" -> "We are in constant training and in perfect health." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects the defenceless males of our city. Our elite forces are the Green Ferrets." +"guard" -> * +"green","ferrets" -> "Our elite forces are trained by rangers and druids. In the woods they are only second to some elves." +"castle" -> "The castle is not meant for defence but as a residence for the royal family." +"subject" -> "Our citizens have the luck to live under the wise rule of our beloved queen!" +"dogs","of","war" -> "They are a men's club, mainly concerned about bragging and drinking alcohol." +"knights","of","noodles" -> "They are rumoured to be skilled fighters. Then again, in the land of the blind..." +"druid" -> "They are our main magic support and play a major role in our battletactics." +"battletactics" -> "Our tactic is to kiss." +"tactics" -> * +"kiss" -> "K.I.S.S.! Keep It Simple, Stupid! Complicated tactics are to easy to be crushed by a twist of fate." +"bloodblade" -> "Old man. I can't tell what's worse for the shape of Thais' army." +"thais" -> "It's just a rotten hideout for drunks and men too lazy to do some serious work." +"city" -> "Our city blends in with the nature surrounding it. Our druids take care of that." +"bonecrusher" -> "Our family serves in the Carlin army since uncounted generations!" +"sister" -> * +"bambi" -> "She is one of my beloved sisters and serves Carlin as a town guard." +"blossom" -> * +"busty" -> * +"family" -> * +"fenbala" -> "She is one of our Green Ferrets and one of the queen's bodyguards." +"barbara" -> * +"cornelia" -> "Cornelia forges the armor necessary for our troops." +"armor" -> * +"rowenna" -> "Rowenna is responsible for our troops' supply with weapons." +"weapon" -> * +"legola" -> "She is a distant cousin of mine and my sisters." +"ferumbras" -> "Believe it or not. I killed him two times with my own bow, but some unholy forces rise him again and again." +"join" -> "Join what?" +"join","army" -> "Sorry, we dont recruit foreigners. Perhaps you can join by doing a quest for the queen." +"quest",female -> "Sometimes the queen calls for heroines. Keep eyes and ears open!" +"mission",female -> * +"quest",male -> "Yeah. Entrusting a male with an important quest. Get serious!" +"mission",male -> * +"god" -> "I whorship Banor, the first warrior!" +"banor" -> "He is the idol for all fighting women and a reminder of what a man could become, if he could jump over his own shadow!" +"zathroth" -> "Don't mention the dark one in the city of life!" +"monster" -> "We cleared the woods around Carlin from most of them. But lately more and more showed up again." +"excalibug" -> "I am sure only a woman could muster the courage and strength to wield this weapon of myth." +"graveyard" -> "Bah! Just men's tales! Who believes in such bullshit? Perhaps we should put some men there over night and see what happens. Hehehe!" +"cemetary" -> * +"crypt" -> * + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"shit" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +Topic=2 -> "You should be careful with your words!" + +-> "Your words don't make any sense to me." +} diff --git a/app/SabrehavenServer/data/npc/busty.npc b/app/SabrehavenServer/data/npc/busty.npc new file mode 100644 index 0000000..5e2547f --- /dev/null +++ b/app/SabrehavenServer/data/npc/busty.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# busty.npc: Datenbank für die Wächterin Busty Bonecrusher + +Name = "Busty Bonecrusher" +Outfit = (139,96-19-66-95-0) +Home = [32294,31791,7] +Radius = 3 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/app/SabrehavenServer/data/npc/cameron.npc b/app/SabrehavenServer/data/npc/cameron.npc new file mode 100644 index 0000000..1de94d1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/cameron.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Cameron" +Outfit = (128,59-37-115-95-0) +Home = [32196,32840,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye, %N.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/captain1.npc b/app/SabrehavenServer/data/npc/captain1.npc new file mode 100644 index 0000000..ec4573b --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain1.npc @@ -0,0 +1,106 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain1.npc: Kapitän Blaubaer in Thais + +Name = "Captain Bluebear" +Outfit = (129,19-69-107-50-0) +Home = [32310,32210,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +BUSY,"bring","me","to","carlin",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) + +BUSY,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +BUSY,"bring","me","to","ab'dendriel",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Bluebear from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Carlin, Ab'Dendriel, Venore, Port Hope or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghostship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> "This is Thais. Where do you want to go?" +"carlin" -> Price=110, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=130, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron" -> Price=160, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=170, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope" -> Price=160, "Do you seek a passage to Port Hope for %P gold?", Topic=7 +"liberty","bay" -> Price=180, "Do you seek a passage to Liberty Bay for %P gold?", Topic=8 + +"carlin",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=7 +"liberty","bay",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Liberty Bay for %P gold?", Topic=8 + +# für postquest +Topic=2,"yes",Premium, QuestValue(227)=1,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11),SetQuestValue(227,2) + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captain2.npc b/app/SabrehavenServer/data/npc/captain2.npc new file mode 100644 index 0000000..e39c416 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain2.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain2.npc: Kapitän Greyhound in Carlin + +Name = "Captain Greyhound" +Outfit = (129,96-113-95-115-0) +Home = [32388,31822,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +BUSY,"bring","me","to","ab'dendriel",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Greyhound from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Ab'Dendriel, Venore or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=110, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> "This is Carlin. Where do you want to go?" +"ab'dendriel" -> Price=80, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron" -> Price=110, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=130, "Do you seek a passage to Venore for %P gold?", Topic=5 + +"thais",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Thais for %P gold?", Topic=1 +"ab'dendriel",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Venore for %P gold?", Topic=5 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captain3.npc b/app/SabrehavenServer/data/npc/captain3.npc new file mode 100644 index 0000000..7240058 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain3.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain3.npc: Kapitän Seagull in Ab'Dendriel + +Name = "Captain Seagull" +Outfit = (129,60-113-95-115-0) +Home = [32735,31668,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +BUSY,"bring","me","to","carlin",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) + +BUSY,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +BUSY,"bring","me","to","ab'dendriel",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=60,! -> Price=60, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=60,! -> Price=60, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Seagull from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Venore or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"ankrahmun" -> "I'm sorry, but we don't serve this route." +"tiquanda" -> * +"port","hope" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=130, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=80, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> "This is Ab'Dendriel. Where do you want to go?" +"edron" -> Price=70, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=90, "Do you seek a passage to Venore for %P gold?", Topic=5 + +"thais",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=60, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Venore for %P gold?", Topic=5 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captain4.npc b/app/SabrehavenServer/data/npc/captain4.npc new file mode 100644 index 0000000..bef6be4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain4.npc @@ -0,0 +1,121 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain4.npc: Kapitän Seahorse in Edron + +Name = "Captain Seahorse" +Outfit = (129,19-113-95-115-0) +Home = [33176,31764,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +BUSY,"bring","me","to","carlin",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) + +BUSY,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=60,! -> Price=60, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +BUSY,"bring","me","to","ab'dendriel",Premium,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=60,! -> Price=60, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) + +BUSY,"bring","me","to","cormaya",Premium,QuestValue(250)>2,CountMoney>=10,! -> Price=10, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) +BUSY,"bring","me","to","cormaya",Premium,CountMoney>=20,! -> Price=20, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) +ADDRESS,"bring","me","to","cormaya",Premium,QuestValue(250)>2,CountMoney>=10,! -> Price=10, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) +ADDRESS,"bring","me","to","cormaya",Premium,CountMoney>=20,! -> Price=20, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=30,! -> Price=30, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=30,! -> Price=30, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +BUSY,"bring","me","to","ankrahmun",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Seahorse from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Ab'Dendriel, Venore, Port Hope, Ankrahmun or the isle Cormaya?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=110, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=70, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"cormaya" -> Price=20, "Do you seek a passage to Cormaya for %P gold?", Topic=4 +"edron" -> "This is Edron. Where do you want to go?" +"venore" -> Price=40, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun" -> Price=160, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope" -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=7 +"liberty","bay" -> Price=170, "Do you seek a passage to Liberty Bay for %P gold?", Topic=8 + +"thais",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=60, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"cormaya",QuestValue(250)>2 -> Price=10, "Do you seek a passage to Cormaya for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=30, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Port Hope for %P gold?", Topic=7 +"liberty","bay",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Liberty Bay for %P gold?", Topic=8 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) + +# für post-quest +Topic=5,"yes",Premium,QuestValue(227)=3,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11),SetQuestValue(227,4) + +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captain5.npc b/app/SabrehavenServer/data/npc/captain5.npc new file mode 100644 index 0000000..0e4dde4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain5.npc @@ -0,0 +1,118 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain5.npc: Kapitän Fearless in Venore + +Name = "Captain Fearless" +Outfit = (129,19-113-95-115-0) +Home = [32955,32022,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +BUSY,"bring","me","to","carlin",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,QuestValue(250)>2,CountMoney>=120,! -> Price=120, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +ADDRESS,"bring","me","to","carlin",Premium,CountMoney>=130,! -> Price=130, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) + +BUSY,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +BUSY,"bring","me","to","ab'dendriel",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +ADDRESS,"bring","me","to","ab'dendriel",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=30,! -> Price=30, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=30,! -> Price=30, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","darashia",! -> "One moment please %N. I want to warn you about this trip.", Queue +ADDRESS,"bring","me","to","darashia",! -> "One moment please %N. I want to warn you about this trip.", Queue + +BUSY,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +BUSY,"bring","me","to","ankrahmun",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Fearless from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Ab'Dendriel, Port Hope, Edron, Darashia or Ankrahmun?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"ghost" -> "There's a legend of a ghostship cruising between Venore and Darashia. Many captains are afraid to sail this route. Hah, but not me!" + +"thais" -> Price=170, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=130, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=90, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"venore" -> "This is Venore. Where do you want to go?" +"darashia" -> Price=60, "Do you seek a passage to Darashia for %P gold?", Topic=5 +"edron" -> Price=40, "Do you seek a passage to Edron for %P gold?", Topic=4 +"ankrahmun" -> Price=150, "Do you seek a passage to Ankrahmun for %P gold?", Topic=7 +"port","hope" -> Price=160, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay" -> Price=180, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +"thais",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"darashia",QuestValue(250)>2 -> Price=50, "Do you seek a passage to Darashia for %P gold?", Topic=5 +"edron",QuestValue(250)>2 -> Price=30, "Do you seek a passage to Edron for %P gold?", Topic=4 +"ankrahmun",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Ankrahmun for %P gold?", Topic=7 +"port","hope",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +Topic=5,"yes",Premium,CountMoney>=Price -> "I warn you! This route is haunted by a ghostship. Do you really want to go there?", Topic=6 +Topic=6,"yes",Premium,CountMoney>=Price,Random(1,10)=1 -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33330,32172,5), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) + +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic=9,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captain6.npc b/app/SabrehavenServer/data/npc/captain6.npc new file mode 100644 index 0000000..98140d5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain6.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# petros.npc: Fährmann Petros bei Darashia + +Name = "Petros" +Outfit = (128,79-10-127-127-0) +Home = [33289,32481,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. I can take you to Venore, Port Hope or Ankrahmun if you like." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +BUSY,"bring","me","to","ankrahmun",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=190,! -> Price=190, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=190,! -> Price=190, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye!" + +"bye" -> "Good bye!", Idle +"farewell" -> * +"name" -> "My name is Petros." +"job" -> "I take along people to Venore, Port Hope and Ankrahmun." +"ghost" -> "Oh, I don't believe in ghosts." + +"ship" -> "My boat is ready to bring you to Venore, Port Hope or Ankrahmun." +"boat" -> * +"passage" -> * +"venore" -> Price=60, "Do you want to get to Venore for %P gold?", Topic=1 +"ankrahmun" -> Price=100, "Do you want to get to Ankrahmun for %P gold?", Topic=2 +"port","hope" -> Price=180, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay" -> Price=200, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +"venore",QuestValue(250)>2 -> Price=50, "Do you want to get to Venore for %P gold?", Topic=1 +"ankrahmun",QuestValue(250)>2 -> Price=90, "Do you want to get to Ankrahmun for %P gold?", Topic=2 +"port","hope",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay",QuestValue(250)>2 -> Price=190, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +Topic=1,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=2,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic=9,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/captain7.npc b/app/SabrehavenServer/data/npc/captain7.npc new file mode 100644 index 0000000..2a7e41d --- /dev/null +++ b/app/SabrehavenServer/data/npc/captain7.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sinbeard.npc: Kapitän Sinbeard in Ankrahmun + +Name = "Captain Sinbeard" +Outfit = (134,95-10-56-77-0) +Home = [33094,32884,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +BUSY,"bring","me","to","darashia",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=70,! -> Price=70, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "I am known all over the world as Captain Sinbeard." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "My ship is the fastest in the whole world." +"line" -> * +"company" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Darashia, Venore, Port Hope or Edron?" +"route" -> * +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"thais" -> "I'm sorry but my ship does not currently service that port." +"carlin" -> * +"ab'dendriel" -> * + +"darashia" -> Price=100, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron" -> Price=160, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=150, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope" -> Price=80, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay" -> Price=90, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +"darashia",QuestValue(250)>2 -> Price=90, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Port Hope for %P gold?", Topic=8 +"liberty","bay",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic=9,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} + diff --git a/app/SabrehavenServer/data/npc/captainmaxcalassa.npc b/app/SabrehavenServer/data/npc/captainmaxcalassa.npc new file mode 100644 index 0000000..cb6600a --- /dev/null +++ b/app/SabrehavenServer/data/npc/captainmaxcalassa.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Captain Max" +Outfit = (134,95-10-56-77-0) +Home = [31915,32710,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoy, %N. On a mission for the explorer society, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Captain Maximilian." +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Welcome on board, noble %N. I can bring you back to Liberty Bay. Do you want me to do it?", Topic=1 +"passage" -> * +"back" -> * + +"liberty bay" -> "Do you want go back to Liberty Bay?", Topic=1 + + +Topic=1,"yes" -> "Set the sails!", Idle, EffectOpp(11), Teleport(32298,32895,6), EffectOpp(11) +Topic=1 -> "Maybe another time, then." + +"helmet","of","the","deep" -> Type=5460, Amount=1, Price=5000, "Do you want to buy a helmet of the deep for %P gold?", Topic=2 +%1,1<%1,"helmet","of","the","deep" -> Type=5460, Amount=%1, Price=5000*%1, "Do you want to buy %A helmets of the deep for %P gold?", Topic=1 +Topic=2,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Sorry, you do not have enough gold." +Topic=2 -> "Maybe you will buy it another time." + +"sell","helmet","of","the","deep" -> Type=5460, Amount=1, Price=5000, "Do you want to sell a helmet of the deep for %P gold?", Topic=3 +"return" -> * +"sell",%1,1<%1,"helmet","of","the","deep" -> Type=5460, Amount=%1, Price=5000*%1, "Do you want to sell %A helmets of the deep for %P gold?", Topic=2 +"return",%1,1<%1 -> * +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you do not have one." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=3 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/captainmaxlibertybay.npc b/app/SabrehavenServer/data/npc/captainmaxlibertybay.npc new file mode 100644 index 0000000..108f1d5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captainmaxlibertybay.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Captain Max" +Outfit = (134,95-10-56-77-0) +Home = [32298,32895,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoy, %N. On a mission for the explorer society, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Captain Maximilian." +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Welcome on board, noble %N. I can bring you to Calassa, but only if you have the according mission from Berenice." +"passage" -> * + +"calassa" -> Price=200, "That is quite a long unprofitable travel. I'll bring you to Calassa though for %P gold. Do you want me to do it?", Topic=1 + + +Topic=1,"yes",QuestValue(300)>8,Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(31911,32710,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price,QuestValue(300)>8 -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes",CountMoney "You don't have enough money." +Topic=1,"yes",QuestValue(300)<9 -> "I'm sorry, but I can't ship to Calassa with someone of your rank." +Topic>0 -> "Maybe another time, then." +} diff --git a/app/SabrehavenServer/data/npc/captainwaveriderisland.npc b/app/SabrehavenServer/data/npc/captainwaveriderisland.npc new file mode 100644 index 0000000..e006cb1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captainwaveriderisland.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Captain Waverider" +Outfit = (96,0-0-0-0-0) +Home = [32131,32914,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, daring adventurer. If you need a passage, let me know." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32349,32856,7), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32349,32856,7), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh well." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Where do you want to go? To Liberty bay?" +"passage" -> * + +"liberty","bay" -> Price=50, "Do you seek a passage to Liberty bay for %P?", Topic=1 +"back" -> * +"return" -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32349,32856,7), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/captainwaveriderlibertybay.npc b/app/SabrehavenServer/data/npc/captainwaveriderlibertybay.npc new file mode 100644 index 0000000..1519bf0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/captainwaveriderlibertybay.npc @@ -0,0 +1,39 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Captain Waverider" +Outfit = (96,0-0-0-0-0) +Home = [32346,32859,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, daring adventurer. If you need a passage, let me know." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","peg","leg",Premium,QuestValue(17502)>5,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32348,32625,7), EffectOpp(11) +BUSY,"bring","me","to","treasure","island",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32132,32913,7), EffectOpp(11) +ADDRESS,"bring","me","to","peg","leg",Premium,QuestValue(17502)>5,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32348,32625,7), EffectOpp(11) +ADDRESS,"bring","me","to","treasure","island",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32132,32913,7), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh well." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Where do you want to go? To Treasure Island?" +"passage" -> * + +"treasure","island" -> Price=200, "Do you seek a passage to Treasure Island for %P?", Topic=1 +"peg","leg",QuestValue(17502)>5 -> Price=50, "Ohhhh. So... 'you know who' sent you so I sail you to 'you know where'. It will cost 50 gold to cover my expenses. Is it that what you wish?", Topic=2 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32132,32913,7), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32348,32625,7), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/carina.npc b/app/SabrehavenServer/data/npc/carina.npc new file mode 100644 index 0000000..f8bac94 --- /dev/null +++ b/app/SabrehavenServer/data/npc/carina.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# carina.npc: Datenbank für die Juwelierin Carina + +Name = "Carina" +Outfit = (138,97-70-94-76-0) +Home = [33015,32048,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N. I am looking forward to trade with you." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please, %N, give me another minute with our other customer first.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell some of the most beautiful jewels of the lands." +"name" -> "I am Carina Carlson." +"time" -> "It's %T." +"offer" -> "I am selling jewels, just have a look." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"jewel" -> "We offer gold converting rings, wedding rings, golden amulets, and ruby necklaces." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, make sure to come back, as soon as you have enough money." +Topic=1 -> "Perhaps next time." +} diff --git a/app/SabrehavenServer/data/npc/cedrik.npc b/app/SabrehavenServer/data/npc/cedrik.npc new file mode 100644 index 0000000..77bd23a --- /dev/null +++ b/app/SabrehavenServer/data/npc/cedrik.npc @@ -0,0 +1,145 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Cedrik" +Outfit = (134,116-57-97-59-0) +Home = [32292,32818,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait, I am busy right now", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "However." + +"bye" -> "Bye, %N.", Idle +"farewell" -> * +"job" -> "I am a tradesman. I sell and buy weapons and armor." +"name" -> "My name is Cedrik." + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain and brass armors. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy some?" +"legs" -> * + +"arrow" -> Type=3447, Amount=1, Price=3, "Do you want to buy a arrow for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=3*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell a spear for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","hunting","spear" -> Type=3347, Amount=1, Price=250, "Do you want to sell a hunting spear for %P gold?", Topic=2 + + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"hunting","spear" -> Type=3347, Amount=%1, Price=250*%1, "Do you want to sell %A hunting spears for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/ceiron.npc b/app/SabrehavenServer/data/npc/ceiron.npc new file mode 100644 index 0000000..5fce27b --- /dev/null +++ b/app/SabrehavenServer/data/npc/ceiron.npc @@ -0,0 +1,98 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Ceiron" +Outfit = (144,78-100-119-116-3) +Home = [32661,31715,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Always nice to meet a fellow druid, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May Crunor bless and guide you, %N." + +"bye" -> "May Crunor bless and guide you, %N.", Idle +"farewell" -> * + +"addon",QuestValue(17535)=10 -> "I am proud to see you with the Faolan gift." +"outfit",QuestValue(17535)=10 -> * +"addon",QuestValue(17535)=0 -> "What are you thinking! I would never allow you to slay my beloved friends for the sake of your narcism. Only Faolan can grant you a fur like this one.", Topic=1 +"outfit",QuestValue(17535)=0 -> * +Topic=1,"faolan" -> "I know where the great wolf mother lives, but I will not tell that to just anyone. You have to earn my respect first. Are you willing to help me?", Topic=2 +Topic=2,"yes" -> "I hope that I am not asking too much of you with this task. I heard of a flower which is currently unique in Tibia and can survive at only one place. ...", + "This place is somewhere in the bleak mountains of Nargor. I would love to have a sample of its blossom, but the problem is that it seldom actually blooms. ...", + "I cannot afford to travel there each day just to check whether the time has already come, besides I have no idea where to start looking. ...", + "I would be deeply grateful if you could support me in this matter and collect a sample of the blooming Griffinclaw for me. ...", + "Have you understood everything I told you and will fulfill this task for me?", Topic=3 +Topic=2 -> "Maybe another time." +Topic=3,"yes" -> Type=4867, Amount=1, "Alright then. Take this botanist's container and return to me once you were able to retrieve a sample. Don't lose patience!", Create(Type), SetQuestValue(17535,1), SetQuestValue(17594,1) +Topic=3 -> "Maybe another time." + +"task",QuestValue(17535)=1 -> Type=5937, Amount=1, "Were you able to obtain a sample of the Griffinclaw?", Topic=4 +"mission",QuestValue(17535)=1 -> * +"griffinclaw",QuestValue(17535)=1 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Crunor be praised! The Griffinclaw really exists! Now, I will make sure that it will not become extinct. If you are ready to help me again, just ask me for a task.", Delete(Type), SetQuestValue(17535,2) +Topic=4,"yes" -> "Sorry, you do not have it." +Topic=4 -> "Maybe another time." + +"task",QuestValue(17535)=2 -> "Listen, your next task is not exactly easy either. ...", + "In the mountains between Ankrahmun and Tiquanda are two hydra lairs. The northern one has many waterfalls whereas the southern one has just tiny water trickles. ...", + "However, these trickles are said to contain water as pure and clean as nowhere else in Tibia. ...", + "If you could reach one of these trickles and retrieve a water sample for me, it would be a great help. ...", + "It is important that you take the water directly from the trickle, not from the pond - else it will not be so pure anymore. ...", + "Have you understood everything I told you and will you fulfill this task for me?", Topic=5 +"mission",QuestValue(17535)=2 -> * +Topic=5,"yes" -> Type=5938, Amount=1, "Great! Here, take my waterskin and try to fill it with water from this special trickle. Don't lose my waterskin, I will not accept some random dirty waterskin.", Create(Type), SetQuestValue(17535,3) +Topic=5 -> "Maybe another time." + +"waterskin",QuestValue(17535)=3 -> Type=5938, Amount=1, Price=1000, "Have you lost my waterskin? Would you like to buy another one for %P gold?", Topic=6 +Topic=6,"yes",CountMoney>=Price -> "Here, better don't lose it.", DeleteMoney, Create(Type) +Topic=6,"yes" -> "I am sorry, but you do not have enough gold." +Topic=6 -> "Maybe another time." + +"task",QuestValue(17535)=3 -> Type=5939, Amount=1, "Did you bring me a sample of the water from the hydra cave?", Topic=7 +"mission",QuestValue(17535)=3 -> * +"water",QuestValue(17535)=3 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Good work, %N! This water looks indeed extremely clear. I will examine it right away. If you are ready to help me again, just ask me for a task.", Delete(Type), SetQuestValue(17535,4) +Topic=7,"yes" -> "Sorry, you do not have it." +Topic=7 -> "Maybe another time." + +"task",QuestValue(17535)=4 -> "I'm glad that you are still with me, %N. Especially because my next task might require even more patience from your side than the ones before. ...", + "Demons... these unholy creatures should have never been able to walk the earth. They are a brood fuelled only by hatred and malice. ...", + "Even if slain, their evil spirit is not fully killed. It needs a blessed stake to release their last bit of fiendishness and turn them into dust. ...", + "It does not work all the time, but if you succeed, their vicious spirit is finally defeated. ...", + "I want proof that you are on the right side against Zathroth. Bring me 100 ounces of demon dust and I shall be convinced. ...", + "You'll probably need to ask a priest for help to obtain a blessed stake. ...", + "Have you understood everything I told you and will you fulfil this task for me?", Topic=8 +"mission",QuestValue(17535)=4 -> * +Topic=8,"yes" -> "Good! I will eagerly await your return.", SetQuestValue(17535,5) +Topic=8 -> "Maybe another time." + +"task",QuestValue(17535)=5 -> Type=5906, Amount=100, "Were you really able to collect 100 ounces of demon dust?", Topic=9 +"mission",QuestValue(17535)=5 -> * +"demon","dust",QuestValue(17535)=5 -> * +Topic=9,"yes",Count(Type)>=Amount -> "I'm very impressed, %N. With this task you have proven that you are not only on the right side, but also quite powerful. If you are ready to help me again, just ask me for a task.", Delete(Type), SetQuestValue(17535,6) +Topic=9,"yes" -> "Sorry, you do not have that many." +Topic=9 -> "Maybe another time." + +"task",QuestValue(17535)=6 -> "I have one final task for you, %N. Many months ago, I was trying to free the war wolves which are imprisoned inside the orc fortress. ...", + "Unfortunately, my intrusion was discovered and I had to run for my life. During my escape, I lost my favourite wolf tooth chain. ...", + "It should still be somewhere in the fortress if the orcs did not try to eat it. I really wish you could retrieve it for me. ...", + "It has the letter 'C' carved into one of the teeth. Please look for it. ...", + "Have you understood everything I told you and will you fulfil this task for me?", Topic=10 +"mission",QuestValue(17535)=6 -> * +Topic=10,"yes" -> "Good! I will eagerly await your return.", SetQuestValue(17535,7) +Topic=10 -> "Maybe another time." + +"task",QuestValue(17535)=7 -> Type=5940, Amount=1, "Have you really found my wolf tooth chain??", Topic=11 +"mission",QuestValue(17535)=7 -> * +"wolf ","tooth","chain",QuestValue(17535)=7 -> * +Topic=11,"yes",Count(Type)>=Amount -> "Crunor be praised! You found my beloved chain! %N, you really earned my respect and I consider you as a friend from now on. Remind me to tell you about Faolan sometime.", Delete(Type), SetQuestValue(17535,8) +Topic=11,"yes" -> "Sorry, you do not have it." +Topic=11 -> "Maybe another time." + +"faolan",QuestValue(17535)=8 -> "Right, I will keep my promise. Faolan roams Tibia freely, but her favourite sleeping cave is on Cormaya. I will cast a spell on you that enables you to speak the wolf language for a while.", SetQuestValue(17535,9), EffectOpp(13) +} diff --git a/app/SabrehavenServer/data/npc/chantalle.npc b/app/SabrehavenServer/data/npc/chantalle.npc new file mode 100644 index 0000000..6c7062b --- /dev/null +++ b/app/SabrehavenServer/data/npc/chantalle.npc @@ -0,0 +1,69 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Chantalle" +Outfit = (140,79-37-71-70-0) +Home = [32363,32792,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear %N. Have a look at our offers." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am deeply sorry, dear %N, but I am busy with a customer. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am responsible for buying and selling gems, pearls, and the like." +"time" -> "It's %T." +"offer" -> "We offer a great assortment of gems and pearls." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "We trade small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "We trade white and black pearls." +"sabrehaven","talon" -> "This is an extremely rare thing! Sadly, Liberty Bay can't afford any of that." + +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you don't have enough money." +Topic=1 -> "Too bad, perhaps we can trade on the next occasion you visit us." + +Topic=2,"yes",Count(Type)>=Amount -> "Excellent. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "I am sorry, but you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Too bad, perhaps we can trade on the next occasion you visit us." +} diff --git a/app/SabrehavenServer/data/npc/charles.npc b/app/SabrehavenServer/data/npc/charles.npc new file mode 100644 index 0000000..1557d88 --- /dev/null +++ b/app/SabrehavenServer/data/npc/charles.npc @@ -0,0 +1,117 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# charles.npc: Datenbank für den Kapitän Charles + +Name = "Charles" +Outfit = (134,57-29-95-98-0) +Home = [32529,32785,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoi." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +BUSY,"bring","me","to","darashia",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=140,! -> Price=140, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +BUSY,"bring","me","to","ankrahmun",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,CountMoney>=110,! -> Price=110, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(250)>2,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "Just wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am the captain of the Poodle, the proudest ship on all oceans." +"name" -> "It's Charles." +"time" -> "It is precisely %T." +"king" -> "His majesty himself was present at the day the Poodle was launched." + +"jungle" -> "It's a fascinating forest, full of exotic life. If it weren't for my duties, I would spend some time just exploring this jungle." + +"tibia" -> "We live in a fascinating world with even more fascinating oceans. And all its major harbours are known to me." +"major","harbour" -> "Well the harbours of thais, venore, carlin, edron, darashia and ankrahmun. Do you have any questions about one of those harbours?", Topic=20 +Topic=20,"venore" -> "The Venorans build fine ships. Enough said about them." +Topic=20,"thais" -> "Thais is the proud capital of the largest kingdom in the known world." +Topic=20,"carlin" -> "Rebellious women might be amusing for a while, but it is time for them to stop this nonsense and return to the kingdom." +Topic=20,"edron" -> "The coastline of Edron is treacherous and it takes some skills to sail a ship safely into the harbour." +Topic=20,"darashia" -> "An unremarkable little town with a small harbour and quiet people." +Topic=20,"ankrahmun" -> "The city is surely worth a look although its inhabitants are somewhat strange and their customs oddish." + +"kazordoon" -> "An inland town of dwarves, somewhere in the middle of nowhere." +"dwarves" -> "It's fun to see a seasoned dwarven fighter turnining into a shivering green something as soon as we get a mild breeze on sea." +"dwarfs" -> * +"ab'dendriel" -> "My visits there were interesting and I learnt a lot about the elves and their city. I can only recommend a visit there and if it is only to admire the amazing architectural style in which the city was built." +"elves" -> "Elves are very special creatures. They keep in touch with nature almost like druids. Although I don't really understand their way of life, I think we could learn one or two things of them." +"elfs" -> * +"darama" -> "I sailed around the whole continent once and I have seen many of its wonders. For sure there are more waiting to be discovered." + +"ferumbras" -> "He is that for the land what giant sea serpents are for the sea." +"excalibug" -> "You better ask some knight about it." +"apes" -> "I would love to catch a living exemplar and bring it to Thais so the king could see it." +"lizard" -> "They have a small settlement in the southeast of the jungle next to the coast. It looks somewhat primitive but there is evidence it was erected only recently." +"dworcs" -> "They attacked us when we set our feet on the south shore of the continent. They are poison using savages, nothing more." + +"thais" -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia" -> Price=180, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron" -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=160, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun" -> Price=110, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"liberty","bay" -> Price=50, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + +"thais",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"liberty","bay",QuestValue(250)>2 -> Price=40, "Do you seek a passage to Liberty Bay for %P gold?", Topic=9 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=9,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel on board of our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." + +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Darashia, Venore, Ankrahmun or Edron?" +"route" -> * +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * + +} diff --git a/app/SabrehavenServer/data/npc/charlotta.npc b/app/SabrehavenServer/data/npc/charlotta.npc new file mode 100644 index 0000000..a2be7a6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/charlotta.npc @@ -0,0 +1,121 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Charlotta" +Outfit = (157,38-97-115-95-1) +Home = [32268,32841,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, welcome! Welcome %N! If you need druid spells, you've come to the right place. Otherwise it's just nice to have a visitor." +ADDRESS,"hi$",! -> "Ah, welcome! Welcome %N! If you need druid spells, you've come to the right place. Otherwise it's just nice to have a visitor." +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> "Please wait, %N.", Queue +BUSY,! -> NOP +VANISH,! -> "Oh well." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"magic" -> "Every member of the Druids is able to learn the numerous spells of our craft." +"power" -> * +"druid" -> "We are druids, preservers of life. Our magic is about defense, healing, and nature." +"sorcerer" -> "Sorcerers are destrucitve. Their power lies in destruction and pain." +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Druids, paladins, knights, and sorcerers." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Rachel." +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"mission",QuestValue(17502)=3 -> Price=200, "Oh, so you brought some gold from Eleonore to me?", Topic=4 +"errand",QuestValue(17502)=3 -> * + +Topic=4,"yes" -> "Hmm, it seems that Eleonore does trust you. Perhaps she is even right. However. Since we need some help right now I guess we can't be too picky. Return to Eleonore and tell her the secret password: 'peg leg'. She will tell you more about her problem.", DeleteMoney, SetQuestValue(17502,4) +Topic=4 -> "Hmm, interesting how do you even know about the errand then..." +} diff --git a/app/SabrehavenServer/data/npc/chatterbone.npc b/app/SabrehavenServer/data/npc/chatterbone.npc new file mode 100644 index 0000000..7e2cc11 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chatterbone.npc @@ -0,0 +1,128 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chatterbone.npc: Datenbank für den Magiehändler Chatterbone + +Name = "Chatterbone" +Outfit = (18,0-0-0-0-0) +Home = [32981,32080,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "... Greeeeeetiiiingssss" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "... Wait... %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "... Good... Bye" + +"bye" -> "... Good... Bye", Idle +"farewell" -> * +"job" -> "... Selling Spells" +"name" -> "... Chatterbone" +"time" -> "... Time?... Not important... anymore." +"king" -> "..." +"tibianus" -> * +"vladruc" -> "... Maaaaassssterrrrr" +"urghain" -> * +"ferumbras" -> "... un...important" +"market" -> "... You buy?" +"excalibug" -> "... we hid it... so long ago... so long..." +"news" -> "... they build a new city... Carlin shall be its name..." +"flaming","pit" -> "... we conquered them... held them so long... long ago..." +"pits","inferno" -> * +"nightmare","pit" -> * + +"sorcerer" -> "... You... buy spells?" +"power" -> * +"druid" -> "... Ask Smiley..." +"spellbook" -> "... You buy book... store spells... other counter..." +"rune" -> "... Runes... mighty stones... other counter..." +Sorcerer,"spell" -> "... Spells... rune spells... instant spells... what you want? ... Or for which level?", Topic=2 +"spell" -> "... Only sorcerers..." + +Topic=2,"rune","spell" -> "... Attack rune spells ... support rune spells ... Which...?" +Topic=2,"instant","spell" -> "... Attack spells ... healing spells ... supply spells ... support spells ... summon spells. Which...?" +Topic=2,"level" -> "Which level...?", Topic=2 +Topic=2,"bye" -> "... Good... Bye", Idle + +sorcerer,"wand",QuestValue(333)<1 -> "Oooh... present from meee... take it... goooood start for youuuung sorcerers...",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +Sorcerer,"level" -> "Which level...?", Topic=2 +Sorcerer,"rune","spell" -> "... Attack rune spells ... support rune spells ... Which...?" +Sorcerer,"instant","spell" -> "... Attack spells ... healing spells ... supply spells ... support spells ... summon spells. Which...?" + +Sorcerer,"attack","rune","spell" -> "... Missile rune spells ... explosive rune spells ... field rune spells ... wall rune spells ... bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category ... 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category ... 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category ... 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category ... 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category ... 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category ... 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category ... 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category ... 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category ... 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category ... 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "... You want 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "... You want 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "... You want 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "... You want 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "... You want 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "... You want 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "... You want 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "... You want 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "... You want 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "... You want 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "... You want 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "... You want 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "... You want 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "... You want 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "... You want 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "... You want 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "... You want 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "... You want 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "... You want 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "... You want 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "... You want 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "... You want 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "... You want 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "... You want 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "... You want 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "... You want 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "... You want 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "... You want 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "... You want 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "... For level 8 ... 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "... For level 9 ... 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "... For level 10 ... 'Antidote'.", Topic=2 +Topic=2,"11$" -> "... For level 11 ... 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "... For level 13 ... 'Great Light'.", Topic=2 +Topic=2,"14$" -> "... For level 14 ... 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "... For level 15 ... 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "... For level 17 ... 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "... For level 18 ... 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "... For level 20 ... 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "... For level 23 ... 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "... For level 25 ... 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "... For level 27 ... 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "... For level 29 ... 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "... For level 31 ... 'Explosion'.", Topic=2 +Topic=2,"33$" -> "... For level 33 ... 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "... For level 35 ... 'Invisible'.", Topic=2 +Topic=2,"38$" -> "... For level 38 ... 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "... For level 41 ... 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "... For level 45 ... 'Sudden Death'.", Topic=2 + +Topic=2 -> "... No spells for this level ... but for many ... from 8 to 45.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "... You already know..." +Topic=3,"yes",Level Amount=SpellLevel(String), "... not level %A..." +Topic=3,"yes",CountMoney "... More money." +Topic=3,"yes" -> "... Here...", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "... Then not." +} diff --git a/app/SabrehavenServer/data/npc/chemar.npc b/app/SabrehavenServer/data/npc/chemar.npc new file mode 100644 index 0000000..698fcc5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chemar.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chemar.npc: Datenbank für den Teppichpiloten Chemar in Darashia + +Name = "Chemar" +Outfit = (130,95-3-14-76-0) +Home = [33270,32439,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, the wind brings in another visitor. Feel welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=20,! -> Price=20, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=30,! -> Price=30, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=20,! -> Price=20, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=30,! -> Price=30, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) + +BUSY,"bring","me","to","femor",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +BUSY,"bring","me","to","femor",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +ADDRESS,"bring","me","to","femor",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +ADDRESS,"bring","me","to","femor",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) + +BUSY,"hello$",! -> "%N! Be calm as the eye of the storm, and your patience will be rewarded.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings!" + +"bye" -> "Daraman's blessings!", Idle +"name" -> "My name is Chemar Ibn Kalith." +"job" -> "I am a licensed carpetpilot and responsible for the Darashian airmail. I can bring you to the Femor Hills, Edron, or you can buy letters and parcels." +"time" -> "It's %T, precisely." +"caliph" -> "The caliph depends heavily on his carpetfleet for commerce and for war alike." +"kazzan" -> * +"daraman" -> "The prophet of our people; praised be his name." +"ferumbras" -> "This scourge of the west may have connections to the evil soils in Drefia." +"drefia" -> "In the west a big city existed. Its people were corrupted and drew the wrath of the djinn upon them and Drefia was destroyed." +"excalibug" -> "I have been almost everywhere in the world and think it's only a myth." +"thais" -> "I think it's a rolemodel for what befalls people if they forget the teachings of Daraman." +"carlin" -> "That city is getting noisier and more crowded each month." +"news" -> "Our carpetpilots bring in too many news to recall them all." +"rumour" -> * +"rumor" -> * +"flying","carpet" -> "Do you want to buy a flying carpet for 5000 platinum coins?", Price=500000, Topic=7 +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 + +"passage" -> "I can fly you to Femor Hills or Edron if you like. Where do you want to go?" +"fly" -> * +"go" -> * +"transport" -> * +"ride" -> * +"trip" -> * +"tibia" -> * + +"femur" -> "Are you sure that you are not talking about the FEMOR Hills?" +"hill" -> Price=60, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=4 +"femor" -> * +"edron" -> Price=40, "Do you want to get a ride to Edron for %P gold?", Topic=5 + +"hill",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=4 +"femor",QuestValue(250)>2 -> * +"edron",QuestValue(250)>2 -> Price=30, "Do you want to get a ride to Edron for %P gold?", Topic=5 + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Darashian Airmail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." + + +Topic=4,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +Topic=4,"yes" -> "You don't have enough money." +Topic=4 -> "You shouldn't miss the experience." + +Topic=5,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +Topic=5,"yes" -> "You don't have enough money." +Topic=5 -> "You shouldn't miss the experience." + +Topic=7,"yes",CountMoney>=Price -> "Oh, I am sorry, but you have no pilot licence." +Topic=7,"yes" -> "You don't own enough worldly wealth to afford this item." +Topic=7 -> "Maybe another day then, my friend." +} diff --git a/app/SabrehavenServer/data/npc/chephan.npc b/app/SabrehavenServer/data/npc/chephan.npc new file mode 100644 index 0000000..d8e6a21 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chephan.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chephan.npc: Datenbank für den Küchenbedarfshändler Chephan + +Name = "Chephan" +Outfit = (128,2-26-115-76-0) +Home = [32890,32077,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, looking for some cooking gear today, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute, %N. I am talking already, but will be avaliable for you very soon.", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "So long, %N." + +"bye" -> "So long, %N.", Idle +"name" -> "I am Chephan, at your service." +"job" -> "I sell all the cooking gear you can dream of." +"warehouse" -> "Here you can by so many things you will need one day or another. Just have a look." +"time" -> "Watches are sold in the south east part of this warehouse." +"king" -> "Even a king needs a fork now and then. To scratch his back or to poke servants for example." +"tibianus" -> * +"army" -> "They brought most of their cooking gear from thais." +"ferumbras" -> "See this fork? Now imagine what a hero like you could do to an evil sorcerer with that fork! Care to buy one?" +"excalibug" -> "Just an oversized kitchenknife. Better buy the real thing." +"thais" -> "Thaian cooking gear is of inferior quality. Make sure to upgrade yours here as soon as you can." +"tibia" -> "The world is flat as this plate. You should buy one as a symbol for Tibia." +"carlin" -> "So many women and so little intrest in cooking, horrible." +"news" -> "My recipies are family secrets, sorry." +"tax" -> "Those taxes are killing me. And they are getting worse each year!" +"privilege" -> "I don't feel that privileged. In fact our beloved city is bleeding for the profit of Thais." +"gambling" -> "Thanks to that taxes I have not enough spare money to gamble much." + +"offer" -> "That would be: Buckets, bottles, mugs, cups, jugs, plates, baking trays, pots, pans, forks, spoons, knifes, wooden spoons, cleavers, spatulas, and rolling pins." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"baking","tray" -> Type=3464, Amount=1, Price=20, "Do you want to buy a baking tray for %P gold?", Topic=1 +"pot" -> Type=3465, Amount=1, Price=30, "Do you want to buy a pot for %P gold?", Topic=1 +"pan" -> Type=3466, Amount=1, Price=20, "Do you want to buy a pan for %P gold?", Topic=1 +"fork" -> Type=3467, Amount=1, Price=10, "Do you want to buy a fork for %P gold?", Topic=1 +"spoon" -> Type=3468, Amount=1, Price=10, "Do you want to buy a spoon for %P gold?", Topic=1 +"knife" -> Type=3469, Amount=1, Price=10, "Do you want to buy a knife for %P gold?", Topic=1 +"wooden","spoon" -> Type=3470, Amount=1, Price=5, "Do you want to buy a wooden spoon for %P gold?", Topic=1 +"cleaver" -> Type=3471, Amount=1, Price=15, "Do you want to buy a cleaver for %P gold?", Topic=1 +"spatula" -> Type=3472, Amount=1, Price=12, "Do you want to buy an oven spatula for %P gold?", Topic=1 +"rolling","pin" -> Type=3473, Amount=1, Price=12, "Do you want to buy a rolling pin for %P gold?", Topic=1 + +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"baking","tray" -> Type=3464, Amount=%1, Price=20*%1, "Do you want to buy %A baking trays for %P gold?", Topic=1 +%1,1<%1,"pot" -> Type=3465, Amount=%1, Price=30*%1, "Do you want to buy %A pots for %P gold?", Topic=1 +%1,1<%1,"pan" -> Type=3466, Amount=%1, Price=20*%1, "Do you want to buy %A pans for %P gold?", Topic=1 +%1,1<%1,"fork" -> Type=3467, Amount=%1, Price=10*%1, "Do you want to buy %A forks for %P gold?", Topic=1 +%1,1<%1,"spoon" -> Type=3468, Amount=%1, Price=10*%1, "Do you want to buy %A spoons for %P gold?", Topic=1 +%1,1<%1,"knife" -> Type=3469, Amount=%1, Price=10*%1, "Do you want to buy %A knives for %P gold?", Topic=1 +%1,1<%1,"wooden","spoon" -> Type=3470, Amount=%1, Price=5*%1, "Do you want to buy %A wooden spoons for %P gold?", Topic=1 +%1,1<%1,"cleaver" -> Type=3471, Amount=%1, Price=15*%1, "Do you want to buy %A cleavers for %P gold?", Topic=1 +%1,1<%1,"spatula" -> Type=3472, Amount=%1, Price=12*%1, "Do you want to buy %A oven spatulas for %P gold?", Topic=1 +%1,1<%1,"rolling","pin" -> Type=3473, Amount=%1, Price=12*%1, "Do you want to buy %A rolling pins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Please come back with more money." +Topic=1 -> "I hope next time." +} diff --git a/app/SabrehavenServer/data/npc/chester.npc b/app/SabrehavenServer/data/npc/chester.npc new file mode 100644 index 0000000..2263e56 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chester.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chester.npc: Datenbank für Chester, den Chef des TBI + +Name = "Chester Kahs" +Outfit = (131,10-28-47-95-0) +Home = [32348,32184,6] +Radius =2 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations, stranger." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am very busy %N." +BUSY,"hi$",! -> "Sorry, I am very busy %N." +BUSY,! -> NOP +VANISH,! -> "Take care out there!" + +"bye" -> "Take care out there!", Idle +"farewell" -> * +"news" -> "Sorry, almost news that are a little interesting are confidential." +"how","are","you"-> "I am troubled by all the mysteries out there." +"sell" -> "I am no tradesman, sorry." +"king" -> "King Tibianus III is our leader and my direct superior." +"superior" -> "I report directly to the king himself." +"report" -> "My reports are confidential and for the ears and eyes of the king only." +"job" -> "I am the head of the TBI." +"tbi$" -> "The Tibian Bureau of Investigation, the secret service of His Royal Highness." +"investigation" -> "We collect information about people and incidents." +"bureau" -> * +"people" -> "We know much about the citizens and some other people." +"citizen" -> "I only can give you some official information about our citizens. About whom do you wish to talk?" +"incident" -> "There are things that must be kept secret." +"secret" -> "Certain information is not for the eyes and ears of everyone. Please understand that." +"army" -> "Our army might be infested with spies already." +"spies" -> "Polymorphed Minotaurs, shapechanging demons, possessed innocents ... who can tell for sure." +"guard" -> "I think we can't trust the guards anymore." +"trust" -> "Too many possibilities to become a servant of darkness to trust ANYONE!" +"castle" -> "The castle isn't safe! I warned them of the entrance to the dungeons, but no one is litstening. How many people have to die before they do something about that?" +"dogs","of","war"-> "Even they can't stop a handful of demons." +"red","guard" -> "They are at my command now and then ... but it's a mistake to rely on anyone except yourself." +"secret","police"-> "Are you joking? What's secret in Tibia at all?" +"silver","guard" -> "The king's best. But is the best good enough to fight what stalks the nights?" +"city" -> "The city is open to almost everyone. That literally opens doors for all kinds of criminals and fiends." +"criminal" -> "There are so many murderers and thiefs out there that I wonder if there is some greater force of evil subtly encouraging that." +"fiend" -> "Not everything that walks our streets is human ... or even living." +"stutch" -> "He is one of the few people I can trust." +"harsky" -> * +"bozo" -> "He isn't the fool he pretends to be. So to what is he up to?" +"sam" -> "I say it was a mistake to rely on a single person for such vital services but having those venoreans here is even worse." +"gorn" -> "A man too concerned about profit to be trustworthy. This kind of man sells his soul to the highest bidder. It's just a question if he has done it already or will do it soon." +"frodo" -> "Have you noticed how easy it would be to poison his supplies and kill a great deal of people with ease?" +"benjamin" -> "Something happened to him that snapped his mind. Can we be sure what more might have happened to him unnoticed?" +"lugri" -> "At least you KNOW that you have to expect only evilness from this guy and that's the best one can say about him." +"gods" -> "We are just the pawns of the gods. The best we can expect is that our play amuses them enough to keep their interest in us so we might live a day or two longer." +"lynda" -> "She puts her trust in the help of beings she can't comprehend. Think by yourself if that's clever." +"quentin" -> "A peaceful man. But in our days peace is just an illusion. We are surrounded by enemies and dangers." +"enemy" -> "The people of the northern city, the minotaurs, the followers of Zathroth, the demons, and countless others!" +"enemies" -> * +"danger" -> "Danger is common like day and night for a Tibian, who keeps his eyes open." +"dungeon" -> "Monsters lurk in each corner of the dungeons, which spread beneath us, breeding in the shadows and plotting to destroy us all." +"ferumbras" -> "Some say he's the avatar of Zathroth himself, but perhaps the truth about him is even darker then the worst rumours can imagine." +"demon" -> "They say there are just two of them in the underground ruins! These damned fools! There are dozens of them! And the two they already saw are only some of the weakest of demonkind!" +"underground","ruin"-> "We have no clue what happened to the civilization that once dwelled underground, but their complete extinction should be a warning for us!" +"mcronald" -> "Have you ever wondered what these caves under their farm are good for? And have you noticed how many adventurers go down there and never return? Well, think about it!" +"sorcerer" -> "I don't know where they got their secret spells in the first place, nor did most of them know ... If I were a sorcerer that would be a fact to give me nightmares." +"knight" -> "It's too easy to become a knight. They take almost everyone. And if you look in the streets you can see what happens if you give training and a flashy title to almost everyone." +"paladin" -> "They should be noble warriors, but does it take bravery to shoot someone from a certain distance? The former paladins were virtuous heroes, the ones you meet today are just simple treasure hunters." +"druid" -> "It is said that druids are preservers of life and good aligned, but let me ask you if it's so 'good' to sell runes to the highest bidder, no matter who that might be? I think you get the point!" +"truth" -> "The dungeons are full of hideous monsters, unnamed terrors, unsolved riddles ... and maybe some answers. Believe me! The truth is down there ... somewhere!" +"ruthless","seven"-> "We know little about them. But even that gives me nightmares! But it's your lucky day, since this information is confidential, and so it can't bother you." +"aruda" -> "This woman is a clever thief, so watch out when you are talking to her." +"partos" -> "This criminal was wanted for many crimes. At last he got caught and put to jail." +"excalibug" -> "We are surrounded by myths, living and dead. How can someone doubt that there IS something like Excalibug somewhere?" +"necromant","nectar" -> "Followers of evil are investigating about that, though I guess even they don't know what it's good for. Perhaps just a myth of evil." + +"rebellion" -> "I have far too few information about the rebellion, but we suspect the followers of Zathroth behind it." +"berfasmur","is","ferumbras" -> "Yes, thats what I figured out, too. Just one of his disguises." +"berfasmur" -> "Strange name, isn't it? Play around with the letters and you are in for a surprise." +"gamel","rebel" -> "Are you saying that Gamel is a member of the rebellion?", Topic=1 + +Topic=1,"no" -> "Then don't bother me with that. I am a busy man." +Topic=1,"yes" -> "Do you know what his plans are about?", Topic=2 +Topic=2,"magic","crystal","lugri","deathcurse" -> Type=3061, Amount=1, "That is terrible! Will you give me the crystal?", Topic=3 +Topic=2 -> "Tell me precisely what he asked you to do! What, to whom, and what for! It's important!", Topic=2 +Topic=3,"no" -> "Traitor!", Burning(25,25), EffectOpp(6), EffectMe(14), Delete(Type), Idle +Topic=3,"yes",Count(Type)>=Amount -> "Thank you! Take this ring. If you ever need a healing, come, bring the scroll, and ask me to 'heal'.", Delete(Type), Type=3052, Amount=1, Create(Type) +Topic=3,"yes",Count(Type) "Sorry, you have none." +"heal" -> Type=3052, Amount=1, "Do you need the healing now?", Topic=4 +Topic=4,"no" -> "As you whish." +Topic=4,"yes",Count(Type)>=Amount -> "So be healed!", Delete(Type), HP=1000, EffectOpp(13) +Topic=4,"yes",Count(Type) "Sorry, you are not worthy!" +} diff --git a/app/SabrehavenServer/data/npc/chondur.npc b/app/SabrehavenServer/data/npc/chondur.npc new file mode 100644 index 0000000..94ac4a2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chondur.npc @@ -0,0 +1,159 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Chondur" +Outfit = (154,38-113-119-116-3) +Home = [32361,32550,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, child. What do you want in an old shaman's hut?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"shaman" -> "I was chosen by the spirits of the ancestors to serve the people as healer and spiritual leader." +"spirits" -> "Spirits are all around us and on these isles they are especially strong and numerous." +"ancestors" -> "The ancestors still have contact to the world of those alive. If you know how to talk to them, they can be wise guides and powerful friends." +"isles" -> "These isles are a place of strong but secret magic. This magic is strong enough to devour the souls of the weak." +"magic" -> "Magic here is like a powerful and wild beast. You need all your strength to master it. If you fail, your punishment will be swift and hard. Voodoo gives me the power and wisdom necessary to handle those energies." +"magic currents" -> "Hm, well one could call it like this I guess. Magic is strong on these isles. Too strong for many to handle it safely." +"secrets" -> "The spirits speak about a great battle on these isles. Cause for the conflict was the strong magic here. ...", + "The peaceful dwellers, that used to live here in a magical paradise created by themselves, were attacked by a dark power from the depths of the ocean. ...", + "The war destroyed both races and changed the flow of magic forever." +"voodoo" -> "Together with the spirits of the ancestors, I seek for wisdom. Together we can change the flow of magic to do things that are beyond the limits of ordinary magic. ...", + "In conversations with the spirits, I gain insight into secrets that would have been lost otherwise." +"dark power" -> "In the shadow of the depth, a strange and enigmatic race evolved. Using magic to the darkest purposes. ...", + "What they lacked in understanding magic like the dwellers, they made up for in number and destructive determination." +"forever" -> "Yes, the ancestors hint that there has been some disaster in the past that disarranged the magic of the isles and destroyed whole civilisations." +"disaster" -> * +"disarranged" -> "Hm, well one could call it like this I guess. Magic is strong on these isles. Too strong for many to handle it safely." +"civilisations" -> "The spirits tell about two powerful races that once called this area their home. They annihilated themselves in a war." +"quaras" -> "This race rose from slavery to a shadow of their former masters. The dark power that once ruled the ocean here was annihilated by some disaster and the quara used to be one of their serving races." +"mermaid" -> "The mermaids are a possible offspring of one of the magical creatures that some ancient race once created here. ...", + "They are vain and self-centred. Their magic is powerful and almost impossible to reverse once it is cast on some human. ...", + "The only good thing is they quickly lose interest into their victims if something of slightly more interest comes up. ...", + "If you are ever in trouble with a mermaid, look for a distraction." +"king" -> "The king is far away. Our myth are closer to us than any king in faraway Thais could ever be." +"thais" -> "The people see the Thaian occupation like a crippling disease that befalls our isles. Some try to live with it, others are looking for a cure." +"cure" -> "It's my part to cure the body and the spirit. Only the people can cure the land they inhabit." +"venore" -> "The tradesmen of Venore are tainted with greed. They have so little spirituality that their souls must be starving." +"cult" -> "The cult is a mockery of true voodoo. They play with powers they don't even rudimentarily understand and some dark and sinister power is working behind the curtains. Some evil puppet master is misusing them for plans unknown to me." +"goroma" -> "One of the Forbidden Islands. No sane captain will agree to bring you there." +"banana" -> "A banana staff is the sign of a high ape magician." +"pirates" -> "You have to understand that there are two groups of people that are called pirates. One consists of evil thieves and murderers for whom the word pirate actually stands for. ...", + "The others are resistance fighters for whom old ideals are hold true and that are the assertors of the native people." + +# Goroma_Counter_Spell_Quest +"spellbook",QuestValue(17509)=0,Count(6120)>=1 -> "Ah, thank you very much! I will honour his memory.", Amount=1, Delete(6120), SetQuestValue(17509,1) +"counterspell",QuestValue(17509)=0 -> "You should not talk about things you don't know anything about." +"energy","field",QuestValue(17509)=0 -> * +"spellbook",QuestValue(17509)=0 -> * +"energy","field",QuestValue(17509)>0 -> "Ah, the energy barrier set up by the cult is maintained by lousy magic, but is still effective. Without a proper counterspell you won't be able to pass it." + +"counterspell",QuestValue(17509)=1 -> "You mean, you are interested in a counterspell to cross the energy barrier on Goroma?", Topic=1 +Topic=1,"yes" -> "This is really not advisable. Behind this barrier, strong forces are raging violently. Are you sure that you want to go there?", Topic=2 +Topic=1 -> "It's much safer for you to stay here anyway, trust me." +Topic=2,"yes" -> "I guess I cannot stop you then. Since you told me about my apprentice, it is my turn to help you. I will perform a ritual for you, but I need a few ingredients. ...", + "Bring me one fresh dead chicken, one fresh dead rat and one fresh dead black sheep, in that order. Summoned ones will do as well as natural ones.", SetQuestValue(17509,2) +Topic=2 -> "It's much safer for you to stay here anyway, trust me." + +"counterspell",QuestValue(17509)=2 -> Type=4330, Amount=1, "Did you bring the fresh dead chicken?", Topic=3 +"chicken",QuestValue(17509)=2 -> * +Topic=3,"yes",Count(Type)>=Amount -> "Very good! 'Your soul shall be protected!' Now, I need a fresh dead rat.", Delete(Type), SetQuestValue(17509,3) +Topic=3,"yes" -> "You don't have any dead chicken with you don't you?" +Topic=3 -> "Maybe another time." + +"counterspell",QuestValue(17509)=3 -> Type=3994, Amount=1, "Did you bring the fresh dead rat?", Topic=4 +"rat",QuestValue(17509)=3 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Very good! 'You shall face black magic without fear!' Now, I need a fresh dead black sheep.", Delete(Type), SetQuestValue(17509,4) +Topic=4,"yes" -> "You don't have any dead rat with you don't you?" +Topic=4 -> "Maybe another time." + +"counterspell",QuestValue(17509)=4 -> Type=4095, Amount=1, "Did you bring the fresh dead black sheep?", Topic=5 +"sheep",QuestValue(17509)=4 -> * +Topic=5,"yes",Count(Type)>=Amount -> "Very good! 'EVIL POWERS SHALL NOT KEEP YOU ANYMORE! SO BE IT!'", Delete(Type), SetQuestValue(17509,5) +Topic=5,"yes" -> "You don't have any dead black sheep with you don't you?" +Topic=5 -> "Maybe another time." + +"counterspell",QuestValue(17509)>4 -> "Hm. I don't think you need another one of my counterspells to cross the barrier on Goroma." + +# Meriana_Quest +"mission",QuestValue(17524)=0 -> "The evil cult has placed a curse on one of the captains here. I need at least five of their pirate voodoo dolls to lift that curse.", SetQuestValue(17524,1) +"task",QuestValue(17524)=0 -> * + +"mission",QuestValue(17524)>0,QuestValue(17524)<5 -> Type=5810, Amount=1, "Did you bring one of the required pirate voodoo dolls?", Topic=6 +"task",QuestValue(17524)>0,QuestValue(17524)<5 -> * +"doll",QuestValue(17524)>0,QuestValue(17524)<5 -> * + +"mission",QuestValue(17524)=5 -> Type=5810, Amount=1, "Did you bring the last of the required voodoo dolls?", Topic=6 +"task",QuestValue(17524)=5 -> * +"doll",QuestValue(17524)=5 -> * +Topic=6,"yes",Count(Type)>=Amount -> "Now I can weaken that curse a bit. Thank you.", Delete(Type), SetQuestValue(17524,QuestValue(17524)+1) +Topic=6,"yes",Count(Type)>=Amount,QuestValue(17524)=5 -> "Finally I can put an end to that curse. I thank you so much.", Delete(Type), SetQuestValue(17524,QuestValue(17524)+1) +Topic=6,"yes" -> "Sorry, you do not have it." +Topic=6 -> "Maybe another time." + +# Shaman_Outfit_Quest +"outfit",QuestValue(17570)=0 -> "The Shaman outfit can wear only the one's with the great spiritual wisdom." +"addon",QuestValue(17570)=0 -> * + +"outfit",QuestValue(17524)=6,QuestValue(17570)=1 -> "The time has come, my child. I sense great spiritual wisdom in you and I shall grant you a sign of your progress if you can fulfil my task." +"addon",QuestValue(17524)=6,QuestValue(17570)=1 -> * + +"outfit",QuestValue(17570)=5 -> "Yours spiritual wisdom is outstanding %N." +"addon",QuestValue(17570)=5 -> * + +"mission",QuestValue(17524)=6,QuestValue(17570)=1 -> "Deep in the Tiquandan jungle a monster lurks which is seldom seen. It is the revenge of the jungle against humankind. ...", + "This monster, if slain, carries a rare root called mandrake. If you find it, bring it to me. Also, gather 5 of the voodoo dolls used by the mysterious dworc voodoomasters. ...", + "If you manage to fulfil this task, I'll grant you your own staff. Have you understood everything and are you ready for this test?", Topic=7 +"task",QuestValue(17524)=6,QuestValue(17570)=1 -> * +Topic=7,"yes" -> "Good! Come back once you've found a mandrake and collected 5 dworcish voodoo dolls.", SetQuestValue(17570,2), SetQuestValue(17594,1) +Topic=7 -> "Maybe another time." + +"mission",QuestValue(17570)=2 -> "Have you gathered the mandrake and the 5 voodoo dolls from the dworcs?", Topic=8 +"task",QuestValue(17570)=2 -> * +"addon",QuestValue(17570)=2 -> * +"outfit",QuestValue(17570)=2 -> * +"mandrake",QuestValue(17570)=2 -> * +"doll",QuestValue(17570)=2 -> * +Topic=8,"yes",Count(3002)>=5,Count(5014)>=1 -> "I'm proud of you my child, excellent work. This staff shall be yours from now on!", DeleteAmount(3002,5), DeleteAmount(5014,1), SetQuestValue(17570,3), AddOutfitAddon(154,2), AddOutfitAddon(158,2), EffectOpp(13) +Topic=8,"yes" -> "You don't have the required items with you." +Topic=8 -> "Maybe another time." + +"mission",QuestValue(17570)=3 -> "You have successfully passed the first task. If you can fulfil my second task, I will grant you a mask like the one I wear. Will you listen to the requirements?", Topic=9 +"task",QuestValue(17570)=3 -> * +"addon",QuestValue(17570)=3 -> * +"outfit",QuestValue(17570)=3 -> * +Topic=9,"yes" -> "The dworcs of Tiquanda like to wear certain tribal masks which I'd like to take a look at. Please bring me 5 of these masks. ...", + "Secondly, the high ape magicians of Banuta use banana staffs. I'd love to learn more about theses staffs, so please bring me 5 of them, too. ...", + "If you manage to fulfil this task, I'll grant you your own mask. Have you understood everything and are you ready for this test?", Topic=10 +Topic=9 -> "Maybe another time." +Topic=10,"yes" -> "Good! Come back once you have collected 5 tribal masks and 5 banana staffs.", SetQuestValue(17570,4) +Topic=10 -> "Maybe another time." + +"mission",QuestValue(17570)=4 -> "Have you gathered the 5 tribal masks and the 5 banana staffs?", Topic=11 +"task",QuestValue(17570)=4 -> * +"addon",QuestValue(17570)=4 -> * +"outfit",QuestValue(17570)=4 -> * +"mask",QuestValue(17570)=4 -> * +"staff",QuestValue(17570)=4 -> * +Topic=11,"yes",Count(3348)>=5,Count(3403)>=5 -> "Well done, my child! I hereby grant you the right to wear a shamanic mask. Do it proudly.", DeleteAmount(3348,5), DeleteAmount(3403,5), SetQuestValue(17570,5), AddOutfitAddon(154,1), AddOutfitAddon(158,1), EffectOpp(13) +Topic=11,"yes" -> "You don't have the required items with you." +Topic=11 -> "Maybe another time." + +"mission" -> "Sorry, I don't have any missions for you." +"task" -> "Sorry, I don't have any missions for you." + +# Wooden_Stake_Quest +"stake",ExpiringQuestValue(17577)>0 -> "Sorry, but I'm still exhausted from the last ritual. Please come back later." +"stake",QuestValue(17576)=11 -> "Ten prayers for a blessed stake? Don't tell me they made you travel whole Tibia for it! Listen, child, if you bring me a wooden stake, I'll bless it for you. ", SetQuestValue(17576,12) +"stake",QuestValue(17576)=12,ExpiringQuestValue(17577)<0 -> Type=5941, Amount=1, "Would you like to receive a spiritual prayer to bless your stake?", Topic=12 +Topic=12,"yes",Count(Type)>=Amount -> " Sha Kesh Mar!", Delete(Type), Create(5942), SetExpiringQuestValue(17577, 604800000) +Topic=12,"yes" -> "You don't have a wooden stake." +Topic=12 -> "Maybe another time." +} diff --git a/app/SabrehavenServer/data/npc/christoph.npc b/app/SabrehavenServer/data/npc/christoph.npc new file mode 100644 index 0000000..f17bd67 --- /dev/null +++ b/app/SabrehavenServer/data/npc/christoph.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# christoph.npc: Datenbank für eine Stadtwache in Venore + +Name = "Christoph" +Outfit = (131,113-113-113-115-0) +Home = [32885,32031,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/chrystal.npc b/app/SabrehavenServer/data/npc/chrystal.npc new file mode 100644 index 0000000..d0288d5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/chrystal.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chyrstal.npc: Datenbank für die Postfrau Chrystal + +Name = "Chrystal" +Outfit = (136,116-79-117-76-0) +Home = [33174,31811,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "At your service %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please be patient %N. I'll be with you in moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Who is next?" + +"bye" -> "Who is next?", Idle +"name" -> "My name is Chrystal." +"job" -> "I am responsible for this post office." + +"kevin" -> "Mr. Postner is the leader of our guild and the most prominent postofficer in the whole land." +"postner" -> * +"postmasters","guild" -> "Yes, our guild is the lifeblood of the tibia cominity so to say." +"join" -> "You can apply to join only at our headquarter." +"headquarter" -> "You can find it on the road from Thais to Kazordoon." + + +"measurements",QuestValue(234)>0,QuestValue(241)<1 -> "If its necessary ... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(241,1) + +"time" -> "It is %T right now." +"king" -> "Hail to the king!" +"tibianus" -> * +"army" -> "The army ensures the safety of the traderoutes and of our mail system." +"ferumbras" -> "I bet he never gets any letters." +"excalibug" -> "Better ask knights about that." +"tibia" -> "Our post system spans the entire known world." +"thais" -> "We deliver letters and parcels even there." +"carlin" -> * +"kazordoon" -> * +"ab'dendriel" -> * +"edron" -> "Our post system even delivers letters and parcels to and from this isle." +"news" -> "Sorry, that's postal secret." +"rumors" -> * + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(110,8) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(110,6), Create(110,10) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/cipfried.npc b/app/SabrehavenServer/data/npc/cipfried.npc new file mode 100644 index 0000000..3914e9a --- /dev/null +++ b/app/SabrehavenServer/data/npc/cipfried.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cipfried.npc: Datenbank fuer den Moench Cipfried + +Name = "Cipfried" +Outfit = (57,0-0-0-0-0) +Home = [32097,32217,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N! Feel free to ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N. I already talk to someone!", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad, %N. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Well, bye then." + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "I am just a humble monk. Ask me if you need help or healing." +"name" -> "My name is Cipfried." +"monk" -> "I sacrifice my life to serve the good gods of Tibia." +"tibia" -> "That's where we are. The world of Tibia." +"rookgaard" -> "The gods have chosen this isle as the point of arrival for the newborn souls." +"god" -> "They created Tibia and all life on it. Visit our library and learn about them." +"life" -> "The gods decorated Tibia with various forms of life. Plants, the citizens, and even the monsters." +"plant" -> "Just walk around. You will see grass, trees, and bushes." +"citizen" -> "Only few people live here. Walk around and talk to them." +"obi" -> "He is a local shop owner." +"al","dee" -> * +"seymour" -> "Seymour is a loyal follower of the king and responsibe for the academy." +"academy" -> "You should visit Seymour in the academy and ask him about a mission." +"willie" -> "He is a fine farmer. The farm is located to the left of the temple." +"monster" -> "They are a constant threat. Learn to fight by hunting rabbits, deers and sheeps. Then try to fight rats, bugs and perhaps spiders." +"help" -> "First you should try to get some gold and buy better equipment." +"hint" -> * +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "You have to slay monsters and take their gold. Or sell food at Willie's farm." +"money" -> "If you need money, you have to slay monsters and take their gold. Look for spiders and rats." +"rat" -> "In the north of this temple you find a sewer grate. Use it to enter the sewers if you feel prepared. Don't forget a torch; you'll need it." +"sewer" -> * +"equipment" -> "First you need some armor and perhaps a better weapon or a shield. A real adventurer needs a rope, a shovel, and a fishing pole, too." +"fight" -> "Take a weapon in your hand, activate your combat mode, and select a target. After a fight you should eat something to heal your wounds." +"slay" -> * +"eat" -> "If you want to heal your wounds you should eat something. Willie sells excellent meals. But if you are very weak, come to me and ask me to heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +"time" -> "Now, it is %T, my child." +} diff --git a/app/SabrehavenServer/data/npc/clark.npc b/app/SabrehavenServer/data/npc/clark.npc new file mode 100644 index 0000000..c881eeb --- /dev/null +++ b/app/SabrehavenServer/data/npc/clark.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# clark.npc: Datenbank für den captain der Wache clark + +Name = "Clark" +Outfit = (129,19-79-98-95-0) +Home = [32636,32796,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye." + +"bye" -> "Goodbye.", Idle +"farewell" -> * +"job" -> "I am the captain of the guards and responsible for upholding law and order in the colony." +"name" -> "I am called Clark." +"guards" -> "The guards of Port Hope are led by Lord Seamus, a nobleman from Thais." +"seamus" -> "He is ill and I am responsible for upholding the law now." +"time" -> "It is %T right now." +"king" -> "Our king can be proud of this colony." +"venore" -> "The Venorans are fine and generous people. It's a good thing that they put all this effort into this colony. I don't know what we would do if it weren't for them." +"thais" -> "Thais is a big city but if we do well enough here, Port Hope might one day rival its size." +"carlin" -> "It's a shame those women still get away with their independence. Friends told me that too many of Thais's resources are wasted in useless projects and the army is too weak to claim back what is ours." +"edron" -> "The knights of Edron are somewhat arrogant. They use the resources of the isle only for themselves instead of sharing it." +"jungle" -> "The jungle is our first and foremost enemy." + +"tibia" -> "I would guess a good part of the world is under Thaian rule." + +"kazordoon" -> "As long as the dwarves stay there and don't expand, I think it's ok to let them have this ugly piece of rock." +"dwarves" -> "Dwarves are impressive fighters. The Venorans talked about hiring some of them as mercenaries to help us out." +"dwarfs" -> * +"ab'dendriel" -> "My guess is that those elves are secretly allied with Carlin. I would not trust an elf if I would meet one." +"elves" -> * +"elfs" -> * +"darama" -> "This continent will be ours one day." +"darashia" -> "A village of harmless cultists. They pose no real threat and could easily been integrated into the Thaian realm." +"ankrahmun" -> "This city is a threat to Thais's domination of Darama. Yet, we are too weak to handle this threat." +"ferumbras" -> "He is the enemy number one to the kingdom and all good people." +"excalibug" -> "I heard the knights of Edron hide it somewhere instead of using it for the good of the country." +"apes" -> "Rest assured, we will handle that problem." +"lizard" -> "They pose no real threat to us." +"dworcs" -> "They fear our power and hide in some caves." +} diff --git a/app/SabrehavenServer/data/npc/clyde.npc b/app/SabrehavenServer/data/npc/clyde.npc new file mode 100644 index 0000000..9c4aeff --- /dev/null +++ b/app/SabrehavenServer/data/npc/clyde.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# clyde.npc: Datenbank für den Tavernenbesitzer Clyde + +Name = "Clyde" +Outfit = (128,98-40-48-95-0) +Home = [32573,32753,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, dear customer." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, visit us again.", Idle +"farewell" -> * +"job" -> "I am your host. I run this tavern." +"name" -> "I am Clyde, your host." +"time" -> "Don't worry about the time. Take a seat, have a drink. Time runs differently in Port Hope." +"king" -> "I wish the king would be a bit more concerned about this colony. I am convinced with a few more resources, we could improve Port Hope a lot." +"venore" -> "It's not always easy to deal with those Venoran tradesmen. I must admit they don't show any interest in my area of business. Just between you and me, my friend, sometimes they give me shivers." +"thais" -> "Sadly, Thais is far away and you will notice that in many places. You'll find out about this yourself, so lets talk about something else please." +"carlin" -> "I know only little about Carlin and here nobody cares about it for sure." +"edron" -> "As I started business here, I was hoping for a second Edron. I have not abandoned my hope though. Afer all, this place is called Port Hope, isn't it?" +"jungle" -> "This forest is not an ordinary forest. It's more like a force of nature, like a river or even a storm." +"tibia" -> "I have seen only little of this world. Probably it should be you telling me about the world, and not the other way around." +"kazordoon" -> "If you want to learn something about Kazordoon you should talk to our local dwarves." +"dwarves" -> "There was a handful of dwarves that came here when the colony was founded. They were looking for treasures and gold as far as I know. After some argument a bunch of them left, they headed into the jungle and were never seen again." +"dwarfs" -> * +"ab'dendriel" -> "Sadly it is next to nothing that I know about the elves and their city." +"elves" -> * +"elfs" -> * +"darama" -> "It's a continent full of extremes. The jungle in the humid east, the desert in the dry west." +"darashia" -> "I was there quite often, using the flying carpet. It's quite different from the other towns I have seen, but surely worth a trip." +"ankrahmun" -> "If I were you I'd stay as far away from this town as I could. It is ruled by an undead abomination and its inhabitants worship death." +"ferumbras" -> "This incarnation of evil seems to concentrate his efforts on Thais and its surroundings, but who knows what comes next into the mind of this madman?" +"excalibug" -> "The rumours I overheard did not mention this continent as one of its hiding places." +"apes" -> "They seem to live in the depth of the jungle in ruins that show the markings of the lizard folk. I wonder if they now try to conquer our city too." +"lizard" -> "The lizards are hostile to us. They probably see no big difference between us and the ape people." +"dworcs" -> "The dworcs live in the south in an underground network consisting of caves. They use poisoned weapons and love to build all kind of traps. You don't want to know the fate of those that have been trapped, believe me." + +"buy" -> "I can offer you bread, cheese, ham, or meat as well as several drinks." +"offer" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +} diff --git a/app/SabrehavenServer/data/npc/cobra.npc b/app/SabrehavenServer/data/npc/cobra.npc new file mode 100644 index 0000000..59063bb --- /dev/null +++ b/app/SabrehavenServer/data/npc/cobra.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cobra.npc: Datenbank für die steincobra + +Name = "Cobra" +Outfit = (0,2051) +Home = [33366,32855,14] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",QuestValue(270) > 0,Poison>500,! -> "Venture the path of decay!", Teleport(33397,32836,14), EffectOpp(11),SetQuestValue(270,0) +ADDRESS,"hi$",QuestValue(270) > 0,Poison>500,! -> * + +ADDRESS,"hello$",! -> "Begone! Hissssss! You bear not the mark of the cobra!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sssssilence!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hissssssssss." + +} diff --git a/app/SabrehavenServer/data/npc/cornelia.npc b/app/SabrehavenServer/data/npc/cornelia.npc new file mode 100644 index 0000000..8de769a --- /dev/null +++ b/app/SabrehavenServer/data/npc/cornelia.npc @@ -0,0 +1,127 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cornelia.npc: Datenbank für die Rüstungshändlerin Cornelia + +Name = "Cornelia" +Outfit = (139,95-57-102-115-0) +Home = [32334,31796,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the finest armorshop in the land, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> "One moment please, %N.", Queue +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * +"job" -> "I run this armoury. If you want to proctect your life you'd better buy my wares." +"shop" -> * +"name" -> "My name is Cornelia." +"time" -> "It's %T right now." +"help" -> "I sell and buy armor, helmets, and shields. Only the dwarfs can make better ones." +"dwarfs" -> "The ancient dwarfen clan halls are far to the east from here." +"monster" -> "With my armor you need not fear any monsters!" +"dungeon" -> "While exploring the dungeons of the land you will learn how important a good armor is." +"sewer" -> "Sewers are males' business." +"thanks" -> "You are welcome." +"thank","you" -> "You are welcome." +"ghostlands" -> "THE GHOSTLANDS??? Make sure to buy the best protection in store before you get even close to them." + +"buy" -> "What do you need? I sell armor, helmets, shields, and trousers." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are armor, helmets, trousers, and shields." + +"weapon" -> "Ask Rowenna in the other shop about it." +"helmet" -> "I am selling leather helmets, chain helmets, brass helmets, and viking helmets. What do you want?" +"armor" -> "I am selling leather armor, chain armor, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, brass shields, and plate shields. What do you want?" +"trousers" -> "I am selling chain legs and brass legs. What do you need?" +"legs" -> * + +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "Do you want to buy brass legs for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "Do you want to buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "Do you want to buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "Do you want to buy %A brass legs for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "Do you want to sell plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 + +"sell", %1,1<%1, "leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell", %1,1<%1, "plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell", %1,1<%1, "steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell", %1,1<%1, "wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell", %1,1<%1, "battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"addon",QuestValue(17558)=5,male -> "Ah, you must be the hero Trisha talked about. I'll prepare the shoulder spikes for you. Please give me some time to finish. ", SetQuestValue(17558,6), SetExpiringQuestValue(17559, 7200000) +"armor",QuestValue(17558)=5,male -> * +"addon",QuestValue(17558)=5,female -> "Ah, you must be the hero Trisha talked about. I'll prepare the shoulder spikes for you. Please give me some time to finish.", SetQuestValue(17558,6), SetExpiringQuestValue(17559, 7200000) +"armor",QuestValue(17558)=5,female -> * + +"addon",ExpiringQuestValue(17559)>0 -> "Please give me some time to finish it." +"armor",ExpiringQuestValue(17559)>0 -> * + +"addon",ExpiringQuestValue(17559)<0,QuestValue(17558)=6,male -> "Finished! Since you are a man, I thought you probably wanted two. Men always want that little extra status symbol. ", SetQuestValue(17558,7), AddOutfitAddon(142,1), AddOutfitAddon(134,1), EffectOpp(13) +"armor",ExpiringQuestValue(17559)<0,QuestValue(17558)=6,male -> * +"addon",ExpiringQuestValue(17559)<0,QuestValue(17558)=6,female -> "Finished! Wear it proudly sister like Trisha do.", SetQuestValue(17558,7), AddOutfitAddon(142,1), AddOutfitAddon(134,1), EffectOpp(13) +"armor",ExpiringQuestValue(17559)<0,QuestValue(17558)=6,female -> * + +"addon",QuestValue(17558)=7 -> "Sorry, Trisha told me to adorn only one armor for you and you have already received one." +"armor",QuestValue(17558)=7 -> * +} diff --git a/app/SabrehavenServer/data/npc/costello.npc b/app/SabrehavenServer/data/npc/costello.npc new file mode 100644 index 0000000..b78dfc5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/costello.npc @@ -0,0 +1,102 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# costello.npc: Datenbank für den Abt Costello + +Name = "Costello" +Outfit = (57,0-0-0-0-0) +Home = [32180,31936,7] +Radius = 3 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(220)>0,! -> "WHAT? You have to be that trespasser my brothers told me about! Entering the restricted area is a horrible crime!",Topic=3 +ADDRESS,"hi$",QuestValue(220)>0,! -> * + + +ADDRESS,"hello$",! -> "Welcome, %N! Feel free to tell me what has brought you here." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(220)>0,! -> "WHAT? You have to be that trespasser my brothers told me about! Entering the restricted area is a horrible crime!",Idle +BUSY,"hi$",QuestValue(220)>0,! -> * + +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",QuestValue(220)>0 -> "I won't waste my healing powers on you, spawn of evil!", Idle +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking that bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +topic=3,"crime",Level<20,! -> "The only way to redeem such an offense is the sacrifice of 500 gold pieces! Are you willing to pay that sum?",Price=500,Topic=4 +topic=3,"absolution",Level<20,! -> * +topic=3,"crime",Level<40,! -> "The only way to redeem such an offense is the sacrifice of 1.000 gold pieces! Are you willing to pay that sum?",Price=1000,Topic=4 +topic=3,"absolution",Level<40,! -> * +topic=3,"crime",Level<60,! -> "The only way to redeem such an offense is the sacrifice of 5.000 gold pieces! Are you willing to pay that sum?",Price=5000,Topic=4 +topic=3,"absolution",Level<60,! -> * +topic=3,"crime",Level>59,! -> "The only way to redeem such an offense is the sacrifice of 10.000 gold pieces! Are you willing to pay that sum?",Price=10000,Topic=4 +topic=3,"absolution",Level>59,! -> * +topic=3,! -> "Be gone!", Idle + +Topic=4,"yes",CountMoney "Begone! You do not have enough money!", Idle +Topic=4,"yes",! -> "So receive your absolution! And never do such a thing again!", DeleteMoney, EffectOpp(13),SetQuestValue(220,0) +Topic=4,! -> "Then be gone!", Idle + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am the abbot of the white raven monastery on the isle of the kings." +"name" -> "My name is Costello." +"tibia" -> "That is the name of our world and its major continent." +"god" -> "They created Tibia and all life on it." +"life" -> "On Tibia there are many forms of life. Plants, the citizens, and monsters." +"plant" -> "Just walk around, you will see grass, trees, and bushes." +"white","raven" -> "The legends tell us of a white raven which lead the ship of the first monk of our order here. He discovered this isle and the caves beneath it." +"caves" -> "Anselm, the first of our order, discovered them while looking for a suitable burial place for his king." +"anselm" -> "He was a humble and pious man, and he was chosen by the royal family of thais to find a resting place for their dead." +"isle" -> "We founded our monastery to guard the royal tombs and to gather wisdom and knowledge." +"order" -> * +"wisdom" -> "You are allowed to enter the library upstairs. Stay there and don't go upstairs, because that area is reserved for members of our order." +"knowledge" -> * +"tibianus" -> "One day every Tibianus ends up here." +"king" -> "The bygone leaders of the Thaian empire rest beneath this monastery in tombs and crypts." +"tomb",QuestValue(63)<1 -> "The tombs and crypts of the Thaian lineage are well protected deep beneath our abbey, although ... but surely this will not interest you." +"crypts",QuestValue(63)<1 -> * +"although",QuestValue(63)<1 -> "In my dreams the dead are talking to me about torment and disturbance. But I might be imagining things." +"interest",QuestValue(63)<1 -> * +"imagining",QuestValue(63)<1 -> "Brother Fugio, the only one of our order who is allowed to enter the crypts, assures me everything is all right." +"torment",QuestValue(63)<1 -> * +"disturbance",QuestValue(63)<1 -> * + +"fugio",QuestValue(63)<1 -> "To be honest, I fear the omen in my dreams may be true. Perhaps Fugio is unable to see the danger down there. Perhaps ... you are willing to investigate this matter?", topic=1 +"alright",QuestValue(63)<1 -> * +Topic=1,"yes" -> "Thank you very much! From now on you may open the warded doors to the catacombs", SetQuestValue(63,1), SetQuestValue(64,1), SetQuestValue(17584,1) + +# 63=forschen 64=questtür wg. eingang von unten + +Topic=1,"no" -> "Please forgive an old man, I shouldn't have asked a stranger anyways." +Topic=1 -> * + + +"diary",QuestValue(219)=0 -> "Do you want me to inspect a diary?",Type=3212, Amount=1,topic=2 +"diary",QuestValue(219)>0 -> "Thank you again for handing me that diary." + +Topic=2,"yes",Count(Type)>=Amount -> "By the gods! This is brother Fugio's handwriting and what I read is horrible indeed! You have done our order a great favour by giving this diary to me! Take this blessed Ankh. May it protect you in even your darkest hours.", Delete(Type), Create(3214),SetQuestValue(219,2),SetQuestValue(17584,2) +Topic=2,"no" -> "Uhm, as you wish." +Topic=2 -> * + +"passage",QuestValue(63)=1,QuestValue(64)=1 -> "Oh of course, I will order Jack and the fisher Windtrouser to give you transportation if needed.", SetQuestValue(62,1) +"passage" -> "You should not be here at all and I won't allow anyone to transport you from or to this isle." + +"ferumbras" -> "Don't mention this servant of evil here." +"excalibug" -> "Sadly we have only little knowledge on this topic." +"news" -> "Sorry, we rarely hear anything new here." +"monster" -> "There are really too many of them in Tibia. But who are we to question the wisdom of the gods?" + +"heal$",QuestValue(220)>0 -> "I won't waste my healing powers on you, spawn of evil!", Idle +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking that bad. Sorry, I can't help you." + +} diff --git a/app/SabrehavenServer/data/npc/crone.npc b/app/SabrehavenServer/data/npc/crone.npc new file mode 100644 index 0000000..51689c4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/crone.npc @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# crone.npc: Bansheevettel in Vashresamuns Grabmal + +Name = "The Crone" +Outfit = (78,0-0-0-0-0) +Home = [33229,32522,14] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N... mortal" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Patience, mortal %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I don't remember my name, neither my days as a mortal." +"job" -> "Once I was Vashresamun's favourite handmaiden. But I have fallen from her grace and now I am exiled from her tomb." +"grace" -> "Do not ask about that, mortal. Memories bring too much grief." +"fallen" -> * +"exiled" -> * + +"vashresamun" -> "I mourn the dark day I was exiled from her tomb." +"tomb" -> "Her tomb is sealed and can only be entered by a certain melody." +"melody" -> "Vashresamun erased the memory of the tune from my mind, I only remember its name: the secret of the rose garden." +} diff --git a/app/SabrehavenServer/data/npc/dabui.npc b/app/SabrehavenServer/data/npc/dabui.npc new file mode 100644 index 0000000..b1ee084 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dabui.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dabui.npc: Datenbank für die Stadtwache Dabui in Darashia + +Name = "Dabui" +Outfit = (129,95-10-26-76-0) +Home = [33229,32409,7] +Radius = 1 + +Behaviour = { +@"guards-darama.ndb" +} diff --git a/app/SabrehavenServer/data/npc/dagomir.npc b/app/SabrehavenServer/data/npc/dagomir.npc new file mode 100644 index 0000000..de5c927 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dagomir.npc @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dagomir.npc: Datenbank für den "Bankier" Dagomir + +Name = "Dagomir" +Outfit = (130,0-2-41-76-0) +Home = [33018,32047,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Wha... what?? HOW DARE YOU!!?? LEAVE ME ALONE ON MY TOILET AT ONCE!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +} diff --git a/app/SabrehavenServer/data/npc/dallheim.npc b/app/SabrehavenServer/data/npc/dallheim.npc new file mode 100644 index 0000000..8977382 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dallheim.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dallheim.npc: Datenbank für den Dorfwächter Dallheim (Rookgaard) + +Name = "Dallheim" +Outfit = (131,76-38-76-95-0) +Home = [32093,32180,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not now." +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye.", Idle +"farewell" -> * +"how","are","you" -> "Fine." +"sell" -> "I sell nothing." +"advice",level<4 -> "Be careful out there and avoid the dungeons." +"advice",level>3 -> "Be careful out there." +"job" -> "I am the bridgeguard. I defend Rookgaard against the beasts from the wilderness and the dungeons!" +"name" -> "Dallheim." +"time" -> "No idea." +"help" -> "I have to stay here, sorry, but I can heal you if you are wounded." +"monster" -> "I will crush all monsters who dare to attack our base." +"dungeon" -> "Dungeons are dangerous, be prepared." +"wilderness" -> "There are wolves, bears, snakes, deers, and spiders. You can find some dungeon entrances there, too." +"sewer" -> "In the sewers there are some rats, fine targets for young heroes." +"god" -> "I am a follower of Banor." +"banor" -> "The great one! Read books to learn about him." +"king" -> "HAIL TO THE KING!" +"seymour" -> "Leave me alone with this whimp." +"willie" -> "A fine cook and farmer he is." +"amber" -> "I don't trust her." +"hyacinth" -> "Strange Fellow, hides somewhere in the mountains of the isle." +"weapon" -> "With my spikesword I slice even a cyclops in pieces." +"magic" -> "Not interested in such party tricks." +"tibia" -> "A nice place for a hero, but nothing for whelps." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +} diff --git a/app/SabrehavenServer/data/npc/dane.npc b/app/SabrehavenServer/data/npc/dane.npc new file mode 100644 index 0000000..d44489e --- /dev/null +++ b/app/SabrehavenServer/data/npc/dane.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dane.npc: Datenbank für die Wirtin Dane + +Name = "Dane" +Outfit = (136,79-58-86-96-0) +Home = [32308,31838,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the wave cellar, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back from time to time." + +"bye" -> "Please come back from time to time.", Idle +"farewell" -> * +"job" -> "I am the owner of this place of relaxation." +"saloon" -> * +"cellar" -> "It's pretty, isn't it?" +"name" -> "I am Dane." +"time" -> "It is exactly %T." +"news" -> "I heard nothing interesting lately." + +"offer" -> "I can offer you milk, water, and lemonade." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Sorry, we just sell drinks." +"alcohol" -> "Alcohol makes people too aggressive. We don't need such stuff in Carlin." +"wine" -> * +"beer" -> * + +"lemonade" -> Type=2875, Data=12, Amount=1, Price=5, "Do you want to buy a bottle of refreshing lemonade for %P gold?", Topic=1 +"milk" -> Type=2875, Data=9, Amount=1, Price=4, "Do you want to buy a bottle of our revitalizing milk for %P gold?", Topic=1 +"water" -> Type=2875, Data=1, Amount=1, Price=2, "Do you want to buy a bottle of crystal clear water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2875, Data=12, Amount=%1, Price=5*%1, "Do you want to buy %A bottles of refreshing lemonade for %P gold?", Topic=1 +%1,1<%1,"milk" -> Type=2875, Data=9, Amount=%1, Price=4*%1, "Do you want to buy %A bottles of our revitalizing milk for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2875, Data=1, Amount=%1, Price=2*%1, "Do you want to buy %A bottles of crystal clear water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/daniel.npc b/app/SabrehavenServer/data/npc/daniel.npc new file mode 100644 index 0000000..ebcf915 --- /dev/null +++ b/app/SabrehavenServer/data/npc/daniel.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# daniel.npc: Datenbank für Gouverneur Daniel Steelsoul + +Name = "Daniel Steelsoul" +Outfit = (73,0-0-0-0-0) +Home = [33191,31795,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings and Banor with you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,"salutations$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up! I am talking already!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "PRAISE TO BANOR!" + +"bye" -> "PRAISE TO BANOR!", Idle +"news" -> "Nothing new from the north." +"king" -> "LONG LIVE THE KING!" +"leader" -> "King Tibianus III is our wise and just leader." +"name" -> "I am Sir Daniel Steelsoul of the Sacred Order of Banor's Blood." +"job" -> "I am the governor of this isle, Edron, and grandmaster of the Knights of Banor's Blood." +"how","are","you"-> "I did not have much sleep lately, but I am fine." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects the Thaian realm. The order of the Knights of Banor's Blood supports them with all our skills." +"guard" -> * +"general" -> "Harkath Bloodblade declined the governorship because he's needed more in Thais." +"enemies" -> "Evil challenges the forces of good in any shape. Be it the claws of vicious monsters or the seductive dark secrets of rotten power." +"enemy" -> * +"banors","blood" -> "We believe that the blood of Banor runs through the veins of all humans. Therefore, we are responsible to live up to Banors standards and not to stain his legacy with sinful acts." +"castle" -> "The castle was built on elder foundations we found on this isle." + +"edron" -> "This isle is rumoured to have been the home of a powerful ancient race which became extinct before the corpsewars. It was up to King Tibianus III to reclaim it for humanity and to found this colony." +"colony" -> "With the Thaian army bound to other duties, our order was entrusted to secure the area. We defeated the evil minotaurs living right here and cleansed the isle of their unholy presence." +"minotaur" -> "The minotaurs, though evil, were worthy opponents. After the treason of the man who is now known as Kaine Kinslayer, we lack the manpower to crush their cyclopean allies, too." +"cyclop" -> "They live in an underground city, known as cyclopolis in the north of the isle. Constantly forging weapons for the servants of darkness." +"allies" -> * +"kaine" -> "He was my second in command. After learning about the forbidden ruins, he, the priestess Agaltha, and their followers freed the criminals we brought here as workers and headed to the north." +"ruins" -> "An ancient taboo forbids to enter the northern ruins." +"taboo" -> "We thought it was only superstition and no one bothered that Kaine and his friends went there to hunt servants of evil that might have hidden there. So we did not notice the dark cult they started." +"cult" -> "We know only little about them. Kaine and his fallen knights were joined by criminal scum and remaining forces of darkness that escaped us. They were joined by some ominous dark monks." +"monk" -> "We don't know if they came here or already hid in the ruins as we arrived. Maybe they seduced Kaine or Agaltha, maybe they were theirs for years." +"agaltha" -> "She was beautiful but seemed cold hearted. She spoke little to me, prefered the company of Kaine." +"eremo" -> "Eremo is a very wise man. I visit him sometimes on his little island near Edron. Just ask a fisherman for a passage." +"fisherman" -> "Pemaret is a fisherman on Cormaya." +"cormaya" -> "It is a peaceful isle next to Edron with a nice village. There, you should visit the wonderful garden." +"falk" -> "A promising young fellow." +"horn","plenty" -> "I hardly find the time to visit the tavern." +"mirabell" -> * +"willard" -> "When he was young, Willard served in the royal army." +"weapon" -> "Look for Willard, our local blacksmith." +"armor" -> * +"academy" -> "After the treason of Kaine, we observe these mages closely. If even a knight falls prey to the seduction of the forbidden ruins, no one can tell how easy some of these mystics might betray his people." +"amanda" -> "I think this nun might be a bit young for this position, but I won't question the decisions of the church of Banor's Blood." +"benjamin" -> "He and his men fought against Ferumbras somewhere in the north of this isle, long before there were even plans of a colony. Only old Ben returned alive from the battle, but his mind was broken." +"ferumbras" -> "He searched something in the north of the isle years ago. Probably he needed something from the forbidden ruins. He was chased and fought by the troops of General Benjamin." +"join" -> "You may join the order of Banor's blood if you prove your honor." +"honor" -> "Only those who live a life of bravery, honor, and piety may join our sacred order." +"piety" -> * +"bravery" -> * +"quest" -> "A life in bravery, honor, and piety should be every man's most important quest." +"mission" -> * +"god" -> "I worship Banor, the first champion of good!" +"banor" -> "His spirit and blood are within us. Honor this fact or be cast into hell." +"zathroth" -> "Do not mention the name of the cursed one!", Burning(10,1), EffectOpp(5), EffectMe(8) +"brog" -> "The rotten cyclopses whorship the raging giant of hell." +"monster" -> "We cleansed the south of any major enemy, but watch out while travelling the north." +"excalibug" -> "With this weapon in my hand, I would teach the servants of darkness the true meaning of the word fear." +"kazordoon" -> "Now and then a dwarf comes to this isle. Most behave secretive about their reason to come here. As far as I can tell they are looking for some dwarfish artifact which was lost in ancient times." +"dwarf" -> * +"carlin" -> "I belong to a sacred order and don't bother about mundane politics." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +Topic=2 -> "Then be more careful with your words!" + +# Level 6-49 +"task",QuestValue(17632)=0,level>5,level<50 -> "I may have several tasks for you citizen. Edron needs a help in the hunting of: trolls, goblins, rotworms or cyclopes.", Topic=3 +Topic=3,"troll" -> Amount=17633, "The trolls living west of our city have become quite a nuisance lately. Not that they are really dangerous to us, but still, we must show them that there's a line they shouldn't cross. ...", + "I want you to kill 100 of them. If you succeed, I'll provide you some pretty coins and experience. Are you willing to take on this task?", Topic=4 +Topic=3,"goblin" -> Amount=17634, "It's not only the trolls invading from the west coast. Goblins also have a lair there where they constantly prepare for their next attack. ...", + "If you could kill 150 goblins for us, that'd be a good start. Would you be willing to help us in this matter?", Topic=4 +Topic=3,"rotworm" -> Amount=17635, "Maybe you have noticed the numerous rotworms that burrowed under Edron. They're quite a pest. You look strong enough to be able to vanquish a few for us. Do you think you can kill 150 rotworms?", Topic=4 +Topic=3,"cyclop" -> Amount=17636, "We've successfully driven the minotaurs off this island, but the underground city of the cyclopes - Cyclopolis - is still standing. ...", + "We're always looking for adventurers who'd help us decimate the number of cyclopes. Will you assist the city of Edron by killing 150 of them?", Topic=4 + +"task",QuestValue(17633)=100 -> Amount=20*QuestValue(17633)*ExperienceStage(49)*40/100, Price=800, "Very nice, %N. That will push the trolls' forces back a little. Here is your reward!", SetQuestValue(QuestValue(17632),99999), SetQuestValue(17632,0), Experience(Amount), CreateMoney +"task",QuestValue(17634)=150 -> Amount=25*QuestValue(17634)*ExperienceStage(49)*40/100, Price=1000, "Congratulations, you've fought well against the goblin plague. Thank you! Here is your reward!", SetQuestValue(QuestValue(17632),99999), SetQuestValue(17632,0), Experience(Amount), CreateMoney +"task",QuestValue(17635)=150 -> Amount=55*QuestValue(17635)*ExperienceStage(49)*40/100, Price=1200, "Well done! Thanks to you the city is a bit safer. Here's your reward!", SetQuestValue(QuestValue(17632),99999), SetQuestValue(17632,0), Experience(Amount), CreateMoney +"task",QuestValue(17636)=150 -> Amount=150*QuestValue(17636)*ExperienceStage(49)*40/100, Price=4000, "Very good job, %N. You've been a great help. Here's your reward!", SetQuestValue(QuestValue(17632),99999), SetQuestValue(17632,0), Experience(Amount), CreateMoney + +# Speaks +"task",QuestValue(17632)>0 -> "Your current task is in progress. Follow the status of your task in the quest log. If you wish to cancel your in-progress task then don't be afraid and feel free to cancel." + +Topic=4,"yes",QuestValue(Amount)=99999 -> "Oh, I am sorry but you have already finished that task." +Topic=4,"yes" -> "I'm pleased with your eagerness. Good luck!", SetQuestValue(17632,Amount), SetQuestValue(Amount,0), SetQuestValue(17607,1) +Topic=4 -> "Maybe next time." + +"cancel",QuestValue(17632)>0 -> "Are you sure you want to cancel your current task?", Topic=6 +Topic=6,"yes" -> "Alright! Feel free to do other tasks for us.", SetQuestValue(QuestValue(17632),99998), SetQuestValue(17632,0) +Topic=6 -> "Good! Speak to me again when you are done hunting." + +"task" -> "I don't have any tasks for your level." + +} diff --git a/app/SabrehavenServer/data/npc/danlon.npc b/app/SabrehavenServer/data/npc/danlon.npc new file mode 100644 index 0000000..71b67ad --- /dev/null +++ b/app/SabrehavenServer/data/npc/danlon.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Danlon" +Outfit = (146,114-42-40-116-0) +Home = [32187,32949,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/dario.npc b/app/SabrehavenServer/data/npc/dario.npc new file mode 100644 index 0000000..996fc3f --- /dev/null +++ b/app/SabrehavenServer/data/npc/dario.npc @@ -0,0 +1,138 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dario.npc: Datenbank fuer den elfen dario in der arena (ankrahmun) + +Name = "Dario" +Outfit = (144,3-58-41-115-0) +Home = [33156,32810,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "In a few moment I will have my attention %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * + +"job" -> "I am the master archer of the arena. I train distance fighters and sell them equipment." +"name" -> "I am Dario of Ab'Dendriel." +"time" -> "Time is unimportant to me." +"temple" -> "The temple is somewhere south at the coast." +"arkhothep" -> "The pharaoh seems to be mighty beyond imagination." +"ashmunrah" -> "There was some fighting long ago. The old pharaoh lost his power to his son Arkhothep." +"scarab" -> "Scarabs are dangerous. They are quick, resistant to poison and theis shells are hard as steel." +"tibia" -> "I travel a lot to see everything. For now I settle here for some time." +"carlin" -> "I was there some time ago. It was lovely and reminded me of my home Ab'Dendriel." +"thais" -> "Thais is too crowded for my taste." +"edron" -> "I think Edron is quite typical for a human settlement." +"venore" -> "I did not like the greedy attitude of the people there." +"kazordoon" -> "The small people are too hectic and greedy. They don't understand the harmony of nature." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Sometimes I miss my brethren and sisters. But for now I want to see the world and travel around." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is hard and challenging. I like challenges." +"darashia" -> "The city seemed a bit dull and peacefull to me, so I left for Ankrahmun." +"daraman" -> "You should ask about him in Darashia. People there talked a lot about him." +"ankrahmun" -> "Ankrahmun is unlike any other city I've seen. Sometimes it gives me shivers ... on the other hand it makes me stay on guard and feel alive, despite the undeath cult." + +"ascension" -> "I don't care for this human concepts." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * +"undead" -> "I don't understand this cult yet. Just ask around and people will tell you." +"undeath" -> * +"arena" -> "People who fight here do it on their own choice. So I don't care." +"palace" -> "Under the palace are crypts, full of minor undead and creatures that have failed the pharaoh. He allows everyone to slay them as they see it fit." + +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Asha Thrazi.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=4 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=4 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=4 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=4 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=4 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=4 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=4 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=4 + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + + +} diff --git a/app/SabrehavenServer/data/npc/demongrd.npc b/app/SabrehavenServer/data/npc/demongrd.npc new file mode 100644 index 0000000..28bdd33 --- /dev/null +++ b/app/SabrehavenServer/data/npc/demongrd.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# demongrd.npc: Datenbank für den Demonguard + +Name = "Demonguard" +Outfit = (131,113-113-94-113-0) +Home = [32854,32318,10] +Radius = 0 + +Behaviour = { +-> "Die, intruder!", Burning(25,8), EffectOpp(16), EffectMe(14), Idle +} diff --git a/app/SabrehavenServer/data/npc/demonskeleton.npc b/app/SabrehavenServer/data/npc/demonskeleton.npc new file mode 100644 index 0000000..37b0c4b --- /dev/null +++ b/app/SabrehavenServer/data/npc/demonskeleton.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# demonskeleton.npc: Datenbank fuer ein NPC-Daemonenskelett + +Name = "Demon Skeleton" +Outfit = (37,0-0-0-0-0) +Home = [32666,31676,15] +Radius = 0 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/app/SabrehavenServer/data/npc/dermot.npc b/app/SabrehavenServer/data/npc/dermot.npc new file mode 100644 index 0000000..1d26868 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dermot.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dermot.npc: Datenbank für den Ortsvorsteher Dermot auf der Insel Fibula + +Name = "Dermot" +Outfit = (129,76-49-19-95-0) +Home = [32165,32437,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, traveller %N. How can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I cannot talk to two persons at the same time. You'll have to wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you again." + +"bye" -> "See you again.", Idle +"farewell" -> * +"job" -> "I am the magistrate of this isle." +"equipment" -> "I am not selling equipment. You'll have to visit Timur." +"timur" -> "He is the salesman in this village. " +"name" -> "I am Dermot, the magistrate of this isle." +"time" -> "Time is not important on Fibula." +"dermot" -> "I am the magistrate of this isle." +"magistrate" -> "Thats me." +"fibula" -> "You are at Fibula. This isle is not very dangerous. Just the wolves bother outside the village." +"wolf" -> "There are a lot of wolves outside the townwall. They disturb our farmers." +"farmer" -> "The inhabitants of Fibula live on fishing, farming, and hunting." + +QuestValue(231)=1,"present" -> Type=3218, Amount=1,"You have a present for me?? Really?",Topic=2 +"present" -> "I don't understand what you are talking about." +Topic=2,"yes",Count(Type)>=Amount -> "Thank you very much!",Delete(Type),SetQuestValue(231,2) +Topic=2,"yes" -> "What? There is no present, at least none for me! Stop this foolish jokes!",Idle +Topic=2 -> "Hmm, maybe next time." + +"dungeon" -> "Oh, my god. In the dungeon of Fibula are a lot of monsters. That's why we have sealed it with a solid door." +"sewer" -> * +"monster" -> * +"entrance" -> "The entrance is near here." +"key" -> Type=2968, Data=3940, Amount=1, Price=2000, "Do you want to buy the dungeon key for %P gold?", Topic=1 +"door" -> * + +Topic=1,"yes",CountMoney>=Price -> "Now you own the key to the dungeon.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You've not enough money to buy the key." +Topic=1 -> "Hmm, maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/digger.npc b/app/SabrehavenServer/data/npc/digger.npc new file mode 100644 index 0000000..08216fb --- /dev/null +++ b/app/SabrehavenServer/data/npc/digger.npc @@ -0,0 +1,69 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# digger.npc: Datenbank für den Magieladen-Verkäufer Digger + +Name = "Digger" +Outfit = (9,0-0-0-0-0) +Home = [32970,32087,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, %N is that you? You look inconveniently healthy." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, your time will finally come.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"name" -> "They call me Digger, that fine with me." +"frans" -> "I think the FRANS is bugged." +"digger" -> "So what?" +"job" -> "I am selling some potions." +"sorcerer" -> "The way of the magicwielder is the only way to true power." +"druid" -> * +"magic" -> "This is the magic market. Just have a look around." +"market" -> * +"vladruc" -> "Better don't cross the master!" +"urghain" -> * +"ferumbras" -> "An upstart of minor skills and great ambitions." +"excalibug" -> "Just a knights' legend." + +"offer" -> "You may be interested in my life and mana fluids." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"potion" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2 -> "Don't overestimate my patience." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3 -> "Don't overestimate my patience." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/dixi.npc b/app/SabrehavenServer/data/npc/dixi.npc new file mode 100644 index 0000000..3d5a4a4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dixi.npc @@ -0,0 +1,161 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dixi.npc: Datenbank für Obi's Angestellte Dixi + +Name = "Dixi" +Outfit = (136,96-99-76-115-0) +Home = [32105,32207,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Hello, Sir. How may I help you, %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Hello, Mam. How may I help you, %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N. I'll be with you in a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye", male -> "Good bye, Sir.", Idle +"farewell", male -> * +"bye", female -> "Good bye, Mam.", Idle +"farewell", male -> * +"how","are","you" -> "I am fine, thank you." +"sell" -> "We're selling many things. Please have a look at the blackboards downstairs to see a list of our inventory." +"job" -> "I'm helping my grandfather Obi with this shop. Do you want to buy or sell anything?" +"name" -> "I'm Dixi." +"time" -> "It is %T." +"help" -> "If you need something, please let me know." +"stuff" -> "We sell equipment of all kinds. Please let me know if you need something." + +"wares" -> "We sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "We sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "We sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "We sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "We sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "We sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? We sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "I am sorry, an agent of Al Dee bought all our picks. Now he has a monopoly on them." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"sell","club" -> "I'm sorry, we don't buy this." +"sell","dagger" -> Type=3267, Amount=1, Price=2, "I can give you %P gold for this, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "I can give you %P gold for this, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/djema.npc b/app/SabrehavenServer/data/npc/djema.npc new file mode 100644 index 0000000..cd53811 --- /dev/null +++ b/app/SabrehavenServer/data/npc/djema.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# djema.npc: Datenbank für die Bibliothekarin Djema + +Name = "Djema" +Outfit = (136,77-9-86-131-0) +Home = [33101,32520,3] +Radius = 2 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Wow! A human? Here? Hey? Where do you come from, %N? Oh, I'm so excited!" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Another human?! Please don't go away! Stay! Please wait a minute, %N!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Please don't go!" + +"bye" -> "Aww - you really have to leave so soon? You must come and visit me again. Please - promise me!", Idle +"farewell" -> * +"name" -> "My name is Djema. Daddy says it means 'Moonflower' in the old language." +"djem" -> "That is my name. I do not like it much, though. Everybody around here calls me Djem." +"job" -> "I am the librarian in this place. I don't like the work too much because we do not really have that many books, and most of them are written by people who have died thousands of years ago. ...", + "If dad wanted me to have a job to keep me entertained that was a real non-starter." +"librarian" -> "Yes. I administrate the library. You know - registering new books, sorting them in alphabetical order etc. ...", + "To be honest I am not very good in this. Thank goodness daddy gave that magical blackboard to me. It is quite useful." +"gabel" -> "Gabel is my father. He runs this place. Of course, he is not my real daddy. But he adopted me, you know. Or rather, all the Marid adopted me." +"father" -> * +"dad" -> * +"parents" -> "I can't remember them. I... They have both died a long time. At least that is what I have been told. Listen, can we talk about something else?" +"marid" -> "The djinn you have met call themselves the Marid. They are generally very nice. Nice, but boring." +"djinn" -> "The djinn are a curious race. They are nice, but they are always so serious. Oh, don't get me wrong, there is not a single djinn around here I do not like, but, you know, they are not much fun. ...", + "I guess that is because they are all such devout followers of Daraman, but perhaps it is just because of all the bad things that have happened." +"daraman" -> "Daraman was a human, but he must have been something very special - he was a holy man. To this day daddy and all the other djinn around here look up to Daraman as a true prophet." + +"king" -> "Officially there is no king of the djinn. Daddy used to hold the title, but he has chosen to put if off. Of course, he is still the undisputed leader of the Marid. He simply dislikes the title." +"efreet" -> "Apparently the greenskins are different from the Marid who have raised me. I don't know. Perhaps the Efreet would be more fun than the djinn around here, but then daddy says they are really evil." +"malor" -> "Malor is the leader of the Efreet. I have never seen him, but they say he is really nasty. Daddy always gets upset when this name is pronounced." +"mal'ouquah" -> "Oh, that place. They say it is pure evil. But I don't think it looks that evil. I have seen the dark fortress once, you know?!" +"dark", "fortress" -> "One night I went there. I wanted to see it for myself. Don't tell daddy, though. He would freak out if he heard I was there." +"ashta'daramai" -> "That's what this place is called. Sure, it is beautiful, but it is as also boring and sometimes downright depressing. Sometimes I feel like I am bound to this place by golden chains." +"human" -> "I have lived here for as long as I can remember, but I know I don't belong here. I belong to them! I am a human! One day I will leave this place, and I will never come back." +"zathroth" -> "Yes, I have heard his name before. Let's see... Yes, I have read his name in a book! It was a book about gods and creation. Pretty weird stuff." +"tibia" -> "Daddy has often told me about how huge and mysterious this world is. How much I would like to see it all. But he won't let me go. ...", + "I have read books about the northern continent inhabited by thousands and thousands of people. Ordinary humans just like you and me! Imagine that!" +"darashia" -> "Darashia is a beautiful city to the north. I have been there! One day Daddy disguised himself, and he took me there. It was awesome. ...", + "There was so much life and colour and excitement... I suppose he did it because he knew how much I yearned to go there. Bless him - he only wanted to help. But after that I felt worse than ever." +"edron" -> "I have read all about the northern cities. It is almost as if I had been there myself." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "If there is one city I don't want to see it is Ankrahmun. I have heard all kinds of stories about the pharaoh and his cult of weirdos!" +"scarab" -> "Ah, those nasty critters. They give me the creeps!" + +"pharaoh" -> "Apparently he is an undead! Yuk - how disgusting!" +"palace" -> "I do not care just how beautiful the pharaoh's palace is. I will never go there. The minute I would see some undead pile of flesh I would dash for the door screaming." +"ascension" -> "I have heard that term before. Has to do with the pharaoh's cult, I think, but I do not know for sure, and I'm not particularly eager to learn more about it." +"akh'rah" -> "Hm. No - doesn't ring a bell." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "I don't know whether I should hate or love these mountains. I mean, they are so beautiful. If only you saw those peaks in the evening, when the sun is setting. It is like a thousand fires that set the horizon aglow. ...", + "But... ah, I don't know. I think I would give it all away if I could live somewhere among my own kind." +"kha'labal" -> "That is the huge desert to the east. You can see it from here if you look in the direction of the rising sun. It seems huge... endless... It makes my heart sink whenever I watch it." +"war" -> "Daddy and all the other djinn around here are so restive lately. I know they try not to show it, but I can sense that they are tense and perhaps even a bit afraid. ...", + "To be honest I am sick of being patronised. I am a grown-up woman now, and I don't need anybody else's protection anymore!" +"melchior" -> "Hm. I think I have heard that name before. A human, wasn't it? I think he used to drop quite often when I was much younger, but I have no clear memory of him. ...", + "I think he has not visited Ashta'Daramai for a long time." +"alesar" -> "I have never known this djinn, but apparently there is some sad story behind this. Daddy is very sad that Alesar left us. Of course, he tries not to show, but as usual he does a bad job about it." +"lamp" -> "When I was still a kid I could not understand how it could be that I was not able to sleep in a lamp. ...", + "That was when I wanted nothing more than to be like the people around me. I may see things a bit more clearly now, but the feeling of yearning remains." +"fa'hradin" -> "Uncle Fad is a weird guy. He is incredibly intelligent, but he is also totally inept in worldly matters. Sometimes I feel he is not quite at home in this world." +"fa'hradin","lamp" -> "I have heard many a story about this artifact. It was used to trap that Malor guy. Clever idea of good old uncle Fa'hradin." +"book" -> "The books around here are not exactly what I would call a riveting read. Most of them are technical documents written by uncle Fad at some point or other. Now and then he turns up and brings new files. Not that anybody would ever read them. ...", + "It is my job to make sure they are filed and registered." +#"blackboard" -> "Yes! All books we have are listed in it! The whole library! And the best thing is that whenever a new book is added or one is removed the list is updated by magic. Isn't that great! Take a look!" +#"magical","blackboard" -> * +#"spy" -> "Yes! There was a spy! I have seen him! He was over there, right next to the door. When I came in he just slipped out. It was an Efreet! His green skin gave him away!" +#"fr 0457", QuestValue (###)=### -> "fr 0457? Missing? Let me see? Hey, you are right. It is not on the list. Could it be?","Daraman have mercy! You're right! The file is gone! The spy must have stolen it. Oh dear. What am I to do now?","Quick! Go and tell uncle Fa'hradin. He will know what to do!", SetQuestValue (###)=### +} diff --git a/app/SabrehavenServer/data/npc/don.npc b/app/SabrehavenServer/data/npc/don.npc new file mode 100644 index 0000000..b17a1b5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/don.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# don.npc: Datenbank fuer den Bauern Donald McRonald + +Name = "Donald McRonald" +Outfit = (128,41-94-79-76-0) +Home = [32391,32229,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Hello, Druid %N!" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",! -> "Hmmm, well, hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hmm, I'm busy %N." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "?" + +"bye",Druid -> "May Crunor bless you, Druid %N!", Idle +"farewell",Druid-> * +"job",Druid -> "My wife and I run this farm as good as we can." +"wife",Druid -> "Sherry is my beloved wife." +"donald",Druid -> "I was named Donald, like my grandfather." +"farm",Druid -> "It's a hard but rewarding task to run this farm." +"name",Druid -> "My name is Donald McRonald, noble druid." +"time",Druid -> "Unfortunately I can't help you with that, noble druid." + +"bye" -> "Yes, bye!", Idle +"farewell"-> "Yes, farewell!", Idle +"job" -> "I run a farm, what else?!" +"wife" -> "Sherry is my wife." +"donald" -> "I am Donald." +"farm" -> "It is my farm, yes." +"name" -> "Donald McRonald." +"time" -> "Who cares?" +"weather" -> "Weather is good enough to work on the fields." +"crops" -> "It is hard to grow but worth the effort." +"field" -> "My fields are enchanted by the druids and the wheat grows very quickly." + +"city" -> "The city is to the north." +"crops" -> "I take care of our crops" +"mill" -> "I somtimes have to bring the wheat there." +"spooked" -> "I dont know." +"king" -> "King Tibianus is our king." +"frodo" -> "Frodo? He is a friend of mine." +"oswald" -> "He ignores us and we ignore him." +"bloodblade" -> "A general in the army." +"muriel", sorcerer -> "I dont trust sorcerers like you." +"muriel" -> "I dont trust sorcerers." +"elane" -> "Too noble to care about us." +"gregor" -> "Knights always feel superior to us farmers." +"gregor", knight -> "Knights like you always feel superior to us farmers." +"marvik" -> "Druids are a great help for us, they know much about nature." +"marvik", druid -> "Druids like you are a great help for us, they know much about nature." +"gorn" -> "Hardly know him." +"sam" -> "A blacksmith, eh?" +"quentin" -> "A generous person." +"lynda" -> "She has a good soul." +"spider" -> Type=3988, Amount=1, Price=2, "I will give you %P gold for every spider you bring me. But not a rotten spider that was already dead for some time. Do you have any with you?", Topic=2 + +"buy" -> "I can offer you wheat, cheese, carrots, and corncobs." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have wheat, cheese, carrots, and corn to sell. If you want to sell bread, talk to my wife, Sherry." +"bread" -> "If you want to sell bread, talk to my wife, Sherry." + +"wheat" -> Type=3605, Amount=1, Price=1, "Do you want to buy wheat for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=1 +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=1 + + +%1,1<%1,"wheat" -> Type=3605, Amount=%1, Price=1*%1, "Do you want to buy %A wheat for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=1 + + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You have no spider that died recently." +Topic=2 -> "Hmpf." +} diff --git a/app/SabrehavenServer/data/npc/doug.npc b/app/SabrehavenServer/data/npc/doug.npc new file mode 100644 index 0000000..c8a035b --- /dev/null +++ b/app/SabrehavenServer/data/npc/doug.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Doug" +Outfit = (128,116-37-116-116-0) +Home = [32201,32756,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/dove.npc b/app/SabrehavenServer/data/npc/dove.npc new file mode 100644 index 0000000..254a0ea --- /dev/null +++ b/app/SabrehavenServer/data/npc/dove.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dove.npc: Datenbank für die Postbeamtin Dove + +Name = "Dove" +Outfit = (136,59-86-106-115-0) +Home = [32919,32075,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, noble %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back soon, noble %N." + +"bye" -> "Come back soon, noble %N.", Idle +"farewell" -> * + +"kevin" -> "Mr. Postner is one of the most honorable men I know." +"postner" -> * +"postmasters","guild" -> "As long as everyone lives up to our standarts our guild will be fine." +"join" -> "We are always looking able recruits. Just speak to Mr.Postner in our headquarter." +"headquarter" -> "Its easy to be found. Its on the road from Thais to Kazordoon and Ab'dendriel." + + +"measurements",QuestValue(234)>0,QuestValue(238)<1 -> "Oh no! I knew that day would come! I am slightly above the allowed weight and if you can't supply me with some grapes to slim down I will get fired. Do you happen to have some grapes with you?",Type=3592, Amount=1,Topic=5 + +"grapes",QuestValue(234)>0,QuestValue(238)<1 -> "Do you happen to have some grapes with you?",Type=3592, Amount=1,Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Oh thank you! Thank you so much! So listen ... ", Delete(Type),SetQuestValue(234,QuestValue(234)+1),SetQuestValue(238,1) +Topic=5,"yes" -> "Don't tease me! You don't have any." +Topic=5 -> "Oh, no! I might loose my job." + + +"job" -> "I am responsible for this post office. If you have questions about the mail system or the depots, just ask me." +"name" -> "My name is Dove." +"dove" -> "Yes, like the bird. " +"time" -> "Now it's %T." +#"mail" -> "The Tibian mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +#"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all Tibian citizens." +"king" -> "Even the king can be reached by the mailsystem." +"tibianus" -> * +"army" -> "The soldiers get a lot of letters and parcels from Thais each week." +"ferumbras" -> "Try to contact him by mail." +"general" -> * +"sam" -> "Ham? No thanks, I ate fish already." +"excalibug" -> "If i find it in an undeliverable parcel, I will contact you." +"news" -> "Well, there are rumours about the swampelves and the amazons, as usual." +"thais" -> "All cities are covered by our mail system." +"carlin" -> * +"swampelves" -> "They live somewhere in the swamp and usually stay out of our city. Only now and then some of them dare to interfere with us." +"amazon" -> "These women are renegades from Carlin, and one of their hidden villages or hideouts might be in the swamp." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/duncan.npc b/app/SabrehavenServer/data/npc/duncan.npc new file mode 100644 index 0000000..f6ad94d --- /dev/null +++ b/app/SabrehavenServer/data/npc/duncan.npc @@ -0,0 +1,96 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Duncan" +Outfit = (151,38-23-0-116-1) +Home = [32330,32605,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi %N, come closer. Have a look at my wares and such." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye" + +"bye" -> "Good bye.", Idle +"farewell" -> * +"do","you","sell" -> "Well erm, currently we are a little short on weapons and the like, but I still have pirate tapestries for sale." +"wares" -> * +"offer" -> * + +"pirate tapestry" -> Type=5615, Amount=1, Price=40, "Do you want to buy a pirate tapestry for %P gold?", Topic=1 +%1,1<%1,"pirate tapestry" -> Type=5615, Amount=%1, Price=40*%1, "Do you want to buy %A pirate tapestries for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you go. Those would fit nicely into your house, showing that you're a fan and supporter and all that.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Maybe next time." + +# Meriana_Quest +"mission",QuestValue(17520)=1,QuestValue(17523)=0 -> "I need a new quality atlas for our captains. Only one of the best will do it. I heard the explorers society sells the best, but only to members of a certain rank. You will have to get this rank or ask a high ranking member to buy it for you.", SetQuestValue(17523,1) +"task",QuestValue(17520)=1,QuestValue(17523)=0 -> * + +"mission",QuestValue(17523)=1 -> Type=6108, Amount=1, "Did you get an atlas of the explorers society as I requested?", Topic=2 +"task",QuestValue(17523)=1 -> * +"atlas",QuestValue(17523)=1 -> * +Topic=2,"yes",Count(Type)>=Amount -> "Indeed, what a fine work... the book I mean. Your work was acceptable all in all.", Delete(Type), SetQuestValue(17523,2) +Topic=2,"yes" -> "Sorry, you do not have it." +Topic=2 -> "Maybe another time." + +"mission",QuestValue(17525)=0,QuestValue(17520)>4 -> "You did some impressive things. I think people here start considering you as one of us. But these are dire times and everyone of us is expected to give his best and even exceed himself. Do you think you can handle that?", Topic=3 +"task",QuestValue(17525)=0,QuestValue(17520)>4 -> * +Topic=3,"yes" -> "I am glad to hear this. Please listen. The pirates on Nargor are breeding tortoises. They think eating tortoises makes a hard man even harder. ...", + "However I am quite fond of tortoises and can't stand the thought of them being eaten. So I convinced Captain Striker that I can train them to help us. As a substitute for rafts and such ...", + "All I need is one tortoise egg from Nargor. This is the opportunity to save a tortoise from a gruesome fate! ...", + "I will ask Sebastian to bring you there. Travel to Nargor, find their tortoise eggs and bring me at least one of them.", SetQuestValue(17525,1) +Topic=3 -> "Maybe another time." + +"mission",QuestValue(17525)=1 -> Type=6125, Amount=1, "Did you rescue one of those poor soon-to-be baby tortoises from Nargor?", Topic=4 +"task",QuestValue(17525)=1 -> * +"egg",QuestValue(17525)=1 -> * +Topic=4,"yes",Count(Type)>=Amount -> "A real tortoise egg ... I guess you are more accustomed to rescue some noblewoman in distress but you did something good today.", Delete(Type), SetQuestValue(17525,2) +Topic=4,"yes" -> "Sorry, you do not have it." +Topic=4 -> "Maybe another time." + +# Pirate_Outfit_Quest +"addon",QuestValue(17520)<12 -> "You're talking about my sabre? Well, first you have to earn our trust before you are granted to wear such a sabre." +"outfit",QuestValue(17520)<12 -> * +"addon",QuestValue(17567)<4,QuestValue(17520)=12 -> "You're talking about my sabre? Well, even though you earned our trust, you'd have to fulfil a task first before you are granted to wear such a sabre." +"outfit",QuestValue(17567)<4,QuestValue(17520)=12 -> * +"addon",QuestValue(17567)>3 -> "Firebird is the finest pirate sabre isin't it?" +"outfit",QuestValue(17567)>3 -> * + +"task",QuestValue(17567)=0,QuestValue(17520)=12 -> "Are you up to the task which I'm going to give you and willing to prove you're worthy of wearing such a sabre?", Topic=5 +"mission",QuestValue(17567)=0,QuestValue(17520)=12 -> * +Topic=5,"yes" -> "Listen, the task is not that hard. Simply prove that you are with us and not with the pirates from Nargor by bringing me some of their belongings. ...", + "Bring me 100 of their eye patches, 100 of their peg legs and 100 of their hooks, in that order. ...", + "Have you understood everything I told you and are willing to handle this task?", Topic=6 +Topic=5 -> "Maybe another time." +Topic=6,"yes" -> "Good! Come back to me once you have gathered 100 eye patches.", SetQuestValue(17567,1), SetQuestValue(17594,1) +Topic=6 -> "Maybe another time." + +"patch",QuestValue(17567)=1 -> Type=6098, Amount=100, "Have you gathered 100 eye patches?", Topic=7 +"mission",QuestValue(17567)=1 -> * +"task",QuestValue(17567)=1 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Good job. Alright, now bring me 100 peg legs.", Delete(Type), SetQuestValue(17567,2) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe another time." + +"leg",QuestValue(17567)=2 -> Type=6126, Amount=100, "Have you gathered 100 peg legs?", Topic=8 +"mission",QuestValue(17567)=2 -> * +"task",QuestValue(17567)=2 -> * +Topic=8,"yes",Count(Type)>=Amount -> "Nice. Lastly, bring me 100 pirate hooks. That should be enough to earn your sabre.", Delete(Type), SetQuestValue(17567,3) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe another time." + +"hook",QuestValue(17567)=3 -> Type=6097, Amount=100, "Have you gathered 100 hooks?", Topic=9 +"mission",QuestValue(17567)=3 -> * +"task",QuestValue(17567)=3 -> * +Topic=9,"yes",Count(Type)>=Amount -> "I see, I see. Well done. Go to Morgan and tell him this codeword: 'firebird'. He'll know what to do.", Delete(Type), SetQuestValue(17567,4) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe another time." + +"mission" -> "Sorry, I don't have any missions for you." +"task" -> * +} diff --git a/app/SabrehavenServer/data/npc/duria.npc b/app/SabrehavenServer/data/npc/duria.npc new file mode 100644 index 0000000..6425808 --- /dev/null +++ b/app/SabrehavenServer/data/npc/duria.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# duria.npc: Datenbank fuer die Ritterin Duria + +Name = "Duria" +Outfit = (70,0-0-0-0-0) +Home = [32617,31938,8] +Radius = 3 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Hiho, fellow knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,Knight,"hiho$",! -> * +ADDRESS,"hello$",! -> "Hiho, visitor %N. Whatdoyouwant?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Waitaminute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Becarefulloutthere, jawoll." + +"bye" -> "Goodbye.",Idle +"farewell" -> * +"job" -> "Iam the Highknight of the dwarfs." +"name" -> "I am Duria Steelbender, daughter of Fire, of the Dragoneaters." +"time" -> "Dunno." +"hero" -> "Heroes are rare in this days, jawoll." +"tibia" -> "Bah, to much plantsandstuff, to few tunnels ifyoudaskme." +"thais" -> "Was there once. Can't handle the crime overthere." +"knight" -> "Knights are proud of being dwarfs, jawoll." +"vocation" -> "Vocation, vocation, wouldratherlike a vacation." +"spellbook" -> "Sellingno spellbooks here. Do I look like a sorc?" +Knight,"spell" -> "Can teach ye healing spells and support spells. What kind of spell you like? Or for which level you want a spell?", Topic=2 +"spell" -> "Sorry, selling spells only to knights, jawoll." + +Knight,"instant","spell" -> "Can teach ye healing spells and support spells. What kind of spell you like?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level you want a spell?", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Goodbye.",Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "Youknowthatspell." +Topic=3,"yes",Level Amount=SpellLevel(String), "Nah, you havetobe level %A to learn this one." +Topic=3,"yes",CountMoney "Hey! Whereisyourgold?" +Topic=3,"yes" -> "Hereyouare. It's now in your spellbook, jawoll.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe nexttime." +} diff --git a/app/SabrehavenServer/data/npc/dustrunner.npc b/app/SabrehavenServer/data/npc/dustrunner.npc new file mode 100644 index 0000000..a8a8ca4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/dustrunner.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dustrunner.npc: Datenbank fuer den Rennhund Dustrunner + +Name = "Dustrunner" +Outfit = (32,0-0-0-0-0) +Home = [32914,32076,6] +Radius = 14 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/app/SabrehavenServer/data/npc/ebenizer.npc b/app/SabrehavenServer/data/npc/ebenizer.npc new file mode 100644 index 0000000..f9c9986 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ebenizer.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ebenizer.npc: Datenbank für den Bankier Ebenizer + +Name = "Ebenizer" +Outfit = (128,59-95-87-76-0) +Home = [33175,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Yes? What may I do for you, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "It's not your turn %N. Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Have a nice day." + +"bye" -> "Have a nice day.", Idle +"farewell" -> * +"name" -> "My name? Ebenizer!" +"job" -> "I am running this Bank" +"time" -> "It is %T, precisely." +"king" -> "Hail to the king!" +"tibianus" -> * +"army" -> "Soldiers have not that much money that I would care about." +"ferumbras" -> "A true threat to wealth and trade." +"excalibug" -> "This weapon, if real, might be worth a lot." +"thais" -> "We are in constant contact with the city of Thais." +"tibia" -> "There are countless ways of profit in this world." +"carlin" -> "It's underdeveloped and economically insignificant." +"edron" -> "The riches of our isle are its mineral resources." +"news" -> "I only care about financial news." +"rumors" -> * + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/edala.npc b/app/SabrehavenServer/data/npc/edala.npc new file mode 100644 index 0000000..1fa30aa --- /dev/null +++ b/app/SabrehavenServer/data/npc/edala.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edala.npc: Datenbank für die heilerin edala (nahe Elfenstadt) + +Name = "Edala" +Outfit = (63,0-0-0-0-0) +Home = [32698,31718,2] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, please wait a moment %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I am a mystic of the suns. I provide protective blessings for those in need." +"name" -> "My name is Edala, pilgrim." + +"mystic" -> "We mystics are philosophers and healers." + + +"cenath" -> "I don't consider me a member of any caste, and I don't want to talk about this matter." +"kuridai" -> * +"deraisim" -> * +"abdaisim" -> * +"teshial" -> * + +"crunor" -> "Crunor is great in his beauty." +"priyla" -> "The daughter of the stars is my patron." + +"excalibug" -> "It is true that this weapon brings great power. But you should not look for power. It is wisdom you really need." +"news" -> "News? I don't care about news." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask my about the blessing you are interested in." +"spiritual" -> " You may receive the spiritual shielding in the whiteflowertemple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." + +"fire" -> "Do you wish to receive the blessing of the two suns? It will cost you 10.000 gold, pilgrim.",Price=10000, Topic=5 +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(103) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "Kneel down and receive the warmth of sunfire, pilgrim.", DeleteMoney, EffectOpp(13), SetQuestValue(103,3), Bless(3) +Topic=5,! -> "All right. As you wish." + + +} diff --git a/app/SabrehavenServer/data/npc/eddy.npc b/app/SabrehavenServer/data/npc/eddy.npc new file mode 100644 index 0000000..47a3ab3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/eddy.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eddy.npc: Möbelverkäufer Eddy auf Fibula + +Name = "Eddy" +Outfit = (128,60-64-0-95-0) +Home = [32168,32431,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Eddy. I sell furniture." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/edoch.npc b/app/SabrehavenServer/data/npc/edoch.npc new file mode 100644 index 0000000..4b93664 --- /dev/null +++ b/app/SabrehavenServer/data/npc/edoch.npc @@ -0,0 +1,48 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edoch.npc: Datenbank für den Bogenmacher Edoch + +Name = "Edoch" +Outfit = (129,95-0-40-116-0) +Home = [33232,32430,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings, traveller." + +"bye" -> "Daraman's blessings, traveller.", Idle +"job" -> "I am nothing but a humble fletcher. I am selling bows, crossbows, and ammunition. Do you need any of these?" +"fletcher" -> * +"name" -> "I am Edoch Ibn Ibrach." +"time" -> "You surely can buy a watch somewhere on this bazaar." +"tibia" -> "The world is vast and dangerous. Better prepare yourself with a bow before you travel out there." +"thais" -> "I was there once to learn about their ways. Needless to say I was horrified and returned to Darashia as soon as possible." +"do","you","sell" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/edowir.npc b/app/SabrehavenServer/data/npc/edowir.npc new file mode 100644 index 0000000..f388d14 --- /dev/null +++ b/app/SabrehavenServer/data/npc/edowir.npc @@ -0,0 +1,165 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edowir.npc: Datenbank fuer den Weisen Edowir + +Name = "Edowir" +Outfit = (130,0-58-96-95-0) +Home = [32343,32363,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, hello %N! How nice of you to visit an old man like me." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Be patient %N. Learn to listen, listen to learn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back whenever you're in need of wisdom." + +"bye" -> "Come back whenever you're in need of wisdom.", Idle +"farewell" -> * +"how","are","you" -> "I am fine, thank you." +"sell" -> "I sell nothing, but I share my wisdom now and then." +"job" -> "I gather wisdom and knowledge. I am also an astrologer." +"name" -> "I am Edowir, but don't worry about remembering my name. I will forget your name as well." +"edowir" -> "That's me, but don't worry about remembering my name. I will forget your name as well." +"time" -> "Time is a pillar and our lives wind around it like vine." +"help" -> "I would like to help you. What is your problem?" +"monster" -> "Man or monster, the difference is often just a matter of hides and scales." +"dungeon" -> "Dungeons are a place of danger, not of joy. Keep that in mind on your travels." +"sewer" -> "Sewers are sometimes the safer ways to get where you want to." +"god" -> "Learn about the gods to learn from the gods." +"king" -> "Kings are children adorned with crowns." +"bozo" -> "Who laughs last, thinks slowest." +"joke" -> * +"jester" -> * +"rumour" -> "Rumours are an unsafe path to follow." +"gossip" -> * +"fuck" -> "If that's all you can think about...", Idle +"weapon" -> "Those who live by the sword get shot by those who don't." +"magic" -> "I believe that true love is stronger than all magic, don't you agree?" +"old" -> "Growing old is mandatory, growing up is optional." +"age" -> * +"tibia" -> "If Tibia is a fallen god, does that makes us the maggots crawling on it?" +"castle" -> "A strong wall may protect from an assault, but what will protect you from the enemy within?" +"muriel" -> "Mages claim to be be wise, but how wise can it be to sacrifice your life to books and scrolls and not for the people?" +"sorcerer" -> * +"elane" -> "A paladin is more than just a knight armed with a bow and some spells, though most seem to be unaware of that fact." +"paladin" -> * +"marvik" -> "Druids seek enlightenment in nature, but they often just find what they brought with them." +"druid" -> * +"gregor" -> "Knights could be artists, but tend to become sellswords." +"knight" -> * +"necromant","nectar" -> "There is no such thing, believe me. The dead don't care for taste." +"goshnar" -> "The Necromant King. He is dead forever, and that is the nicest thing I can say about him. May he rot in his tomb." +"necromant" -> "How could they try to understand death, if they don't care to understand life?" +"nightmare","knight" -> "The Nightmare Knights were an ancient order dedicated to fight evil. They were guided by prophetic dreams. The order ceased to exist after their war against the Brotherhood of Bones." +"brotherhood","bone" -> "This brotherhood was an secret society of necromancers and followers of purest evil. They were vanquished long ago by their arch-enemies, the Nightmare Knights." +"pits","inferno" -> "An infernal place in which the nightmare knights created a base to fight the minions of evil. It was lost when the Ruthless Seven conquered it." +"ruthless","seven" -> "They are more than a myth, they are a horrible reality. It is possible that they still reside in the pits of inferno." +"sternum" -> "Behind the mountain lies a land of great danger." +"mintwallin" -> "The underground city of the minotaurs can be reached through a dangerous passage from the old temple." +"old","temple" -> "In the old days the underground temple was built for the glory of Banor after a victory over the orcish hordes. It is now an abandoned and dreary place overrun by rotworms." + +"carlin" -> "A city in the far north. It separated from the Thaian kingdom about 100 years ago. Now it is ruled by a dynasty of queens." +"thais" -> "Thais is the capital of an ancient human kingdom. Once its rule was more or less undisputed. In the years the strength of the thaian kingdom eroded by different events." +"ab'dendriel" -> "Although lovely, the city of the elves lacks the grace and the vibrance of the elven cities of old. The elves are still working on improvement of their settlement." +"Shadowthorn" -> "The elves of shadowthorn are hosile to intruders. Their Kuridai leaders practise some sinister cults and the other castes are more their minions then their equals." +"castes" -> "The elven society is divdided into certain cates, the cenath, the kuridai, the deaisim, the abdaisim and the legendary theshial." +"cenath" -> "The cenath favour magic above all other. They are the keeper of elven lore and wisdom. They are resposible for the astounding feats of druidic magic the elves are capable of." +"kuridai" -> "The Kuridai are the craftsmen and warriors of elvenkind. They are allways moving, allways sheming. They are the most agressive elves and distrust outsiders. An utsider might be each non-Kuridai to them." +"deraisim" -> "One could call the Deraisim the scouts and rangers of elvenkind. Although all elves are formidable in that area, the Deraisim excell them all." +"abdaisim" -> "The Abdaisim are what humans would call 'independent elves'. They take shelter wherever they might find it, are wanderers and explorers. They only keep loose contact with the elven society." +"teshial" -> "Its said that those elves were the masters of the dreams. Which many consider as a special brand of magic. However they seem to have vanished from the face of tibia ages ago and their fate is unknown." +"kazordoon" -> "The ancient fortrescity of the dwarf was carved into the mountain known as 'the big old one'. Its quite hidden and heavily guarded to withstand any assault." +"dwarf" -> "The small but strong dwarves are tireless workers and fierce warriors. They are familiar with several crafts and mastered most of them. In our days their smithing skills are rivaled only by those of the cyclopses." +"dwarv" -> * +"cyclops" -> "Cyclopses are seen as the smithes of blog, whom they call 'the ragehammer' or 'ragehammerer'. Indeed their skills create mostly crude and nasty looking weapons and armor which are incredicle effective nontheles." +"blog" -> "Blog is the god of rage and fierce battle. Hes also the patron of power, although a power to opress and bully others around. He is the son of Zathroth and one of the tibian suns." +"zathroth" -> "Zathroth is the dark twin of Uman. They are one and they are two seperate entities. We mortals can't realy grasp this concept. He is the patron of dark magic and even darker secrets, the lust for dominance +through cunning, and manipulation." +"zathroth" -> "Uman is the light twin of Zathroth. Their unity and seperatuion at once is a concept we cannot hope to grasp. He is the patron of light magic, the knowledge that beniefits all and brings progress to the society." +"venore" -> "Venore is a center of commerce and trade. Its ambitous trade-barons are nominaly subjects of the thaian kingdom." + +"paradox", "tower" -> "The paradox Tower was home of a mighty but mad wizard. Its said that only the cunning and mad can brave the tests of that tower to gain its treasures." +"ridler" -> "As far as I can tell this creature is not fond of cheaters and wont allow them to pass his tests." + +"magic","metal" -> "There are sevral kinds of magic metals in our world, the best known are called Mesh Kaha Rogh, Za'Kalortith, Uth'Byth, Uth'Morc, Uth'Amon, Uth'Maer, Uth'Doon, and Zatragil." + +"Mesh","Kaha","Rogh" -> "The so called singing steel causes a constant humming while its forged. It's said its a sign that it absorbs magic powers in the process and its probably easy to enchant it. However the secret where to mine or how to creat this ore is lost in time." +"Za'Kalortith" -> "This is the metal of 'evil'. The hell forged iron no ordinary flames can melt. Its rumored to be harvestet in hell from iron rocks in which damned souls were imprisoned." +"Uth'Byth" -> "This steel absorbs magic, its of inferior quality compared to ordinary steel but its absorbing qualities make it important though." +"Uth'Morc" -> "What makes this black steel special is its lightness and special property of lacking the common steel 'noise'. Its also called silent steel or thiefs steel for that reason." +"Uth'Amon" -> "The luminescent brightsteel is used for artwork mainly. In ancient times items of great magic power were created using the brightsteel. Those secrets were lost with the races which hold them as their secrets." +"Uth'Maer" -> "The dwarfs call it heartiron and claim its part of the heart of the big old one. Therefore is sacred and its use is limited and regulated." +"Uth'Doon" -> "The dwarfen high steel is relatively common but expensive and still hard to come by. The elite dwarfen weaponary and armors are made of Uth'Doon." +"Zatragil" -> "The so called dreamsilver is a legendary metal. Almost everything we know about it are rumours only." + + +"plains","havoc" -> "Somewhere in the Plains of Havoc, where the Necromant King was defeated lies the secret entrance to the pits of inferno." +"excalibug" -> "The ancient dwarfen kings forged it using magic metal , which they took from cyclopses who found it in the heart of a fallen star." +"venore" -> "The swamp city is a center of commerce and known for it riches and its merchant barons. It is part of the Thaian kingdom." +"rookgaard" -> "It was on rookgaard where the soul vortex appeared. The Thaian kingdom holds an outpost there to protect the vortex and to guide the newly arrived souls." +"soul","vortex" -> "The gods created the vortex to guide powerful souls to our world so they might join the battle for creation." +"magic","items" -> "Magic items are numerous. If you would like some details, please ask me about a specific item." +"bronze","amulet" -> "Some creatures are able to attack your magic power rather than your lifeforce. This amulet bestows some protection on you against those attacks." +"silver","amulet" -> "This amulet purifies your blood. It will reduce the damage caused by poison." +"platinum","amulet" -> "These powerful amulets are usually blessed by some god. They offer additional armour and protection." +"strange","talisman" -> "These amulets protect you from harm taken by energy attacks and magic fields." +"amulet","life" -> "These amulets were created and enchanted by powerful magicians and priests. They protect both body and soul from the losses caused by the trauma of death." +"stone","skin","amulet" -> "Though they possess only a few charges, stone skin amulets are sought after because they offer complete protection from physical damage." +"dragon","necklace" -> "The core piece of these amulets is a little dragon scale. It protects against fire damage to some extent." +"garlic","necklace" -> "This charm, feared and despised by the undead, protects your lifeforce from lifedraining powers." +"elven","amulet" -> "These ancient elven artifacts are highly enchanted and grant some protection against any each form of damage." +"shielding","amulet" -> "These amulets are a more powerful version of the elven amulets. They were created by a race long gone from this plane and offer significant protection against every kind of damage." + +"mightring" -> "This ring will give you limited protection against any kind of damage." +"swordring" -> "This ring will increase your skill when wielding swords." +"axering" -> "This ring will increase your skill when wielding any kind of axe." +"clubring" -> "This ring will increase your skill when wielding a club weapon." +"powerring" -> "This kind of ring will increase your skill when fighting with bare hands." +"timering" -> "These rings warp the fabric of time, greatly enhancing your running speed." +"lifering" -> "These rings improve your regenerative powers, accelerating the recovery of both your mana and your lifeforce." +"ring","healing" -> "This ring increases the rate with which you heal your physical wounds." +"stealthring" -> "These rings were created by an ancient, long forgotten race. It is said they valued secrecy above all. They used these magic rings to make themselves invisible." +"dwarvenring" -> "Actually rings of this kind are not created by dwarves. However, if you wear one you can drink as though you were a dwarf. They give you partial immunity against drunkenness." +"energyring" -> "These rings were created by the sorcerers' guilds of old. They temporarily provide their wielders with a shield of magic." + +"ghostlands" -> "The ancient structures that were found deep beneath the ghostlands were built by an unkown race for an unknown purpose. Its quite certain that, whatever they were once used for, they now cause madness and ghost sightings in the sourounding area." +"banshee" -> "The banshees were creatures that grief and despair turned into vengefull spirits after their deaths. Their wail is deadly and they draw new strength from the pain and fear of others." +"banshee","queen" -> "The legendary banshee queen is incerdibly old and likely next to invincible. Shes rumored to have her lair in the deepest caverns of the ghotlands. She seems to be more likely to talk then her sisters but is for sure even more evil then them." +"queen","banshee" -> * + +"hugo" -> "I think you are referring to the beast Hugo that is said to still haunt the Plains of Havoc. The legends which tell of this creature are ancient and almost forgotten." +"legend" -> "As far as we know, once a terrible beast roamed the lands we now call the Plains of Havoc. It was so fierce that no one dared to even dream about killing it. Finally it was tricked by the knight Endulos." +"endulos" -> "Endulos was not a great warrior, but a man of wit and genius. After many of his brethren of the Nightmare Knights had fallen prey to the beast, he came up with a cunning plan to end that threat." +"plan" -> "He lured Hugo into a trap. Bound by roots and stones charged with powerful magic he could not move anymore. Now, the beast lies trapped in a hidden cave for eternity." +"cave" -> "The legends tell us that the Nightmare Knights trapped it beneath one of their fortresses, or rather that they built a fortress on top of his eternal prison." +"ghostland" -> "The Ghostlands are haunted by their past. In bygone days some ancient race lived there. Deep beneath the earth some of their structures are still intact and defile the surrounding lands." +"defile" -> "Whatever the original meaning of that underground complex was, it is now like an open wound in the nearby lands, spreading madness and attracting all kinds of ghosts and apparitions." +"edron" -> "Edron is the latest colony of the Thaian kingdom. However, structures of an earlier colonisation have been found. We cannot tell if those inhabitants were human or of any other known race." +"daraman" -> "Daraman was a sage with ambition, that is for sure. His philosophy centered around the idea that by controlling yourself you could improve yourself. The closer you are coming to perfection, the closer you are to ascension to divinity." +"darama" -> "The desert lands of Darama are harsh and unforgiving. Therefore Daraman led his people there to found a new community based upon his teachings." +"darashia" -> "The town of Darashia is built around one of the few sweet water supplies of Darama. It is famous for its sand wasp honey and its sandworm stew." +"drefia" -> "The dreaded town of Drefia was once a haven for heretics, necromancers and demon worshipper. Its was destroyed in the war of the Djinn." +"war", "djinn" -> "Although some priests claim that war was fought on behalf of the gods, it seems more likely that this was kind of a civil war between the Djinn of good and the Djinn of evil." +"djinn" -> "Legend has it that the Djinn were created by Zathroth by using the stolen chalice of life. They roamed the world for Aeons causing strife and despair until Gabel, one of their lords met a very special human." +"gabel" -> "Gabel was the most powerful among the Djinn lords. He was cruel and merciless, until one day his minions brought a certain human to him whom they had captured and tortured." +"certain","human" -> "This human showed no fear and did not yield under torture. They could bend him, but they never managed to break him. Impressed by this mortal, Gabel talked to him and learned about his philosophy." +"philosophy" -> "The human whom the Djinn had caught was none other but Daraman. The mighty Gabel was intrigued by his philosophy an changed his ways according to Daramans teachings." +"teachings" -> "The teachings of asceticism, inner peace and ascension appealed to the Djinn, although in the beginning this was probably only because of his vanity and his greed for divinity. However, Malor and his followers opposed him." +"malor" -> "Malor was second in power only to Gabel and his followers among the Djinn were many. It was not easy for the evil Djinn to change their ways, and many preferred to follow Malor instead of Gabel. In the end a civil war erupted." +"civil","war" -> "In the war of the Djinn citys were levelled, lands were cursed and islands sunk. Finally, Malor was caught and imprisoned in an enchanted bottle. His followers fled this plane, while the good djinn laid to rest to recover their strength." +"chalice","life" -> "This chalice was a tool of the gods which they created to make their task to create life easier. Zathroth who lacked the knowledge of creation stole that chalice and used it to spawn his evil minions." +"minion","evil" -> "The Djinn were the result of his first attempts. They were powerful and quite evil, but not as evil as Zathroth wished and quite independent in their thinking. Finally he discarded them and decided his second try would become his masterpiece." +"masterpiece" -> "Zathroth channeled all the hatred and foulness he could muster. He added the burning rage of his son Blog and mixed it with fire. The energy that was released destroyed the chalice, but Zathroth had succeeded in creating the first demon." +"demon" -> "Demons are the servants of evil. More or less devoted servers of Zathroth they cause strife and havoc wherever they appear. Their masters are known as Demonlords, Demon Overlords and Archdemons." +"demonlords" -> "Demonlords are the generals of their kind. They are more cunning than ordinary demons, and they can channel their hatred more effectively than their lesser brethren, making them even more formidable opponents." +"demon","overlords" -> "The overlords of the demonkind are more powerful than even demonlords are. They are nearly indestructible. Armoured with layers of impenetrable hide and endowed with awesome magical power, demon overlords are true incarnation of death." +"archdemons" -> "The archdemons are few, and they are extremely rare. And a good thing, too, for they are the rulers of the demonrace. They are vain and powerhungry creatures who tend to form only small cabals and fight each other instead of allying up against creation." +"cabals" -> "There are at least five demonic cabals of archdemons. The ruthless seven are the most prominent and powerful." +} + + + + + diff --git a/app/SabrehavenServer/data/npc/edvard.npc b/app/SabrehavenServer/data/npc/edvard.npc new file mode 100644 index 0000000..b4cb130 --- /dev/null +++ b/app/SabrehavenServer/data/npc/edvard.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edvard.npc: Möbelverkäufer Edvard in Edron + +Name = "Edvard" +Outfit = (128,59-115-96-95-0) +Home = [33229,31831,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Edron Furniture Store, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Edvard. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/elane.npc b/app/SabrehavenServer/data/npc/elane.npc new file mode 100644 index 0000000..2275517 --- /dev/null +++ b/app/SabrehavenServer/data/npc/elane.npc @@ -0,0 +1,176 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elane.npc: Datenbank für die Paladinin Elane + +Name = "Elane" +Outfit = (137,113-63-120-119-2) +Home = [32343,32239,7] +Radius = 4 + +Behaviour = { +ADDRESS,Paladin,"hello$",! -> "Hi, %N! What can I do for you?" +ADDRESS,Paladin,"hi$",! -> * +ADDRESS,"hello$",! -> "Welcome to the paladins, %N! Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am the leader of the Paladins. I help our members." +"name" -> "My name is Elane. I am the famous leader of the Paladins." +"time" -> "Oops. I have forgotten my watch." +"king" -> "King Tibianus is a wise ruler." +"tibianus" -> * +"quentin" -> "A humble monk and a wise man." +"lynda" -> "Hm, a litte too nice for my taste." +"harkath" -> "A fine warrior and a skilled general." +"army" -> "Some paladins serve in the kings army." +"ferumbras" -> "Someday I will slay that bastard!" +"general" -> "Harkath Bloodblade is the royal general." +"sam" -> "Strong man. But a little shy." +"gorn" -> "He sells a lot of useful equipment." +"frodo" -> "The alcohol he sells shrouds the mind and the eye." +"galuna" -> "One of the most important members of our guild. She makes all the bows and arrows we need." +"bozo" -> "How spineless do you have to be to become a jester?" +"baxter" -> "He has some potential." +"oswald" -> "If there wouldn't be higher powers to protect him..." +"sherry" -> "The McRonalds are simple farmers." +"donald" -> * +"mcronald" -> * +"elane" -> "Yes?" +"muriel" -> "Just another arrogant sorcerer." +"gregor" -> "He and his guildfellows lack the grace of a true warrior." +"marvik" -> "A skilled healer, that's for sure." +"lugri" -> "A follower of evil that will get what he deserves one day." +"excalibug" -> "A weapon of myth. I don't believe that this weapon exists." +"news" -> "I am a paladin, not a storyteller." + +"member" -> "Every paladin profits from his vocation. It has many advantages to be a paladin." +"profit" -> "We will help you to improve your skills. Besides I offer spells for paladins." +"advantage" -> "We will help you to improve your skills. Besides I offer spells for paladins." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Paladins, knights, sorcerers, and druids." +"paladin" -> "Paladins are great warriors and magicians. Besides that we are excellent missile fighters. Many people in Tibia want to join us." +"skill" -> "Paladins are great warriors and magicians. Besides that we are excellent missile fighters. Many people in Tibia want to join us." +"warrior" -> "Of course, we aren't as strong as knights, but no druid or sorcerer will ever defeat a paladin with a sword." +"magician" -> "There are many magic spells and runes paladins can use." +"missile" -> "Paladins are the best missile fighters in Tibia!" +"spellbook" -> "In a spellbook your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Xodet in his magic shop." +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Bye.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." + +"sniper","gloves",Count(5875)<=0 -> "We are always looking for sniper gloves. They are supposed to raise accuracy. If you find a pair, bring them here. Maybe I can offer you a nice trade." +"sniper","gloves",QuestValue(17538)=0 -> Type=5875, Amount=1, "You found sniper gloves?! Incredible! Listen, if you give them to me, I will grant you the right to wear the sniper gloves accessory. How about it?", Topic=4 +Topic=4,"yes",Count(Type)>=Amount -> "Great! I hereby grant you the right to wear the sniper gloves as accessory. Congratulations! Also, you can ask for power bolts to Galuna she will be surprised to see a hunter like you.", Delete(Type), SetQuestValue(17538,1), AddOutfitAddon(137,2), AddOutfitAddon(129,2), EffectOpp(13) +Topic=4,"yes" -> "You don't have it." +Topic=4 -> "Maybe another time." + +"sniper","gloves",QuestValue(17538)=1 -> Type=5875, Amount=1, Price=2000, "You found sniper gloves?! Incredible! I would pay you 2000 gold pieces for them. How about it?", Topic=5 +Topic=5,"yes",Count(Type)>=Amount -> "Congratulations.", Delete(Type), CreateMoney +Topic=5,"yes" -> "You don't have it." +Topic=5 -> "Maybe another time." + +"outfit" -> "Oh, my winged tiara? Those are traditionally awarded after having completed a difficult task for our guild, only to female aspirants though. Male warriors will receive a hooded cloak." +"addon" -> * + +"task",QuestValue(17539)=0,male -> "So you are saying that you would like to prove that you deserve to wear such a hooded cloak?", Topic=6 +"mission",QuestValue(17539)=0,male -> * +"task",QuestValue(17539)=0,female -> "So you are saying that you would like to prove that you deserve to wear such a winged tiara?", Topic=6 +"mission",QuestValue(17539)=0,female -> * +Topic=6,"yes" -> "Alright, I will give you a chance. Pay close attention to what I'm going to tell you now. ...", + "Recently, one of our members moved to Liberty Bay out of nowhere, talking about some strange cult. That is not the problem, but he took my favourite crossbow with him. ...", + "Please find my crossbow. It has my name engraved on it and is very special to me. ...", + "Secondly, we need a lot of leather for new quivers. 100 pieces of lizard leather and 100 pieces of red dragon leather should suffice. ...", + "Third, since we are giving out tiaras, we are always in need of enchanted chicken wings. Please bring me 5, that would help us tremendously. ...", + "Lastly, for our arrow heads we need a lot of steel. Best would be one piece of royal steel, one piece of draconian steel and one piece of hell steel. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=7 +Topic=6 -> "However." +Topic=7,"yes" -> "That's the spirit! I hope you will find my crossbow, %N.", SetQuestValue(17539,1), SetQuestValue(17594,1) +Topic=7 -> "Maybe another time." + +"crossbow",QuestValue(17539)=1 -> Type=5947, Amount=1, "I'm so excited! Have you really found my crossbow?", Topic=8 +"mission",QuestValue(17539)=1 -> * +"task",QuestValue(17539)=1 -> * +Topic=8,"yes",Count(Type)>=Amount,male -> "Yeah! I could kiss you right here and there! Besides, you're a handsome one. Please bring me 100 pieces of lizard leather and 100 pieces of red dragon leather now!", Delete(Type), SetQuestValue(17539,2) +Topic=8,"yes",Count(Type)>=Amount,female -> "Good work, %N! Please bring me 100 pieces of lizard leather and 100 pieces of red dragon leather now!", Delete(Type), SetQuestValue(17539,2) +Topic=8,"yes" -> "You don't have it." +Topic=8 -> "Maybe another time." + +"leather",QuestValue(17539)=2 -> "Did you bring me 100 pieces of lizard leather and 100 pieces of red dragon leather?", Topic=9 +"mission",QuestValue(17539)=2 -> * +"task",QuestValue(17539)=2 -> * +Topic=9,"yes",Count(5948)>=100,Count(5876)>=100 -> "Good work, %N! That is enough leather for a lot of sturdy quivers. Now, please bring me 5 enchanted chicken wings.", DeleteAmount(5948,100), DeleteAmount(5876,100), SetQuestValue(17539,3) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe another time." + +"enchanted","chicken","wing",QuestValue(17539)=3 -> Type=5891, Amount=5, "Were you able to get hold of 5 enchanted chicken wings?", Topic=10 +"mission",QuestValue(17539)=3 -> * +"task",QuestValue(17539)=3 -> * +Topic=10,"yes",Count(Type)>=Amount -> "Great! Now we can create a few more Tiaras. If only they weren't that expensive... Well anyway, please obtain one piece of royal steel, draconian steel and hell steel each.", Delete(Type), SetQuestValue(17539,4) +Topic=10,"yes" -> "You don't have that many." +Topic=10 -> "Maybe another time." + +"steel",QuestValue(17539)=4 -> "Ah, have you brought one piece of royal steel, draconian steel and hell steel each?", Topic=11 +"mission",QuestValue(17539)=4 -> * +"task",QuestValue(17539)=4 -> * +Topic=11,"yes",Count(5888)>=1,Count(5889)>=1,Count(5887)>=1 -> "Wow, I'm impressed, %N. Your really are a valuable member of our paladin guild. I shall grant you your reward now. Wear it proudly!", DeleteAmount(5888,1), DeleteAmount(5889,1), DeleteAmount(5887,1), SetQuestValue(17539,5), AddOutfitAddon(137,1), AddOutfitAddon(129,1), EffectOpp(13) +Topic=11,"yes" -> "You don't have that many." +Topic=11 -> "Maybe another time." + +"mission",QuestValue(17539)=5 -> "Paladin guild has no more tasks for you our loyal %N." +"task",QuestValue(17539)=5 -> * +} diff --git a/app/SabrehavenServer/data/npc/elathriel.npc b/app/SabrehavenServer/data/npc/elathriel.npc new file mode 100644 index 0000000..4e5e591 --- /dev/null +++ b/app/SabrehavenServer/data/npc/elathriel.npc @@ -0,0 +1,94 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# erathriel.npc: Datenbank für den Kuridai-Anführer Elathriel (Elfenstadt) + +Name = "Elathriel" +Outfit = (64,0-0-0-0-0) +Home = [32684,31671,9] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up! Can't you see that I am talking?!" +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi, stranger.", Idle +"farewell" -> * +"asha","thrazi" -> * +"name" -> "Not that I like to talk to you, but I am Elathriel Shadowslayer." +"job" -> "I am the leader of the Kuridai and the Az'irel of Ab'dendriel. Humans would call it sheriff, executioner, or avenger." +"sheriff" -> "Sometimes people get imprisoned for some time. True criminals will be cast out and for comitting the worst crimes offenders are thrown into the hellgate." +"executioner" -> * +"avenger" -> * + +"hellgate" -> "It was here among other structures, like the depot tower, before our people came here. It's secured by a sealed door." +"door" -> "For safety we keep the door to the hellgate locked all times. I have the keys to open it when needed." +"sealed" -> * +"key" -> Type=2970, Data=3012, Amount=1, Price=5000, "If you are that curious, do you want to buy a key for %P gold? Don't blame me if you get sucked in.", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Believe me, it's better for you that way." + +"time" -> "I couldn't care less." + +"carlin" -> "We watch this city and the actions of its inhabitants closely." +"thais" -> "The thain kingdom and we share some enemys, so its only logical to cooperate in a few areas." +"venore" -> "The merchants of venore provide us with some usefull goods. Still I an convinced that they get more out of our bargain then we do." +"roderick" -> "He is tolerated here as the spokesman of the thaian king." +"olrik" -> "This human is too unimportant to be even mentioned." + +"king" -> "It's hard for some of my people to grasp the true concept of a strong leader." +"tibianus" -> "A human weakling, not much more." +"eloise" -> * +"elves" -> "My people are divided in castes in these times, until they comprehend that only the way of the Kuridai can save us all." +"dwarfs" -> "We might use the shelter earth and hills provide us, but their obsession for metal is a waste of time." +"humans" -> "They are useful ... and better stay useful." +"troll" -> "Like all inferior races they can be at least used for something good. The other castes are just jealous about our use of them." +"army" -> "It's one of the more useful concepts we can learn from the other races." +"cenath" -> "Arrogant bastards, but they wield quite powerful magics." +"kuridai" -> "We are the heart of the elven society. We forge, we build, and we don't allow our people to be pushed around." +"deraisim" -> "Confused cowards. With all their skill they still tend to hide and run. What a waste." +"abdaisim" -> "Even more undecided then the deraisim." +"teshial" -> "Dreamers are of no practical use. I don't mourn their demise." +"ferumbras" -> "Even if he'd walk through the town above the other castes won't see the necessity to follow OUR way." +"crunor" -> "I have no use for the treething. I worship Mortiur, the ravager, of course." +"mortiur" -> "The celestial paladin of revenge. He was one of the greatest elven wariors of all times." +"excalibug" -> "I still doubt it exists." +"news" -> "News are confidential and not your business." + +"magic" -> "I mastered some spells of battle." +"druid" -> "Druids' magic is too peaceful for my taste." +"sorcerer" -> "I have seen human sorcerers doing some impressive things ... before they died." +"spellbook" -> "I don't sell such stuff." +"spell" -> "I teach the spells 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball', 'Great Fireball', 'Fire Bomb', and 'Explosion'." + +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 + +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 + +"light","missile" -> "I'm sorry, but this spell is only for druids and paladins." +"heavy","missile" -> * +"fireball" -> * +"great","fireball" -> "I'm sorry, but this spell is only for druids." +"fire","bomb" -> * +"explosion" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/app/SabrehavenServer/data/npc/eleonore.npc b/app/SabrehavenServer/data/npc/eleonore.npc new file mode 100644 index 0000000..11da91a --- /dev/null +++ b/app/SabrehavenServer/data/npc/eleonore.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Eleonore" +Outfit = (140,95-10-13-15-1) +Home = [32331,32763,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted. What brings you here?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh well." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"ring",QuestValue(17502)=0 -> "My ring was stolen by a parrot, directly from my dressing table near the window. It flew to the nearby mountains and I fear my ring will be lost forever. Whoever returns it to me will be rewarded generously. ...", + "I guess that evil parrot hid the ring somewhere on a high tree or a rock so that you might need a rake to get it.", SetQuestValue(17502,1), SetQuestValue(17593,1) +"mission",QuestValue(17502)=0 -> * + +"ring",QuestValue(17502)=1 -> Type=6093, Amount=1, Price=150, "Oh, my beloved ring! Have you found it and want to return it to me?", Topic=1 +"mission",QuestValue(17502)=1 -> * + +Topic=1,"yes",Count(Type)>=Amount -> "Oh, thank you so much! Take this gold as a reward. ... which reminds me, I would need some help in another matter. It is only a small errand. Are you interested?", Delete(Type), CreateMoney, SetQuestValue(17502,2), Topic=2 +Topic=1,"yes" -> "You don't have it!" +Topic=1 -> "Too bad." + +"ring",QuestValue(17502)=2 -> Price=200, "I would need some help in another matter. It is only a small errand. Are you interested?", Topic=3 +"mission",QuestValue(17502)=2 -> * +"errand",QuestValue(17502)=2 -> * +Topic=2,"yes" -> Price=200, "Thank you! It is not a difficult matter but a rather urgent one. I need to send some money to a person in town. Would you be willing to run this small errand for me?", Topic=3 +Topic=2 -> "Too bad." +Topic=3,"yes" -> "I was hoping that you'd agree. Please deliver these 200 gold pieces to the herbalist Charlotta in the south-western part of the town. If you return from this errand, I will grant you 5 gold pieces as reward for your efforts.", CreateMoney, SetQuestValue(17502,3) +Topic=3 -> "Too bad." + +"mission",QuestValue(17502)=3 -> "Charlotta is still waiting for your delivery." +"errand",QuestValue(17502)=3 -> * + +"mission",QuestValue(17502)=4 -> Price=5, "Great, thank you! As promised, here are your 5 gold pieces. Is there ... anything left that you might want to discuss with me?", CreateMoney, SetQuestValue(17502,5) +"errand",QuestValue(17502)=4 -> * + +"peg","leg",QuestValue(17502)=5 -> "You have returned my ring and proven yourself as trustworthy. There is something I have to discuss with you. Are you willing to listen?", Topic=4 +Topic=4,"yes" -> "I am glad to hear that. So please listen: Due to circumstances too complicated to explain now, I met Captain Ray Striker. He is ... a freedom fighter and would not find my father's acceptance, but we fell in love ...", + "Even though he had to hide for a while, we have stayed in contact for a long time now. And our love grew even further against all odds ...", + "However, recently we lost contact. I don't know what has happened to him and fear the worst ...", + "We always have been aware that something terrible might happen to him due to his lifestyle. But perhaps there is a harmless explanation for the absence of messages . I have arranged a passage for you to Ray's hiding place ...", + "Contact Captain Waverider, the old fisherman, and tell him the secret word 'peg leg'. He will make sure that you arrive safely ...", + "Please look for Ray and find out what happened to him and why he was not able to answer. Return to me as soon as you have found something out. I wish you a good journey.", SetQuestValue(17502,6) +Topic=4 -> "Too bad." +"mission",QuestValue(17502)>5,QuestValue(17502)<13 -> "Return to me as soon as you have found something out about Ray." +"ray","striker",QuestValue(17502)=13 -> " Oh, he is so wonderful. A very special man with a special place in my heart.", Topic=5 +Topic=5,"mermaid" -> "I can't thank you enough for freeing my beloved Ray from that evil spell. I am still shocked that a mermaid could steal his love that easily.", SetQuestValue(17502,14) + +} diff --git a/app/SabrehavenServer/data/npc/elfguard.npc b/app/SabrehavenServer/data/npc/elfguard.npc new file mode 100644 index 0000000..8ca770f --- /dev/null +++ b/app/SabrehavenServer/data/npc/elfguard.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elfguard.npc: Datenbank für die Elfenwache in Ab'Dendriel + +Name = "Elf Guard" +Outfit = (63,0-0-0-0-0) +Home = [32642,31709,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "My name is unimportant, only my duty does matter." +"job" -> "I am a guardian of this town. I have no time to chat!" +"time" -> "It's %T." +"town" -> "This is the elven town of Ab'Dendriel." +"city" -> * +"ab'dendriel" -> * +"thais" -> "The city of the humans lies somewhere far to the south beyond the mountains of the dwarfs." +"carlin" -> "This city of humankind is located to the west of our area." +"kazordoon" -> "The dwarfish settlement is hidden somewhere in the mountains in the south." +"elf" -> "The elves of this city are the casts of the Cenath, the Kuridai, and the Deraisim." +"elves" -> * +"cenath" -> "The Cenath are magic users. Look for them on the upper levels of the town." +"kuridai" -> "The Kuridai are the smiths and craftsmen. Look for them in the underground parts of the city." +"deraisim" -> "The Deraisim are scouts and hunters. You may find them on the groundlevel of the city." +"abdaisim" -> "The Abdaisim are wanderers. Since they live as nomads and travel the world you won't find them here." +"teshial" -> "There are no Teshial." +"ferumbras" -> "He is not allowed to enter this city." +"army" -> "Such a thing is a human concept. We have no need for that, though some Kuridai might think otherwise." +"spell" -> "Ask around in Ab'Dendriel. Many elves can teach you something about magic. The Cenath love magic most of all." +"magic" -> * +"armor" -> "If you are looking for that kind of equipment you should ask a Kuridai." +"weapon" -> * +"food" -> "Ask some Deraisim where you can get food." + +"tha'shi","ab'dendriel" -> "In the crude human language you would translate it with 'My life for Ab'Dendriel' or even 'I am one with Ab'Dendriel'." +"bahaha","aka" -> "This means 'Take your punishment, defiler'." +} diff --git a/app/SabrehavenServer/data/npc/elvith.npc b/app/SabrehavenServer/data/npc/elvith.npc new file mode 100644 index 0000000..f487bc1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/elvith.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elvith.npc: Datenbank für den Musiker Elvith + +Name = "Elvith" +Outfit = (144,76-3-0-76-0) +Home = [32669,31607,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "I am Elvith Rollingstone." +"job" -> "I sell musical instruments of many kinds." +"time" -> "Time has its own song. Close your eyes and listen to the symphony of the seasons." + +"carlin" -> "Carlin is a city that thrives for a harmony it can never achive." +"thais" -> "I heared about Thais and id did not sound like a place I'd want to visit." +"venore" -> "By all what I heared this city is not only built into a swamp but its a swamp of intrigue and corruption itself." +"roderick" -> "This man trys too hard not to offend someone." +"olrik" -> "He appreciates my music and allthough he is loud and clumsy as all humans it seems not everything is lost." + +"music" -> "Music is an attempt to condensate emotions in harmonies and save them for the times to come." +"harmonies" -> "Everything is a song. Life, death, history ... everything. To listen to the song of something is the first step to understand it." +"melodies" -> * +"harmony" -> * +"melody" -> * +"song" -> * +"sing" -> "Sorry, but there is a melody in my heart that wants to be born. I would loose it before by singing right now." +"elf" -> "We are the most graceful of all races. We feel the music of the universe in our hearts and souls." +"elves" -> * +"dwarf" -> "They could at least use their picks and hammers with more rythm." +"human" -> "They are too loud and don't even understand the concept of a melody." +"troll" -> "I went down to the mines and tried to lighten up their spirit, the foolish creatures did not listen to my songs, though." +"cenath" -> "The Cenath think they know the 'art' but the only true art is the music." +"kuridai" -> "They could dig some halls for a big musical event, but they won't listen to me about that matter." +"deraisim" -> "The other deraisim are too much concerned with mastering the nature so they don't listen to its music anymore." +"abdaisim" -> "The wanderers have no patience. You need patience and passion to create and to enjoy music." +"teshial" -> "I bet they were great musicians." +"ferumbras" -> "Only humans made songs about him and his evil deeds." +"crunor" -> "That is some god the humans worship. Our pople are not interested in this gods anymore." +"excalibug" -> "There are too many songs about that weapon to retell them all. Most of them are human and therefore quite crude anyways." +"spell" -> "Sorry, I don't feel like teaching magic today." +"magic" -> * + +"elven", "poetry",QuestValue(311)=1,QuestValue(312)=0 -> "The last issue I had was bought by Randor Swiftfinger. He was banished through the hellgate and probably took the book with him ...", "I would not recommend to seek him or the book there but of course its possible." +"song", "forest",QuestValue(311)=1 -> * + +"elven", "poetry",QuestValue(311)=1,QuestValue(312)=1 -> Type=4844, Amount=1, Price=500,"By luck I aquired another copy of the book you are looking for. Do you want to buy a copy of 'songs of the forest' for 500 gold?.",topic=2 +"song", "forest",QuestValue(311)=1 -> * +Topic=2,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Sorry, you do not have enough gold." +Topic=2 -> "Maybe you will buy it another time." + + + +"elven", "poetry" -> "Sorry, I have no issue of this book left." +"song", "forest" -> * + +"hellsgate" -> "For the worst of crimes the criminals are cast into hellgate. Its said noone can return from there. Since it is not forbidden to enter hellgate you might convince Elathriel to grant you entrance." +"elathriel" -> "He is a kuridai and the local Az'irel. Something like the head of the human townsguards." + +"offer" -> "I sell lyres, lutes, drums, and simple fanfares." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=1 +"drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=1 +"simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=1 +"love","poem" -> Type=5952, Amount=1, Price=200, "Do you want to buy a love poem for %P gold?", Topic=1 + +%1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyre for %P gold?", Topic=1 +%1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lute for %P gold?", Topic=1 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drum for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfare for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/app/SabrehavenServer/data/npc/eranth.npc b/app/SabrehavenServer/data/npc/eranth.npc new file mode 100644 index 0000000..dab8bec --- /dev/null +++ b/app/SabrehavenServer/data/npc/eranth.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Eranth" +Outfit = (129,116-79-78-113-0) +Home = [32123,32953,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/erayo.npc b/app/SabrehavenServer/data/npc/erayo.npc new file mode 100644 index 0000000..58b5de5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/erayo.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Erayo" +Outfit = (152,85-86-106-86-3) +Home = [32518,32911,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "What the... I mean, of course I sensed you.!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a minute, %N." +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle + +"addon",QuestValue(17562)<9 -> "You don't look like the one for who I should answer about my assassin head piece." +"outfit",QuestValue(17562)<9 -> * + +"addon",QuestValue(17562)=9 -> "Vescu gave you an assassin outfit? Haha. Noticed it lacks the head piece? You look a bit silly. Want my old head piece?", Topic=1 +"outfit",QuestValue(17562)=9 -> * +Topic=1,"yes" -> "Thought so. Could use some help anyway. Listen, I need stuff. Someone gave me a strange assignment - sneak into Thais castle at night and shroud it with cloth without anyone noticing it. ...", + "I wonder why anyone would want to shroud a castle, but as long as the guy pays, no problem, I'll do the sneaking part. Need a lot of cloth though. ...", + "Gonna make it colourful. Bring me 50 pieces of blue cloth, 50 pieces of green cloth, 50 pieces of red cloth, 50 pieces of brown cloth, 50 pieces of yellow cloth and 50 pieces of white cloth. ...", + "Besides, gonna need 10 spools of yarn. Understood?", Topic=2 +Topic=1 -> "Maybe another time." +Topic=2,"yes" -> "Good. Start with the blue cloth. I'll wait.", SetQuestValue(17562,10) +Topic=2 -> "Maybe another time." + +"blue","cloth",QuestValue(17562)=10 -> Type=5912, Amount=50, "Brought the 50 pieces of blue cloth?", Topic=3 +"mission",QuestValue(17562)=10 -> * +"task",QuestValue(17562)=10 -> * +Topic=3,"yes",Count(Type)>=Amount -> "Good. Get me 50 pieces of green cloth now.", Delete(Type), SetQuestValue(17562,11) +Topic=3,"yes" -> "You don't have that many." +Topic=3 -> "Maybe another time." + +"green","cloth",QuestValue(17562)=11 -> Type=5910, Amount=50, "Brought the 50 pieces of green cloth?", Topic=4 +"mission",QuestValue(17562)=11 -> * +"task",QuestValue(17562)=11 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Good. Get me 50 pieces of red cloth now.", Delete(Type), SetQuestValue(17562,12) +Topic=4,"yes" -> "You don't have that many." +Topic=4 -> "Maybe another time." + +"red","cloth",QuestValue(17562)=12 -> Type=5911, Amount=50, "Brought the 50 pieces of red cloth?", Topic=5 +"mission",QuestValue(17562)=12 -> * +"task",QuestValue(17562)=12 -> * +Topic=5,"yes",Count(Type)>=Amount -> "Good. Get me 50 pieces of brown cloth now.", Delete(Type), SetQuestValue(17562,13) +Topic=5,"yes" -> "You don't have that many." +Topic=5 -> "Maybe another time." + +"brown","cloth",QuestValue(17562)=13 -> Type=5913, Amount=50, "Brought the 50 pieces of brown cloth?", Topic=6 +"mission",QuestValue(17562)=13 -> * +"task",QuestValue(17562)=13 -> * +Topic=6,"yes",Count(Type)>=Amount -> "Good. Get me 50 pieces of yellow cloth now.", Delete(Type), SetQuestValue(17562,14) +Topic=6,"yes" -> "You don't have that many." +Topic=6 -> "Maybe another time." + +"yellow","cloth",QuestValue(17562)=14 -> Type=5914, Amount=50, "Brought the 50 pieces of yellow cloth?", Topic=7 +"mission",QuestValue(17562)=14 -> * +"task",QuestValue(17562)=14 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Good. Get me 50 pieces of white cloth now.", Delete(Type), SetQuestValue(17562,15) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe another time." + +"white","cloth",QuestValue(17562)=15 -> Type=5909, Amount=50, "Brought the 50 pieces of white cloth?", Topic=8 +"mission",QuestValue(17562)=15 -> * +"task",QuestValue(17562)=15 -> * +Topic=8,"yes",Count(Type)>=Amount -> "Good. Get me 10 spools of yarn now.", Delete(Type), SetQuestValue(17562,16) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe another time." + +"yarn",QuestValue(17562)=16 -> Type=5886, Amount=10, "Brought the 10 spools of yarn?", Topic=9 +"mission",QuestValue(17562)=16 -> * +"task",QuestValue(17562)=16 -> * +Topic=9,"yes",Count(Type)>=Amount -> "Thanks. That's it, you're done. Good job, %N. I keep my promise. Here's my old assassin head piece.", Delete(Type), SetQuestValue(17562,17), AddOutfitAddon(152,1), AddOutfitAddon(156,1), EffectOpp(13) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe another time." + +"mission",QuestValue(17562)>16 -> "Sorry, I don't have any tasks for you anymore." +"task",QuestValue(17562)>16 -> * + +} diff --git a/app/SabrehavenServer/data/npc/eremo.npc b/app/SabrehavenServer/data/npc/eremo.npc new file mode 100644 index 0000000..84c0f5e --- /dev/null +++ b/app/SabrehavenServer/data/npc/eremo.npc @@ -0,0 +1,126 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eremo.npc: Datenbank für den Weisen Eremo + +Name = "Eremo" +Outfit = (130,0-109-128-95-0) +Home = [33322,31883,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little garden, adventurer %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,premium,promoted,"hello$",! -> "Welcome to my little garden, humble %N!" +ADDRESS,premium,promoted,"hi$",! -> * +ADDRESS,premium,promoted,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Shouldn't I teleport you back to Pemaret?" + +"bye" -> "Shouldn't I teleport you back to Pemaret?", Idle +"farewell" -> * +"name" -> "I am Eremo, an old man who has seen many things." +"job" -> "I teach some spells, provide one of the five blessings, and sell some amulets." +"offer" -> * +"magic" -> * +"island" -> "I have retired from my adventures to this place." +"isle" -> * +"garden" -> * +"adventure" -> "I explored dungeons, I walked through deserts, I sailed on the seas and climbed up on many a mountain." +"thing" -> * +"Tibia" -> "A great world full of magic and wonder." + +"amulet",PvPEnforced -> "I've collected quite a few protection amulets. Also, I'm interested in buying broken amulets." +"amulet" -> "I've collected quite a few protection amulets, and some amulets of loss as well. Also, I'm interested in buying broken amulets." + + +"amulet","of","loss" -> Type=3057, Amount=1, Price=50000, "Do you want to buy an amulet of loss for %P gold?", Topic=3 +"amulet","of","loss",PvPEnforced,! -> "What a strange name for an amulet. Never heard about that one." + + +"protection","amulet" -> Type=3084, Amount=1, Price=700, "Do you want to buy a protection amulet for %P gold?", Topic=3 +"broken","amulet" -> Type=3080, Amount=1, Price=50000, "Do you want to sell a broken amulet for %P gold?", Topic=4 +"amulet","of","life" -> * + +premium,promoted,"spell" -> "I can teach 'Enchant Staff' to sorcerers, 'Challenge' to knights, 'Wild Growth' to druids, and 'Power Bolt' to paladins." +"spell" -> "I am sorry, but you are not promoted yet." + +sorcerer,premium,promoted,"enchant","staff" -> String="Enchant Staff", Price=2000, "Do you want to learn the spell 'Enchant Staff' for %P gold?", Topic=1 +"enchant","staff" -> "I am sorry but this spell is only for master sorcerers." + +knight,premium,promoted,"challenge" -> String="Challenge", Price=2000, "Do you want to learn the spell 'Challenge' for %P gold?", Topic=1 +"challenge" -> "I am sorry but this spell is only for elite knights." + +druid,premium,promoted,"wild","growth" -> String="Wild Growth", Price=2000, "Do you want to learn the spell 'Wild Growth' for %P gold?", Topic=1 +"wild","growth" -> "I am sorry but this spell is only for elder druids." + +paladin,premium,promoted,"power","bolt" -> String="Power Bolt", Price=2000, "Do you want to learn the spell 'Power Bolt' for %P gold?", Topic=1 +"power","bolt" -> "I am sorry but this spell is only for royal paladins." + +"teleport" -> "Should I teleport you back to Pemaret?",Topic=2 +"pemaret" -> * +"back" -> * +"cormaya" -> * +"edron" -> * + +Topic=1,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "You must be have level %A or better to learn this spell." +Topic=1,"yes",CountMoney "Oh. You do not have enough money." +Topic=1,"yes" -> "Here you are. Look in your spellbook for the pronounciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Fine. Do as you please." + + +Topic=2,"yes" -> "Here you go!", Idle, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) +Topic=2 -> "Maybe later." + +Topic=3,"yes",CountMoney>=Price -> "Thank you. Use it wisely.", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Sorry, you do not have enough gold." +Topic=3 -> "Maybe another time." + +Topic=4,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=4,"yes" -> "Sorry, you do not own one." +Topic=4 -> "Maybe another time." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of Tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just tell me in which of the five blessings you are interested." + +"spiritual" -> " You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of Tibia." + +"fire" -> "You should ask for the blessing of the two suns in the suntower near Ab'Dendriel." +"suns" -> * +"wisdom" -> "I can provide you with the wisdom of solitude. But you will have to sacrifice 10.000 gold to receive it. Are you still interested?",Price=10000,Topic=5 +"solitude" -> * + +Topic=5,"yes", QuestValue(101) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the wisdom of solitude, pilgrim", DeleteMoney, EffectOpp(13), SetQuestValue(101,1), Bless(4) +Topic=5,! -> "Ok. As you wish." + +"letter",QuestValue(17522)=1,Count(6113)>=1 -> "A letter from that youngster Morgan? I believed him dead since years. These news are good news indeed. Thank you very much, my friend.", DeleteAmount(6113, 1), SetQuestValue(17522,2) +"letter" -> "I am not waiting for any letters from strangers." +} + diff --git a/app/SabrehavenServer/data/npc/eroth.npc b/app/SabrehavenServer/data/npc/eroth.npc new file mode 100644 index 0000000..0987b5e --- /dev/null +++ b/app/SabrehavenServer/data/npc/eroth.npc @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eroth.npc: Datenbank für den Cenath-Anführer Eroth (Elfenstadt) + +Name = "Eroth" +Outfit = (63,0-0-0-0-0) +Home = [32661,31685,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "I greet thee, outsider." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence!" +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi. Go, where you have to go.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the leader of the Cenath caste." +"name" -> "My name is Eroth Ramathi." +"time" -> "That is a inferior concept invented by the minor races." + +"carlin" -> "Their druids seek my counsel quite often. I provide them with as many insights their little minds can keep up with and I feel appropriate." +"thais" -> "A city of filth and dirt. Any elf should visit this city at least once to see what a society without good guidance can become." +"venore" -> "The merchants of venore have prooven usefull and are therefore tolerated." +"roderick" -> "A stupid human who won't comprehend our complex society." +"olrik" -> "A human who dreams to become an elf. It would be funny if it were not that pathetic." + +"king" -> "Our people have no use for kings or queens." +"tibianus" -> * +"elves" -> "Our people are the children of light and darkness, the heirs of dusk and dawn." +"dwarfs" -> "The diggers are not welcome in our realm." +"humans" -> "We tolerate them and allow them to be used by us." +"troll" -> "The Kuridai have the distasteful habit to keep some trolls for inferior work." +"army" -> "Stop this Kuridai nonsense." +"cenath" -> "We are the shepherds of our people. The other castes need our guidance." +"kuridai" -> "The Kuridai are aggressive and victims of their instincts. Without our help they would surely die in a foolish war." +"deraisim" -> "They lack the understanding of unity. We are keeping them together and prevent them from being slaughtered one by one." +"abdaisim" -> "They are fools and almost deserve the extinction that awaits them. Though we will take it upon us to rescue even them by leading them home." +"teshial" -> "They are gone. They alone were almost equal to us Cenath among elvenkind." +"dreamer" -> "The Teshial were masters of the so called dream magic." +"dream","master"-> "The dream masters, though overestimated, wielded some impressive power without much practical use." +"dreammaster" -> * +"ferumbras" -> "A human born evil. Another evidence of the destructive potential of that race." +"crunor" -> "Gods are for the weak. We will master the world on our own. We need no gods." +"excalibug" -> "Just another human myth." +"news" -> "I heared the new human settlement in the west became independent from the human empire." + +"magic" -> "Magic comes almost naturally to the Cenath. We keep the secrets of ages." +"druid" -> "Druids master spells of defence, healing, and nature." +"sorcerer" -> "Sorcerers are not attuned to nature and therefore can't master it." +"vocation" -> "You are narrow minded to think in such boundaries." +"spellbook" -> "Cenath rarely use spellbooks. The minor castes rely on them though." +"spell" -> "I can teach the spells 'Magic Shield', 'Invisible', 'Destroy Field', 'Creature Illusion', 'Chameleon', 'Convince Creature', and 'Summon Creature'." + +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +"magic","shield" -> "I'm sorry, but this spell is only for druids and paladins." +"destroy","field" -> * +"chameleon" -> "I'm sorry, but this spell is only for druids." +"creature","illusion" -> * +"convince","creature" -> * +"summon","creature" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/app/SabrehavenServer/data/npc/etzel.npc b/app/SabrehavenServer/data/npc/etzel.npc new file mode 100644 index 0000000..e4bd28c --- /dev/null +++ b/app/SabrehavenServer/data/npc/etzel.npc @@ -0,0 +1,152 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# etzel.npc: Datenbank für den Magier Etzel + +Name = "Etzel" +Outfit = (66,0-0-0-0-0) +Home = [32626,31917,5] +Radius = 3 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Hiho and welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> * +ADDRESS,Sorcerer,"hiho$",! -> * +ADDRESS,"hello$",! -> "Hiho, %N. " +ADDRESS,"hi$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Patient, young %N! Wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care out there, young one. " + +"bye" -> "Take care out there, young one. ", Idle +"farewell" -> * +"job" -> "I am der dwarfish mastermage. I am keeper of the secrets of magic." +"name" -> "My name is Etzel Fireworker, son of fire, of the Molten Rocks." +"time" -> "It's precisely %T now." +"wisdom" -> "Wisdom is not aquired cheeply." +"sorcerer" -> "Sorcery is not for the lazy or the impatient." +"power" -> "Great power brings great responsibility, young one." +"arcane" -> * +"responsibility" -> * +"vocation" -> "Being sorcerer is belonging to a vocation of great arcane power and responsibility." + + +"rune" -> "Sorry, I don't sell these anymore. I'm old and have to focus on more important things. Please ask my brother Sigurd next door. " +"life","fluid" -> * +"mana","fluid" -> * +"blank","rune" -> * +"spellbook" -> * + + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Take care out there, young one. ", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> " Come on, young one, you already know this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need some more money." +Topic=3,"yes" -> "IT BE! Now look into your spellbook for the words of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +Topic=4,"yes",CountMoney>=Price -> "Here, young one.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Not enough money." +Topic=4 -> "As you wish." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=7 +"vial" -> * +"flask" -> * + +Topic=6,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=6,"yes" -> "Come back, when you have enough money." +Topic=6 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=7,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=7,"yes" -> "You don't have any empty vials." +Topic=7 -> "Hmm, but please keep Tibia litter free." + + +} diff --git a/app/SabrehavenServer/data/npc/eva.npc b/app/SabrehavenServer/data/npc/eva.npc new file mode 100644 index 0000000..1df93a6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/eva.npc @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eva.npc: Datenbank für die Bankangestellte Eva (Carlin) + +Name = "Eva" +Outfit = (136,96-60-95-0-0) +Home = [32325,31780,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Take a seat, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I work in this bank. I can change money for you." +"name" -> "I am Eva." +"time" -> "It is exactly %T." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/evan.npc b/app/SabrehavenServer/data/npc/evan.npc new file mode 100644 index 0000000..2b425e4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/evan.npc @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Evan" +Outfit = (132,115-87-81-76-0) +Home = [32259,32881,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello there. What are you doing in the distillery?" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a minute, %N." +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle + +"fire bug" -> Type=5467, Amount=1, Price=100, "Do you want to buy a fire bug for %P gold?", Topic=1 +%1,1<%1,"fire bug" -> Type=5467, Amount=%1, Price=100*%1, "Do you want to buy %A fire bugs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you go.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/explorer.ndb b/app/SabrehavenServer/data/npc/explorer.ndb new file mode 100644 index 0000000..5ec2a47 --- /dev/null +++ b/app/SabrehavenServer/data/npc/explorer.ndb @@ -0,0 +1,338 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-expl.ndb: Datenbank für die Explorers Society + +"job" -> "I am the local representative of the explorer society." + +"explorer", "society" -> "Our noble society is dedicated to explore the unknown. No location is too remote for our members to travel there ...", "No beast is too wild to be hunted. No treasure buried too deep to be unearthed ...", "Only the most dedicated and fearless adventurers may join our ranks." +"base" -> "Currently we maintain public bases in Port Hope and Northport." +"join",QuestValue(300)>0 -> "But you are already a member, %N" +"join",QuestValue(300)<1 -> "Do you want to join the explorer society?",topic=1 +"no", topic=1 -> "I see. Not everyone has the guts it takes to become a professional explorer." +"yes", topic=1 -> "Fine, though it takes more than a mere lip service to join our ranks. To prove your dedication to the cause you will have to acquire an item for us ...", "The mission should be simple to fulfil. For our excavations we have ordered a sturdy pickaxe in Kazordoon. You would have to seek out this trader Uzgod and get the pickaxe for us ...", "Simple enough? Are you interested in this task?", topic=2 + +"no", topic=2 -> "I see. Not everyone has the guts it takes to become a professional explorer." +"yes", topic=2 -> "We will see if you can handle this simple task. Get the pickaxe from Uzgod in Kazordoon and bring it to one of our bases. Report there about the pickaxe.", SetQuestValue(325,1) + +"pickaxe",QuestValue(300)<1 -> Type=4845,Amount=1,"Did you get the requested pickaxe from Uzgod in Kazordoon?",topic=9 +################# +"no", topic=9 -> "Get that special pickaxe from Uzgod in Kazordoon." +"yes", topic=9,Count(Type) "Get that special pickaxe from Uzgod in Kazordoon." +"yes", topic=9,Count(Type)>=Amount -> "Excellent, you brought just the tool we need! Of course it was only a simple task. However ...", "I officially welcome you to the explorer society. From now on you can ask for missions to improve your rank.",Delete(Type), SetQuestValue(300,1) + +"rank",QuestValue(300)>0,QuestValue(300)<4 -> "You are a novice of the explorer society." +"rank",QuestValue(300)>3,QuestValue(300)<7 -> "You are a journeyman of the explorer society." +"rank",QuestValue(300)>6,QuestValue(300)<9 -> "You are a relic hunter of the explorer society." +"rank",QuestValue(300)>8 -> "You are an explorer of the explorer society." +################## Allgemeine Missionen +"ratha",QuestValue(302)=0 -> "Ratha was a great explorer and even greater ladies' man. Sadly he never returned from a visit to the amazons. Probably he is dead ...", "The society offers a substantial reward for the retrieval of Ratha or his remains. Do you have any news about Ratha?",Type=3207,Amount=1,Price=250, topic=3 +"ratha",QuestValue(302)=1 -> "Ratha was a great explorer and even greater ladies' man. Thank you for returning his remains." + +"no",topic=3 -> "If you ever stumble across some information about Ratha, let us know." +"yes",topic=3,QuestValue(302)=0,Count(Type) "What are you talking about? You have nothing to clarify Ratha's whereabouts." +"yes",topic=3,QuestValue(302)=0,Count(Type)>=Amount -> "Poor Ratha. Thank you for returning this skull to the society. We will see to a honourable burial of Ratha.", Delete(Type), CreateMoney, SetQuestValue(302,1) + + +"brooch" -> "Our members travel to far away places and cross dangerous areas, many fall prey to enemies or the land ...", "Sometimes the personal explorer brooches can be recovered. That way we learn about the fate of our members ...", "We offer a reward for each brooch returned to us. Have you found an explorer brooch?",Type=3005,Amount=1,Price=50, topic=53 + +"no", topic=53 -> "Sometimes no news is good news." +"yes", topic=53,Count(Type)>=Amount -> "It's always a sad day when we learn about the death of a member. But at least we learnt about his fate. Thank you, here is your reward.", Delete(Type), CreateMoney +"yes", topic=53,Count(Type) "You don't have any lost brooch with you. This is not a topic to make fun of." + +"smith", "hammer" -> "The explorer society is looking for a genuine giant smith hammer for our collection. It is rumoured the cyclopses of the Plains of Havoc might be using one. Did you by chance obtain such a hammer?",Type=3208,Amount=1,Price=250, topic=4 +"smith", "hammer" ,QuestValue(303)=1 -> "The explorer society was looking for a genuine giant smith hammer until you brought us one. Thank you again." + +"yes", topic=4,Count(Type)>=Amount -> "Marvellous! You brought a giant smith hammer for the explorer society!", Delete(Type), CreateMoney, SetQuestValue(303,1) +"yes", topic=4,Count(Type) "This is no giant smith hammer." +"no", topic=4 -> "Just as you like. But think about the reward!" + +####### + +"hydra","egg" -> "The examination of hydra eggs is a valuable source of information. We buy hydra eggs for 500 gold. Are you interested in selling one?",Type=4839,Amount=1,Price=500, topic=40 + +"sell",%1,1<%1,"hydra","egg" -> Type=4839, Amount=%1, Price=500*%1, "The examination of hydra eggs is a valuable source of information. We buy hydra eggs for 500 gold each. Do you want to sell %A hydra eggs for %P gold?", Topic=40 + +"no",topic=40 -> "If you ever aquire a hydra egg, bring it here." +"yes",topic=40,Count(Type) "What are you talking about? You don't own that many hydra eggs." +"yes",topic=40,Count(Type)>=Amount -> "Thank you in the name of science.", Delete(Type), CreateMoney +#### +"scroll","lizard" -> "The examination of scrolls with lizard writings is a valuable source of information. We buy such scrolls for 500 gold. Are you interested to sell one?",Type=4831,Amount=1,Price=500, topic=41 +"parchment" -> * + +"no",topic=41 -> "If you ever aquire a parchment with lizard writings, bring it here." +"yes",topic=41,Count(Type) "What are you talking about? You own no parchment with lizard writings at all." +"yes",topic=41,Count(Type)>=Amount -> "Thank you in the name of science.", Delete(Type), CreateMoney + +###### + + +######################## Missionen + +"mission",QuestValue(300)>0,QuestValue(300)<4 -> "The missions available for your rank are the butterfly hunt, plant collection and ice delivery." + +"butterfly", "hunt",QuestValue(300)>0,QuestValue(304)=0 -> "The mission asks you to collect some species of butterflies, are you interested?", topic=5 +"no", topic=5 -> "Perhaps another mission suits you more." +"yes", topic=5 -> "This preparation kit will allow you to collect a purple butterfly you have killed ...", "Just use it on the fresh corpse of a purple butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,1) + +"butterfly", "hunt",QuestValue(304)=1 -> "Did you acquire the purple butterfly we are looking for?",Type=4865,Amount=1, topic=6 +"no", topic=6 -> "Then go and look for one." +"yes", topic=6,Count(Type) "I can't see a purple butterfly. Perhaps you lost it somewhere." +"yes", topic=6,Count(Type)>=Amount -> "A little bit battered but it will do. Thank you! If you think you are ready, ask for another butterfly hunt.", Delete(Type), SetQuestValue(304,2) + + +"butterfly", "hunt",QuestValue(304)=2 -> "This preparation kit will allow you to collect a blue butterfly you have killed ...", "Just use it on the fresh corpse of a blue butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,3) + +"butterfly", "hunt",QuestValue(304)=3 -> "Did you acquire the blue butterfly we are looking for?",Type=4866,Amount=1,topic=7 +"no",topic=7 -> "Then go and look for one." +"yes",topic=7,Count(Type) "I can't see a blue butterfly. Perhaps you lost it somewhere." +"yes",topic=7,Count(Type)>=Amount -> "Again I think it will do. Thank you! If you think you are ready, ask for another butterfly hunt.", Delete(Type), SetQuestValue(304,4) + + +"butterfly", "hunt",QuestValue(304)=4 -> "This preparation kit will allow you to collect a red butterfly you have killed ...", "Just use it on the fresh corpse of a red butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,5) + +"butterfly", "hunt",QuestValue(304)=5 -> "Did you acquire the red butterfly we are looking for?",Type=4864,Amount=1, topic=8 +"no", topic=8 -> "Then go and look for one." +"yes", topic=8,Count(Type) "I can't see a red butterfly. Perhaps you lost it somewhere." +"yes", topic=8,Count(Type)>=Amount -> "That is an extraordinary species you have brought. Thank you! That was the last butterfly we needed.", Delete(Type), SetQuestValue(304,6),SetQuestValue(300,QuestValue(300)+1) +"butterfly", "hunt",QuestValue(304)=6 -> "You have already finished the butterfly hunt. Of course you can always ask me what missions are available." + +#### topic 9 ist verwendet + +"plant", "collection",QuestValue(300)>0,QuestValue(305)=0 -> "In this mission we require you to get us some plant samples from Tiquandan plants. Would you like to fulfil this mission?", topic=10 +"no", topic=10 -> "Perhaps another mission suits you more." +"yes", topic=10 -> "Fine! Here take this botanist's container. Use it on a jungle bells plant to collect a sample for us. Report about your plant collection when you have been successful.", Create(4867),SetQuestValue(305,1) + +"plant", "collection",QuestValue(305)=1 -> "Did you acquire the sample of the jungle bells plant we are looking for?",Type=4868,Amount=1, topic=11 +"no", topic=11 -> "Then go and look for one." +"yes", topic=11,Count(Type) "Sorry, you don't have a useful sample." +"yes", topic=11,Count(Type)>=Amount -> "I see. It seems you've got some quite useful sample by sheer luck. Thank you! Just tell me when you are ready to continue with the plant collection.", Delete(Type),SetQuestValue(305,2) + +"plant", "collection",QuestValue(305)=2 -> "Use this botanist's container on a witches cauldron to collect a sample for us. Bring it here and report about your plant collection.", Create(4867),SetQuestValue(305,3) + +"plant", "collection",QuestValue(305)=3 -> "Did you acquire the sample of the witches cauldron we are looking for?",Type=4870,Amount=1, topic=12 +"no", topic=12 -> "Then go and look for one." +"yes", topic=12,Count(Type) "Sorry, you don't have any useful sample." +"yes", topic=12,Count(Type)>=Amount -> "Ah, finally. I started to wonder what took you so long. But thank you! Another fine sample, indeed. Just tell me when you are ready to continue with the plant collection.", Delete(Type),SetQuestValue(305,4) + +"plant", "collection",QuestValue(305)=4 -> "Use this botanist's container on a giant jungle rose to obtain a sample for us. Bring it here and report about your plant collection.", Create(4867),SetQuestValue(305,5) + +"plant", "collection",QuestValue(305)=5 -> "Did you acquire the sample of the giant jungle rose we are looking for?",Type=4869,Amount=1, topic=13 +"no", topic=13 -> "Then go and look for one. Keep in mind we need samples of the giant jungle rose, not the small one." +"yes", topic=13,Count(Type) "Sorry you don't have any useful sample. Perhaps you did not use the botanist's container on a giant jungle rose as requested, but on a small one." +"yes", topic=13,Count(Type)>=Amount -> "What a lovely sample! With that you have finished your plant collection missions.", Delete(Type),SetQuestValue(305,6),SetQuestValue(300,QuestValue(300)+1) + + +"plant", "collection",QuestValue(305)=6 -> "You have already finished the plant collection missions. Of course you can always ask me what missions are available." + +"ice", "delivery",QuestValue(300)>0,QuestValue(306)=0 -> "Our finest minds came up with the theory that deep beneath the ice island of Folda ice can be found that is ancient. To prove this theory we would need a sample of the aforesaid ice ...", "Of course the ice melts away quickly so you would need to hurry to bring it here ...", "Would you like to accept this mission?",topic=14 + +"no",topic=14 -> "Perhaps another mission suits you more." +"yes",topic=14 -> "So listen please: Take this ice pick and use it on a block of ice in the caves beneath Folda. Get some ice and bring it here as fast as you can ...", "Should the ice melt away, report on your ice delivery mission anyway. I will then tell you if the time is right to start another mission.", Create(4872),SetQuestValue(306,1) +"ice", "delivery",QuestValue(306)=1 -> "Did you get the ice we are looking for?",Type=4837,Amount=1,topic=15 +"no",topic=15 -> "Did it melt away?",topic=16 + "no",topic=16 -> "Then don't waste your time and go to the caves on Folda to get some ice." + "yes",topic=16,QuestValue(307)=0 -> "I think you are wrong, just try to get that ice as you were told." + "yes",topic=16,QuestValue(307)=1,Count(Type)>Amount -> "What are you talking about, I can see you still have some ice on you." + "yes",topic=16,QuestValue(307)=1,Count(Type) "*Sigh* I think the time is right to grant you another chance to get that ice. Hurry up this time.",SetQuestValue(307,0), EffectOpp(13) + +"yes",topic=15,Count(Type) "Sorry, you don't have any ice on you." +"yes",topic=15,Count(Type)>=Amount,QuestValue(307)=0 -> "This ice looks odd. I don't think it's from Folda. Make sure to travel there to get the ice we are looking for." +"yes",topic=15,Count(Type)>=Amount,QuestValue(307)=1 -> "Just in time. Sadly not much ice is left over but it will do. Thank you again.",SetQuestValue(306,2),SetQuestValue(300,QuestValue(300)+1), Delete(Type) + +"ice", "delivery",QuestValue(306)=2 -> "You already brought us the ice sample that we needed. Of course you can always ask me what missions are available." + +######### +# Debug # +######### + +#"advancement", QuestValue(300)>0, QuestValue(304)<6 -> "You have not even finished your butterfly hunt. Perform some missions and ask again." +#"advancement", QuestValue(300)>0, QuestValue(304)=6 -> "So you think you are worthy of an advancement?",topic=55 +#"no",topic=55 -> "Then stop bothering me with it." +#"yes",topic=55, QuestValue(305)<6 -> "You have not even finished your plant collection. Perform some missions and ask again." +#"yes",topic=55, QuestValue(305)=6 -> "Well, you performed some simple tasks at least but do you realy think you deserve an advancement?",Topic=56 +#"no",topic=56 -> "Then stop bothering me with it." +#"yes",topic=56, QuestValue(306)<2 -> "First finish the ice delivery mission. Then we might talk about advancement." +#"yes",topic=56, QuestValue(306)=2, QuestValue(300)>3 -> "Sorry but your rank mirrors your performance perfectly." +#"yes",topic=56, QuestValue(306)=2, QuestValue(300)<4 -> "This is odd indeed. I must have confused something in my papers, sorry. I grant you the rank of a journeyman.",SetQuestValue(300,4) + + +######################## +# NEUE MISSIONEN (4-6) # +######################## + + +"mission",QuestValue(300)>3,QuestValue(300)<7 -> "The missions available for your rank are lizard urn, beholder secrets and orc powder." + +"lizard", "urn",QuestValue(308)=0,QuestValue(300)>3 -> "The explorer society would like to acquire an ancient urn which is some sort of relic to the lizard people of Tiquanda. Would you like to accept this mission?",topic=17 + +"no",topic=17 -> "Perhaps another mission suits you more." +"yes",topic=17 -> "You have indeed the spirit of an adventurer! In the south-east of Tiquanda is a small settlement of the lizard people ...", "Beneath the newly constructed temple there, the lizards hide the said urn. Our attempts to acquire this item were without success ...", "Perhaps you are more successful.",SetQuestValue(308,1) + +"lizard", "urn",QuestValue(308)=1 -> "Did you manage to get the ancient urn?",Type=4847,Amount=1,topic=18 + +"no",topic=18 -> "It must be somewhere beneath the newly constructed lizard temple in the south-east of Tiquanda." +"yes",topic=18,Count(Type) "Sorry, you don't have the urn. It has to be somewhere beneath the newly constructed lizard temple in the south-east of Tiquanda." +"yes",topic=18,Count(Type)>=Amount -> "Yes, that is the prized relic we have been looking for so long. You did a great job, thank you.", Delete(Type),SetQuestValue(308,2),SetQuestValue(300,QuestValue(300)+1) + +"lizard", "urn",QuestValue(308)=2 -> "You already retrieved the urn for us. Of course you can always ask me what missions are available." + +###################### + +"beholder", "secret",QuestValue(309)=0,QuestValue(300)>3 -> "We want to learn more about the ancient race of beholders. We believe the black pyramid north east of Darashia was originally built by them ...", "We ask you to explore the ruins of the black pyramid and look for any signs that prove our theory. You might probably find some document with the numeric beholder language ...", "That would be sufficient proof. Would you like to accept this mission?",topic=19 + +"no",topic=19 -> "Perhaps another mission suits you more." +"yes",topic=19 -> "Excellent! So travel to the city of Darashia and then head north-east for the pyramid ...", "If any documents are left, you probably find them in the catacombs beneath. Good luck!",SetQuestValue(309,1) + +"beholder", "secret",QuestValue(309)=1 -> "Have you found any proof that the pyramid was built by beholders?",Type=4846,Amount=1,topic=20 +"no",topic=20 -> "We are sure there is some document left. Probably in the deepest catacombs beneath the black pyramid." +"yes",topic=20,Count(Type) "Sorry, whatever you have found is no true proof for our theory. Please return to the black pyramid and explore it carefully." +"yes",topic=20,Count(Type)>=Amount -> "You did it! Excellent! The scientific world will be shaken by this discovery!", Delete(Type),SetQuestValue(309,2),SetQuestValue(300,QuestValue(300)+1) + + +"beholder", "secret",QuestValue(309)=2 -> "You already recovered the scroll. Of course you can always ask me what missions are available." + +##################### + +"orc", "powder",QuestValue(310)=0,QuestValue(300)>3 -> "It is commonly known that orcs of Uldereks Rock use some sort of powder to increase the fierceness of their war wolves and berserkers ...", "What we do not know are the ingredients of this powder and its effect on humans ...", "So we would like you to get a sample of the aforesaid powder. Do you want to accept this mission?", topic=21 + +"no",topic=21 -> "Perhaps another mission suits you more." +"yes",topic=21 -> "You are a brave soul. As far as we can tell, the orcs maintain some sort of training facility in some hill in the north-east of their city ...", "There you should find lots of their war wolves and hopefully also some of the orcish powder. Good luck!",SetQuestValue(310,1) + +"orc", "powder",QuestValue(310)=1 -> "Did you acquire some of the orcish powder?",Type=4838,Amount=1,topic=34 +"no",topic=34 -> "Make sure to search in the hill they use to raise and train their war wolves." +"yes",topic=34,Count(Type) "Sorry, you have nothing with you that would fit the descriptions we got of the powder." +"yes",topic=34,Count(Type)>=Amount -> "You really got it? Amazing! Thank you for your efforts.", Delete(Type),SetQuestValue(310,2),SetQuestValue(300,QuestValue(300)+1) +"orc", "powder",QuestValue(310)=2 -> "You already brought us some orcish powder. Of course you can always ask me what missions are available." + +########################## +# NEUE MISSIONEN (7-9) # +########################## + +"mission",QuestValue(300)>6,QuestValue(300)<10 -> "The missions available for your rank are elven poetry, memory stone and rune writings." + +"elven", "poetry",QuestValue(311)=0,QuestValue(300)>6 -> "Some high ranking members would like to study elven poetry. They want the rare book 'Songs of the Forest' ...", "For sure someone in Ab'Dendriel will own a copy. So you would just have to ask around there. Are you willing to accept this mission?",topic=22 +"no",topic=22 -> "Perhaps another mission suits you more." +"yes",topic=22 -> "Excellent. This mission is easy but nonetheless vital. Travel to Ab'Dendriel and get the book.",SetQuestValue(311,1) + + +"song", "forest",QuestValue(311)=1 -> "Did you acquire a copy of 'Songs of the Forest' for us?",Type=4844,Amount=1,topic=23 +"elven", "poetry",QuestValue(311)=1 -> * +"book",QuestValue(311)=1 -> * +"no",topic=23 -> "Then try harder. Someone might own it. If you lost a copy, ask around again." +"yes",topic=23,Count(Type) "Whatever you thought you acquired, it's not the book we are looking for! If you lost the copy, ask around again." +"yes",topic=23,Count(Type)>=Amount,QuestValue(312)=0 -> "It can easily be seen that this book was forged by a dwarf. Try to get a real copy somewhere." +"yes",topic=23,Count(Type)>=Amount,QuestValue(312)=1 -> "Let me have a look! Yes, that's what we wanted. A copy of 'Songs of the Forest'. I won't ask any questions about those bloodstains.", Delete(Type),SetQuestValue(311,2),SetQuestValue(300,QuestValue(300)+1) + +"elven", "poetry",QuestValue(311)=2 -> "You already pleased our leadership by acquiring a copy of that book. Of course you can always ask me what missions are available." + +########################## + +"memory", "stone",QuestValue(313)=0,QuestValue(300)>6 -> "We acquired some knowledge about special magic stones. Some lost civilisations used it to store knowledge and lore, just like we use books ...", "The wisdom in such stones must be immense, but so are the dangers faced by every person who tries to obtain one...", "As far as we know the ruins found in the north-west of Edron were once inhabited by beings who used such stones. Do you have the heart to go there and to get us such a stone?",topic=24 +"no",topic=24 -> "Perhaps another mission suits you more." +"yes",topic=24 -> "In the ruins of north-western Edron you should be able to find a memory stone. Good luck.",SetQuestValue(313,1) + + +"memory", "stone",QuestValue(313)=1 -> "Were you able to acquire a memory stone for our society?",Type=4841,Amount=1,topic=25 +"no",topic=25 -> "Try harder. We are sure there are memory stones left in the north-western dungeons of Edron." +"yes",topic=25,Count(Type) "You don't have any memory stone!" +"yes",topic=25,Count(Type)>=Amount,QuestValue(314)=0 -> "This memory stone looks damaged. Probably you bought it from some suspicious individual. Travel to Edron and get one on your own." +"yes",topic=25,Count(Type)>=Amount,QuestValue(314)=1 -> "A flawless memory stone! Incredible! It will take years even to figure out how it works but what an opportunity for science, thank you!", Delete(Type),SetQuestValue(313,2),SetQuestValue(300,QuestValue(300)+1) + + +"memory", "stone",QuestValue(313)=2 -> "You already brought us a memory stone. It will take years to make it work and probably decades to decipher and understand the knowledge the stone contains." + +######################### + +"rune", "writing",QuestValue(315)=0,QuestValue(300)>6 -> "We would like to study some ancient runes that were used by the lizard race. We suspect some relation of the lizards to the founders of Ankrahmun ...", "Somewhere under the ape infested city of Banuta, one can find dungeons that were once inhabited by lizards ...", "Look there for an atypical structure that would rather fit to Ankrahmun and its tombs. Copy the runes you will find on this structure ...", "Are you up to that challenge?",topic=26 +"no",topic=26 -> "Perhaps you are interested at another time." +"yes",topic=26 -> "Excellent! Here, take this tracing paper and use it on the object you will find there to create a copy of the ancient runes.", Create(4842),SetQuestValue(315,1) + +"rune", "writing",QuestValue(315)=1 -> "Did you create a copy of the ancient runes as requested?",Type=4843,Amount=1,topic=27 +"no",topic=27 -> "Please remember, somewhere in the dungeons beneath Banuta has to be some structure that seemingly does not belong there. Use the copy paper on it." +"yes",topic=27,Count(Type) "You don't have any copy of the runes that we need!" +"yes",topic=27,Count(Type)>=Amount,QuestValue(316)=0 -> "This copy is ruined by sweat and blood, sorry. Travel to Banuta and make sure to make a clean copy." +"yes",topic=27,Count(Type)>=Amount,QuestValue(316)=1 -> "It's a bit wrinkled but it will do. Thanks again.", Delete(Type),SetQuestValue(315,2),SetQuestValue(300,QuestValue(300)+1) + +"rune", "writing",QuestValue(315)=2 -> "You already brought us a copy of those runes which gave us some interesting insights." + +######################## + +"mission",QuestValue(300)=10,QuestValue(317)=0 -> "The explorer society needs a great deal of help in the research of astral travel. Are you willing to help?", topic=28 +"no", topic=28 -> "Perhaps you are interested at another time" +"yes" , topic=28 -> "Fine. The society is looking for new means to travel. Some of our most brilliant minds have some theories about astral travel that they want to research further ...", "Therefore we need you to collect some ectoplasm from the corpse of a ghost. We will supply you with a collector that you can use on the body of a slain ghost ...", "Do you think you are ready for that mission?", topic=29 +"no", topic=29 -> "Perhaps you are interested some other time." +"yes", topic=29 -> "Good! Take this container and use it on a ghost that was recently slain. Return with the collected ectoplasm and hand me that container ...", "Don't lose the container. They are expensive!", Create(4852),SetQuestValue(317,1) + +"ectoplasm",QuestValue(317)=1 -> "Do you have some collected ectoplasm with you?",Type=4853,Amount=1,topic=30 +"container",QuestValue(317)=1 -> * +"mission",QuestValue(317)=1 -> * +"no",topic=30 -> "Just use the container on a slain ghost. Make sure it lost its unholy mockery of life before you fulfil your task. Be quick, a partly decomposed corpse will not contain enough ectoplasm." +"yes",topic=30,Count(Type) "Sorry, but you have no ectoplasm at all!" +"yes",topic=30,Count(Type)>=Amount -> "Phew, I had no idea that ectoplasm would smell that ... oh, it's you, well, sorry. Thank you for the ectoplasm.", Delete(Type),SetQuestValue(317,2),SetQuestValue(300,11) + +"ectoplasm",QuestValue(317)=2 -> "You already brought us enough ectoplasm for our research." + +######################### + +"mission",QuestValue(300)=11,QuestValue(318)=0 -> "The research on ectoplasm makes good progress. Now we need some spectral article. Our scientists think a spectral dress would be a perfect object for their studies ...", "The bad news is that the only source to got such a dress is the queen of the banshees. Do you dare to seek her out?",topic=31 + +"no",topic=31 -> "Perhaps you are interested in it some other time." +"yes",topic=31 -> "That is quite courageous. We know, it's much we are asking for. The queen of the banshees lives in the so called Ghostlands, south west of Carlin. It is rumoured that her lair is located in the deepest dungeons beneath that cursed place ...", "Any violence will probably be futile, you will have to negotiate with her. Try to get a spectral dress from her. Good luck.",SetQuestValue(318,1) + +"spectral", "dress",QuestValue(318)=1 -> "Have you acquired the spectral dress we need?",Type=4836,Amount=1,topic=32 +"mission",QuestValue(318)=1 -> * + +"no", topic=32 -> "Remember, the queen of the banshees lives in the so called Ghostlands, south west of Carlin. Her lair is rumoured to be in the deepest dungeons beneath that cursed place ...", "Any violence will probably be futile and you will have to negotiate with her. Try to get a spectral dress from her." +"yes", topic=32,Count(Type) "Sorry, you don't have a spectral dress!" +"yes", topic=32,Count(Type)>=Amount,QuestValue(319)=0 -> "Sorry, this dress is infested with spectral moths. Get a spectral dress from the banshee queen." +"yes", topic=32,Count(Type)>=Amount,QuestValue(319)=1 -> "Just in time! With this spectral article we can start the final phase of our research.", Delete(Type),SetQuestValue(318,2),SetQuestValue(300,12) + +"spectral",QuestValue(318)=2 -> "You provided us with a spectral dress and we are ready for the final stages of our research project." + + +##################### + +"portals",QuestValue(320)=5 -> "Sorry, you did not charge both floor tiles as requested." +"mission",QuestValue(320)=5 -> * + +"portals",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=0 -> Amount=6, Type=5021,"Both carvings are now charged and harmonised. In theory you should be able to travel in zero time from one base to the other ...", "However, you will need to have an orichalcum pearl in your possession to use it as power source. It will be destroyed during the process. I will give you 6 of such pearls and you can buy new ones in our bases ...", "In addition, you need to be a premium explorer to use the astral travel ...", "And remember: it's a small teleport for you, but a big teleport for all Tibians.",SetQuestValue(323,1), Create(Type) +"mission",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=0 -> * + +"portals",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=1 -> "The portals should be ready to be used now. If you have an orichalcum pearl with you, enter the portal." +"mission",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=1 -> "There are no new missions avaliable right now." +###################### +"orichalcum" -> Amount=1, Type=5021, Price=80, "Do you want to buy an orichalcum pearl for %P gold?", Topic=51 + +Topic=51,"yes",CountMoney>=Price -> "Here, use it wisely.", DeleteMoney, Create(Type) +Topic=51,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=51 -> "Is there anything else I can do for you?" + +%1,1<%1,"orichalcum" -> Amount=%1, Type=5021, Price=80*%1, "Do you want to buy %A orichalcum pearls for %P gold?", Topic=52 + +Topic=52,"yes",CountMoney>=Price -> "Here, use them wisely.", DeleteMoney, Create(Type) +Topic=52,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=52 -> "Is there anything else I can do for you?" + +###################### + +"botanist", "container" -> Amount=1, Type=4867, Price=500, "Do you want to buy a botanist container for %P gold?", Topic=56 +"preparation", "kit" -> Amount=1, Type=4863, Price=250, "Do you want to buy a preparation kit for %P gold?", Topic=56 +"ectoplasm", "container" -> Amount=1, Type=4852, Price=750, "Do you want to buy an ectoplasm container for %P gold?", Topic=56 + +Topic=56,"yes",CountMoney>=Price -> "Here, better don't lose it.", DeleteMoney, Create(Type) +Topic=56,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=56 -> "Is there anything else I can do for you?" + +###################### +"atlas" -> Amount=1, Type=6108, Price=150, "Do you want to buy an atlas for %P gold?", Topic=57 + +Topic=57,"yes",QuestValue(300)<5 -> "Oh, sorry but we sell atlas only for members with a special rank." +Topic=57,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=57,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=57 -> "Is there anything else I can do for you?" + +%1,1<%1,"atlas" -> Amount=%1, Type=6108, Price=150*%1, "Do you want to buy %A atlas for %P gold?", Topic=58 + +Topic=58,"yes",QuestValue(300)<5 -> "Oh, sorry but we sell atlas only for members with a special rank." +Topic=58,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=58,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=58 -> "Is there anything else I can do for you?" diff --git a/app/SabrehavenServer/data/npc/fahradin.npc b/app/SabrehavenServer/data/npc/fahradin.npc new file mode 100644 index 0000000..801ba1b --- /dev/null +++ b/app/SabrehavenServer/data/npc/fahradin.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fahradin.npc: Datenbank für den Maridzauberer Fa'hradin + +Name = "Fa'hradin" +Outfit = (80,0-0-0-0-0) +Home = [33106,32541,5] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Aaaah... what have we here. A human - interesting. And such an ugly specimen, too... All right, human %N. How can I help you?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Wait human. I'll take care of you in a minute, %N.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell, human." + +"bye" -> "Farewell, human. I will always remember you. Unless I forget you, of course.", Idle +"farewell" -> * +"name" -> "I am known as Fa'hradin." +"fa'hradin" -> "Yes, that is me. It seems you have heard my name before." +"job" -> "Well, you could say I am the wizard of the Marid. Of course, I know that all djinn are magical creatures. But let us put it this way: I am slightly better at wielding magic then your average djinn in the street." +"djinn" -> "Our race is in a deplorable state at the moment. However, it is interesting from a scientific point of view. I am really curious to see if the Efreet and the Marid are really going to develop into two completely different species..." +"efreet" -> "I have not be been able to figure out exactly why the Efreet have developed a different skin colour. ...", + "This poses an interesting scientific problem, you know. Perhaps it is a magical effect, but I have a feeling that there are other forces at work here." +"marid" -> "That is what we call ourselves. We like to think of ourselves as the true inheritors of the djinn legacy." + +"gabel" -> "He is our leader. He does have his mistakes, but then he always tries to do what he thinks is right, and I suppose that makes him a good leader." +"king" -> "Djinns do not have kings. Gabel has long abdicated the title because of his convictions, and Malor... Well, I suppose he would not refuse to take the crown, but I doubt he will ever get a chance to do so." +"malor" -> "That treacherous snake has been waiting for a chance to seize power for as long as I can remember. He and Gabel used to be as close as brothers, you know." +"mal'ouquah",QuestValue(281)=0 -> "Mal'ouquah is Malor's fortress which lies to the south. I know it well even though I have never been there myself. Insider information, you know." +"mal'ouquah",QuestValue(281)>0 -> "Mal'ouquah is Malor's fortress which lies to the south. Of course you cannot enter it through the front door. But there's also an unguarded back door in the north-west corner of the fortress..." +"ashta'daramai" -> "That is what this place is called. It is not difficult to guess that that name was not my idea." +"human" -> "You are a curious species: Weak, yet strong. Stupid, yet clever. Evil, yet good. Fascinating, really. ...", + "For thousands of years we regarded the northern continent as barbaric and wild. ...", + "And all of sudden there are roads and pastures and mighty cities. The problem with us djinn is that we always underestimate other species, especially humans." +"zathroth" -> "He created our race, but we find it hard to love him. Sometimes I think that whole war has erupted because there is something like a design flaw in us djinns, an inconsistency in the way we are. ...", + "I have never been able to put my finger on it, but it keeps me wondering..." +"tibia" -> "Eons ago when I was still young I felt the world was a place of wonder and joy. Now all I see is a badly working system full of design flaws. ...", + "Must have been the first world the gods have created. Who knows? Perhaps they have learnt from their mistakes, and they are creating a better world somewhere else?" +"daraman" -> "I have met him myself. He was a sharp thinker and a charismatic conversationalist. ...", + "I suppose he never managed to convince me quite as thoroughly as he managed to convince Gabel, but I came to admire his amazing personal integrity. ...", + "In the end I chose to follow his creed because I felt that we djinn lacked something, and I thought that perhaps Daraman had an answer." +"darashia" -> "Darashia is comparatively young. The local ruler managed to establish his own little caliphate thanks to the riches he accumulated. ...", + "As long as he continues to control the eastern trade routes Darashia will continue to flourish." +"scarab" -> "An interesting species. Oh, they are as thick as two short planks, of course, but there is definitely something magic about them. ...", + "I have not carried out many studies, however, because dissecting scarabs is a real hassle." +"edron" -> "I am not much of a traveller, but I would like to see the northern cities everbody is talking about. Perhaps one day I will do that. Oh, I will use some kind of magical disguise, of course." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That is one of the oldest human settlements in the whole of Tibia. I understand it is currently ruled by the pharaoh - some sort of undead priest-king. I am sure that must be a charming fellow." +"pharaoh" -> "Apparently the whole issue of dying in order to extend the natural life span was his idea. Those humans. You never know what they come up with next!" + +"palace" -> "The pharaoh's palace in Ankrahmun is an impressive building. At least that is how I remember it to be. ...", + "I suppose it is a little less cheerful these days, with all that undead riff-raff roaming its halls." +"ascension" -> "A fundamental part of the pharaoh's cult. I have not studied it in any detail, though." +"rah" -> "Another cornerstone of the undead pharaoh's theological theories. I do not know much more about it, I'm afraid." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "None of the mountains here has developed naturally. The whole range has been raised by using powerful magic, and a lot of this magic lingers to this very day. A great place to be a wizard, but a dangerous place to travel." +"kha'labal" -> "The Kha'labal used to be a paradise. I remember it well. The fact that it is a barren desert today might give you an idea of the things that happened during the war." +"war" -> "For a long time it seemed that the war was over for good. But now that Malor is free again he will surely kindle the flame of war again. ...", + "Damn that foolish orc. If I manage to get hold of him he will be turned into something much worse than a slime." +"melchior" -> "Ah. I remember him. A trader, was he not? I haven't seen him for a long time." +"alesar" -> "That name brings up bad memories. I never really liked him, but you just had to admire his skills at the forge. His desertion was a great loss for our cause." +"baa'leal" -> "Oh, you just have to love that djinn. We have met on the battlefield on half a dozen occasions, and he lost each single one of these battles. ...", + "To be sure, he is a great warrior, but he is also lousy general and a complete dunce. As long as he is Malor's commander-in-chief I think we're safe." +"lamp" -> "Ah yes. We djinn sleep in lamps. We have a natural ability to dematerialise, you see." +"rata'mari" -> "Ah yes. Have you seen him? One of my best works so far. Nobody will ever suspect he is in fact a transformed djinn. The only problem is I'm much better with transforming people into other forms than with transforming them back. Poor fellow." +"fa'hradin","lamp" -> "I hate to flatter myself, but that lamp was a masterpiece. Malor would have been imprisoned in it for the rest of his miserable life if it had not been for that nincompoop who calls himself an orc king. That foolish troglodyte!" + +"work",QuestValue(280)<2 -> "Looking for work, are you? Well, it's very tempting, you know, but I'm afraid we do not really employ beginners. Perhaps our cook could need a helping hand in the kitchen." +"mission",QuestValue(280)<2 -> * + +"work",QuestValue(280)=2,QuestValue(281)=0 -> "I have heard some good things about you from Bo'ques. But I don't know. ...", + "Well, all right. I do have a job for you. ...", + "In order to stay informed about our enemy's doings, we have managed to plant a spy in Mal'ouquah. ...", + "He has kept the Efreet and Malor under surveillance for quite some time. ...", + "But unfortunately, I have lost contact with him months ago. ...", + "I do not fear for his safety because his cover is foolproof, but I cannot contact him either. This is where you come in. ...", + "I need you to infiltrate Mal'ouqhah, contact our man there and get his latest spyreport. The password is PIEDPIPER. Remember it well! ...", + "I do not have to add that this is a dangerous mission, do I? If you are discovered expect to be attacked! So good luck, human!", SetQuestValue(281,1) +"bo'ques",QuestValue(280)=2,QuestValue(281)=0 -> * +"mission",QuestValue(280)=2,QuestValue(281)=0 -> * + +"work",QuestValue(281)=1 -> "Did you already retrieve the spyreport?", Topic=1 +"mission",QuestValue(281)=1 -> * +"report",QuestValue(281)=1 -> * +"spy",QuestValue(281)=1 -> * + +Topic=1,"yes",Count(3232)=1,! -> "You really have made it? You have the report? How come you did not get slaughtered? I must say I'm impressed. Your race will never cease to surprise me. ...", + "Well, let's see. ...", + "I think I need to talk to Gabel about this. I am sure he will know what to do. Perhaps you should have a word with him, too.", Amount=1, Delete(3232), SetQuestValue(281,2) +Topic=1 -> "Don't waste any more time. We need the spyreport of our man in Mal'ouquah as soon as possible! ...", + "Also don't forget the password to contact our man: PIEDPIPER!" + +"work",QuestValue(281)=2 -> "Did you already talk to Gabel about the report? I think he will have further instructions for you." +"mission",QuestValue(281)=2 -> * +"report",QuestValue(281)=2 -> * +"spy",QuestValue(281)=2 -> * +} diff --git a/app/SabrehavenServer/data/npc/falk.npc b/app/SabrehavenServer/data/npc/falk.npc new file mode 100644 index 0000000..ca9c432 --- /dev/null +++ b/app/SabrehavenServer/data/npc/falk.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# falk.npc: Datenbank für den Wachmann/Fremdenführer Falk + +Name = "Falk" +Outfit = (131,76-11-11-76-0) +Home = [33190,31777,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "LONG LIVE KING TIBIANUS! Welcome to the isle of Edron!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I am busy?!." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "Sorry, that's confidential." +"how","are","you"-> "I'm well prepared for my duty." +"sell" -> "Visit the shopkeepers to buy their fine wares." +"king" -> "LONG LIVE THE KING!" +"leader" -> * +"name" -> "I'm Lieutenant Falk." +"job" -> "I'm the Edron harbour guard. I protect Edron castle and inform visitors about this building and Edron itself." +"army" -> "The local army consists only of the Knights of Banor's Blood." +"guard" -> * +"battlegroup" -> "There are the Dogs of War, the Red Guards, and the Silver Guards." +"castle" -> "The Edron castle is the home of many shops, a tavern, a bank, a depot, a post office and the temple of Banor's blood." +"subject" -> "We all live under the benevolent guidance of our king." +"shop" -> "The shops are on the eastern side of the castle. Upstairs you'll find a tailor, a blacksmith, and an equipment store." +"tavern" -> "The tavern is called the Horn of Plenty, and it's located upstairs in the southwest corner of the castle." +"bank" -> "You'll find the bank in the southwest of the castle. Look for Ebenizer, you can't miss him." +"post" -> "The post office, run by the lovely Chrystal is in the southwest corner of the castle, near the Royal Bank." +"temple" -> "The temple can be found underground, in the southeast corner of the castle. There you can become a citizen of Edron" +"citizen" -> * +"Edron" -> "The mysterious isle has many secrets and sights outside the castle. The areas of interest are in the west, the southwest, the north, and the northwest." +"southwest" -> "There are rumours of orc buildings in the southeast. They say some daring fellows found a passage to this area in an old cavern beneath the Edron flats." +"northwest" -> "Don't even think about going there. Renegade Knights of Banor's Blood went there to unearth forbidden secrets in an ancient ruin." +"north" -> "In the north, there is an ancient city of cyclopses, called the cyclopolis. They are wary of us, but trade with servants of evil from any known race." +"west" -> "There are rumours of two tribes of minor monsters who battle each other for dominance over the area. Not worth to crawl the sewers to get there." +"city" -> "In the city there is a furniture store and a jeweller. The Noodles Academy and the cemetary are outside." +"academy" -> "The Noodles Academy of the magic arts is in the east of Edron city." +"cemetary" -> "The cemetary is north of the hamlet of Stonehome, which is at the east coast, northeast of Edron city. The cemetary is rumoured to be haunted." +"work" -> "Explore the isle and destroy any enemy forces encountered. The honor shall be your reward." +"mission" -> * +"quest" -> * + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/app/SabrehavenServer/data/npc/faluae.npc b/app/SabrehavenServer/data/npc/faluae.npc new file mode 100644 index 0000000..9807987 --- /dev/null +++ b/app/SabrehavenServer/data/npc/faluae.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# faluae.npc: Datenbank für die Deraisim-Anführerin Faluae (Elfenstadt) + +Name = "Faluae" +Outfit = (62,0-0-0-0-0) +Home = [32660,31673,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am busy now." +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the spokesperson of the Deraisim caste." +"name" -> "I am known as Faluae Ethrathil." +"time" -> "Sorry, I don't know." + +"carlin" -> "For a human city Carlin is not that bad. Its still a scar in the natural enviroment of course." +"thais" -> "Thais is fa away and that is a good thing." +"venore" -> "I don't appreciate that my people buy that much human wares but there is little I can do about it." +"roderick" -> "He is the thaian ambassador." +"olrik" -> "He is the assistant of the thaian ambassador." + +"king" -> "I know nothing about kings." +"elves" -> "The elves are split in three castes: the Deraisim, the Kuridai, and the Cenath." +"dwarfs" -> "Funny little people sometimes. But their tunnels are harmful to the enviroment." +"humans" -> "I don't like their stone cities and their stench." +"troll" -> "I still can't stand the thought of the Kuridai keeping these disgusting creatures in our settlement." +"army" -> "I don't understand what you mean." +"cenath" -> "They think they are so wise but they lost the ability to adore the simple things." +"kuridai" -> "They are paranoid, what makes them so aggressive." +"deraisim" -> "We only stay here to keep our people together. We hunt for them, provide them with food, and scout the area." +"abdaisim" -> "Our lost brothers and sisters. Oh, we miss them so much." +"teshial" -> "I can only hope they will return one day." +"ferumbras" -> "I hope this fallen servant of evil will never find us." +"crunor" -> "I praise the Great Tree and Mother Earth, who gave birth to all life, and Nera, the celestial paladin." +"nera" -> "The lady of spring, helper of Crunor and Earth itself." +"excalibug" -> "What is that?" +"news" -> "Sorry, the only news I have concern the growth of plants and the coming and going of animal herds." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"magic" -> "I learned a little about some minor spells." +"druid" -> "Druids are very close to Crunor." +"sorcerer" -> "They are so ... destructive." +"spellbook" -> "Sorry, I have none on me." +"spell" -> "I could teach you the spells of 'Light', 'Great Light', 'Food', and 'Find Person'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +"find","person" -> "I'm sorry, but this spell is only for druids and paladins." +"light" -> * +"food" -> * +"great","light" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/app/SabrehavenServer/data/npc/feizuhl.npc b/app/SabrehavenServer/data/npc/feizuhl.npc new file mode 100644 index 0000000..b462079 --- /dev/null +++ b/app/SabrehavenServer/data/npc/feizuhl.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# feizuhl.npc: Möbelverkäufer Feizuhl in Ankrahmun + +Name = "Feizuhl" +Outfit = (133,98-116-43-95-0) +Home = [33066,32886,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, mourned %N.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell furniture both to the mourned and the enlightened." +"shop" -> * +"name" -> "I'm the mourned Feizuhl, pilgrim." +"time" -> "It is %T right now." +"thanks" -> "It was a pleasure, pilgrim." +"thank","you" -> * + +"darama" -> "This is the continent of my birth, my death and also of my ascension, if I learn enough in my mortal days." +"darashia" -> "If they would only see the light and follow the way of ascension. Thrice mourned be they." +"daraman" -> "The false prophet lead his people into damnation. Mourned shall he be." +"ankrahmun" -> "Our city is old. Older even then our beloved pharaoh." +"city" -> * + +"pharaoh" -> "Our pharaoh holds the key to our ascension. Praised be our pharaoh." +"arkhothep" -> * +"mortality" -> "Only if we leave mortality behind will we attain ascension." + +"ascension" -> "The ascension to salvation and perhaps even to divine status." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the union of the three parts that were never meant to be bound together." +"Akh" -> "The Akh is our vulnerable, ageing flesh." + +"undead" -> "Undeath is an important step towards ascension." +"undeath" -> * +"Rah" -> "The Rah is our spiritual essence." +"uthun" -> "The Uthun is our memory. Call it personality if you like." +"mourn" -> "We are mortals and thus to be mourned, for while we are trapped in this frail form we are excluded from enlightenment and ascension." + +"arena" -> "Look for the arena in the heart of our city." +"palace" -> "The palace is located in the centre of our city, south of the arena." +"temple" -> "Look for the temple in the south-eastern section of Ankrahmun." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/fenbala.npc b/app/SabrehavenServer/data/npc/fenbala.npc new file mode 100644 index 0000000..263b991 --- /dev/null +++ b/app/SabrehavenServer/data/npc/fenbala.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fenbala.npc: Datenbank für die Wachfrau Fenbala + +Name = "Fenbala" +Outfit = (139,77-52-64-115-0) +Home = [32320,31755,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hail$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"salutations$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","queen",! -> "Wait for your audience!" +BUSY,"hail$","queen",! -> "Wait for your audience!" +BUSY,"salutations$","queen",! -> "Wait for your audience!" +BUSY,"hi$","queen",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/app/SabrehavenServer/data/npc/fenech.npc b/app/SabrehavenServer/data/npc/fenech.npc new file mode 100644 index 0000000..64b563b --- /dev/null +++ b/app/SabrehavenServer/data/npc/fenech.npc @@ -0,0 +1,76 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fenech.npc: Datenbank für den pyramidenhändler Fenech + +Name = "Fenech" +Outfit = (132,76-40-49-117-0) +Home = [33131,32820,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell runes, wands, rods and spellbooks." +"offer" -> * +"name" -> "I am the mourned Fenech." +"time" -> "Buy a watch on the bazar." +"temple" -> "Ask the guards for locations." +"arena" -> * +"palace" -> * + +"arkhothep" -> "Praised may he be." +"ashmunrah" -> "I don't know. Read some books." +"scarab" -> "Scarabs are dangerous. Stay away from them like I do." +"tibia" -> "I know only Ankrahmun. What else could there be?" +"carlin" -> "I don't know any foreign places or races." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> * +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> * +"elves" -> * +"elfes" -> * +"darama" -> "This is our land." +"darashia" -> "Its somewhere in the north as far as I know." +"daraman" -> "I am not a studied person. Ask someone else." +"ankrahmun" -> "Its my home and all I know." + +"ascension" -> "Ask the priest in the temple." +"Akh'rah","Uthun" -> * + +"rune" -> "I sell blank runes and spell runes." +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=2 +"bp","blank","rune" -> * + +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=2 +%1,1<%1,"bp","blank","rune" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Maybe next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/fergus.npc b/app/SabrehavenServer/data/npc/fergus.npc new file mode 100644 index 0000000..a3df5c8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/fergus.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Fergus" +Outfit = (128,0-37-116-76-0) +Home = [32221,32810,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/ferks.npc b/app/SabrehavenServer/data/npc/ferks.npc new file mode 100644 index 0000000..a393e1c --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferks.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferks.npc: Datenbank für den Bankier Ferks + +Name = "Ferks" +Outfit = (128,78-52-118-115-0) +Home = [32634,32738,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "A good day to you." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a banker, my job is to exchange coins." +"name" -> "My name is Ferks ." +"time" -> "It is exactly %T right now." +"king" -> "His royal highness might visit this little but promising community one day if we develop well." +"venore" -> "Our city and society could be a role model for all Tibians." +"thais" -> "Thais is still a power that must be taken into account. Power is not everything of course." +"carlin" -> "Carlin is nothing but a nuisance." +"edron" -> "Edron has little to offer. It's overestimated. Let those silly knights have it. In the end it will be us who clean up the mess and bring order into the chaos that will be left when they finally leave." +"jungle" -> "This jungle is dangerous. The more area we can finally cultivate, the better. Only if we chop and burn enough of it down and create new farmland, we can build a new centre of commerce." + +"tibia" -> "Sometimes I wonder why the gods don't put more effort into bringing us order but I am a banker, not a priest." + +"kazordoon" -> "Dwarves are disciplined people, which I appreciate." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Those elves are erratic and unreliable. They need to be taught some manners." +"elves" -> * +"elfs" -> * +"darama" -> "This wilderness and the dry desert need to be colonised by civilised people like us. We bring the light of order and prosperity to this continent." +"darashia" -> "This town has little that we are interested in." +"ankrahmun" -> "I have to admit that I somehow admire the strong guidance of this pharao. Most fail to acknowledge that his subjects are in need of such a strict leadership." +"ferumbras" -> "A servant of chaos. I wonder why all the Thaian military power can't stop him for good." +"excalibug" -> "A fictitious weapon is of no use at all." +"apes" -> "They mindlessly attack our settlement to steal tools and everything else they get into their hairy hands." +"lizard" -> "They live so far away in the jungle that we know only little of them. Perhaps this primitive race can be used in some way." +"dworcs" -> "Evil little bastards they are." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/ferryman1.npc b/app/SabrehavenServer/data/npc/ferryman1.npc new file mode 100644 index 0000000..a8d9ef2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferryman1.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman1.npc: Fährmann Nielson am Festland (Ice) + +Name = "Nielson" +Outfit = (129,114-113-68-67-0) +Home = [32232,31677,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Nielson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=20, "Do you want a round-trip passage to Senja for %P gold?", Topic=1 +"folda" -> Price=20, "Do you want a round-trip passage to Folda for %P gold?", Topic=2 +"vega" -> Price=20, "Do you want a round-trip passage to Vega for %P gold?", Topic=3 +"tibia" -> "This is Tibia, the continent." + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/ferryman2.npc b/app/SabrehavenServer/data/npc/ferryman2.npc new file mode 100644 index 0000000..bb458a1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferryman2.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman2.npc: Fährmann Anderson auf Senja (Ice) + +Name = "Anderson" +Outfit = (129,79-113-68-67-0) +Home = [32127,31660,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Anderson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> "This island is Senja." +"folda" -> Price=10, "Do you want a passage to Folda for %P gold?", Topic=1 +"vega" -> Price=10, "Do you want a passage to Vega for %P gold?", Topic=2 +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/ferryman3.npc b/app/SabrehavenServer/data/npc/ferryman3.npc new file mode 100644 index 0000000..d433808 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferryman3.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman3.npc: Fährmann Svenson auf Folda (Ice) + +Name = "Svenson" +Outfit = (129,77-113-68-67-0) +Home = [32044,31582,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Svenson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=10, "Do you want a passage to Senja for %P gold?", Topic=1 +"folda" -> "This island is Folda." +"vega" -> Price=10, "Do you want a passage to Vega for %P gold?", Topic=2 +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/ferryman4.npc b/app/SabrehavenServer/data/npc/ferryman4.npc new file mode 100644 index 0000000..c28f3ec --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferryman4.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman4.npc: Fährmann Carlson in Vega (Ice) + +Name = "Carlson" +Outfit = (129,19-113-68-67-0) +Home = [32029,31692,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Carlson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=10, "Do you want a passage to Senja for %P gold?", Topic=1 +"folda" -> Price=10, "Do you want a passage to Folda for %P gold?", Topic=2 +"vega" -> "This island is Vega." +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/ferrymanjack.npc b/app/SabrehavenServer/data/npc/ferrymanjack.npc new file mode 100644 index 0000000..78375a9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ferrymanjack.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferrymanJack.npc: Fährmann Jack auf der Mönchsinsel + +Name = "Captain Jack" +Outfit = (132,19-70-95-115-0) +Home = [32189,31957,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",QuestValue(220)>0,! -> "By the gods! You must be that intruder the good brothers were talking about! Begone!",Idle +BUSY,"hello$",QuestValue(220)>0,! -> * +ADDRESS,"hi$",QuestValue(220)>0,! -> * +BUSY,"hi$",QuestValue(220)>0,! -> * + +ADDRESS,"hello$",! -> "Ahoy, matey. You are lucky to catch me here. I was preparing to set sail." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "I'm m ol' Captain Jack." +"job" -> "I work as a kind of ferryman. I transport wares and travellers for the monks." +"sail" -> * +"captain" -> * +"ship" -> "All right she's small. But she's a real beauty, don't you think?" +"ferry" -> * +"wares" -> "They always need provisions from the cities, and they sell their wine there." +"traveller" -> "Sometimes pilgrims come to this place. And now and then a monk leaves the monastery for some time." +"trip" -> "Where do you want to go today?" +"passage" -> "If you have the abbot's permission, I can take you to the home of a local fisherman on the continent Tibia. We can't sail to Carlin though." +"carlin" -> "For some obscure political reason the monks never sail to Carlin or Thais directly." +"island" -> "This is the isle of the kings. All the great Tibian leaders have found their final rest here under the monastery." +"monastery" -> "The white raven monastery is a place of wisdom and contemplation, or so the monks say. Sounds like a pretty boring place to me! HAR HAR!" +"monk" -> "The order of the white raven." +"white","raven" -> "I prefer parrots. And monkeys! And snakes! HAR! HAR!" +"continent",QuestValue(62)=2 -> Price=20, "Friends of Dalbrect are my friends too! So you are looking for a passage to the continent for %P gold?", Topic=1 +"tibia",QuestValue(62)=2 -> * +"continent",QuestValue(62)=1 -> Price=20, "Do you want a passage to the continent for %P gold?", Topic=1 +"continent",QuestValue(62)<1 -> "Without the abbots permission I won't take sail you anywhere! Go and ask him for a passage first." +"tibia",QuestValue(62)=1 -> Price=20, "Tibia is the main continent. Do you want a passage to the continent for %P gold?", Topic=1 +"tibia",QuestValue(62)<1 -> "Without the abbots permission I won't take you anywhere! Go and ask him for a passage first." + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32205,31756,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Well, I'll be here if you change your mind." + +} diff --git a/app/SabrehavenServer/data/npc/fiona.npc b/app/SabrehavenServer/data/npc/fiona.npc new file mode 100644 index 0000000..9b45b85 --- /dev/null +++ b/app/SabrehavenServer/data/npc/fiona.npc @@ -0,0 +1,73 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Fiona" +Outfit = (137,115-63-95-38-0) +Home = [33282,31846,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Good day, %N. I hope you bring a lot of magical ingredients with you." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and please come back soon." + +"bye" -> "Good bye and please come back soon.", Idle +"farewell" -> * +"magical ingredients" -> "Oof, there are too many to list. Magical ingredients can sometimes be found when you defeat a monster, for example bat wings. I buy many of these things if you don't want to use them for quests." +"wares" -> * +"offer" -> * + +"sell","bat","wing" -> Type=5894, Amount=1, Price=50, "Do you want to sell a bat wing for %P gold?", Topic=1 +"sell","behemoth","claw" -> Type=5930, Amount=1, Price=2000, "Do you want to sell a behemoth claw for %P gold?", Topic=1 +"sell","beholder","eye" -> Type=5898, Amount=1, Price=80, "Do you want to sell a beholder's eye for %P gold?", Topic=1 +"sell","chicken","feather" -> Type=5890, Amount=1, Price=30, "Do you want to sell a chicken feather for %P gold?", Topic=1 +"sell","demon","dust" -> Type=5906, Amount=1, Price=300, "Do you want to sell a demon dust for %P gold?", Topic=1 +"sell","demon","horn" -> Type=5954, Amount=1, Price=1000, "Do you want to sell a demon horn for %P gold?", Topic=1 +"sell","enchanted","chicken","wing" -> Type=5891, Amount=1, Price=20000, "Do you want to sell a enchanted chicken wing for %P gold?", Topic=1 +"sell","fish","fin" -> Type=5895, Amount=1, Price=150, "Do you want to sell a fish fin for %P gold?", Topic=1 +"sell","flask","of","warrior","sweat" -> Type=5885, Amount=1, Price=10000, "Do you want to sell a flask of warrior's sweat for %P gold?", Topic=1 +"sell","green","dragon","scale" -> Type=5920, Amount=1, Price=100, "Do you want to sell a green dragon scale for %P gold?", Topic=1 +"sell","hardened","bone" -> Type=5925, Amount=1, Price=70, "Do you want to sell a hardened bone for %P gold?", Topic=1 +"sell","heaven","blossom" -> Type=3657, Amount=1, Price=50, "Do you want to sell a heaven blossom for %P gold?", Topic=1 +"sell","holy","orchid" -> Type=5922, Amount=1, Price=90, "Do you want to sell a holy orchid for %P gold?", Topic=1 +"sell","honeycomb" -> Type=5902, Amount=1, Price=40, "Do you want to sell a honeycomb for %P gold?", Topic=1 +"sell","lizard","scale" -> Type=5881, Amount=1, Price=120, "Do you want to sell a lizard scale for %P gold?", Topic=1 +"sell","magic","sulphur" -> Type=5904, Amount=1, Price=8000, "Do you want to sell a magic sulphur for %P gold?", Topic=1 +"sell","perfect","behemoth","fang" -> Type=5893, Amount=1, Price=250, "Do you want to sell a perfect behemoth fang for %P gold?", Topic=1 +"sell","red","dragon","scale" -> Type=5882, Amount=1, Price=200, "Do you want to sell a red dragon scale for %P gold?", Topic=1 +"sell","spider","silk" -> Type=5879, Amount=1, Price=100, "Do you want to sell a spider silk for %P gold?", Topic=1 +"sell","spirit","container" -> Type=5884, Amount=1, Price=40000, "Do you want to sell a spirit container for %P gold?", Topic=1 +"sell","turtle","shell" -> Type=5899, Amount=1, Price=90, "Do you want to sell a turtle shell for %P gold?", Topic=1 +"sell","vampire","dust" -> Type=5905, Amount=1, Price=100, "Do you want to sell a vampire dust for %P gold?", Topic=1 + +"sell",%1,1<%1,"bat","wing" -> Type=5894, Amount=%1, Price=50*%1, "Do you want to sell %A bat wings for %P gold?", Topic=1 +"sell",%1,1<%1,"behemoth","claw" -> Type=5930, Amount=%1, Price=2000*%1, "Do you want to sell %A behemoth claws for %P gold?", Topic=1 +"sell",%1,1<%1,"beholder","eye" -> Type=5898, Amount=%1, Price=80*%1, "Do you want to sell %A beholder's eyes for %P gold?", Topic=1 +"sell",%1,1<%1,"chicken","feather" -> Type=5890, Amount=%1, Price=30*%1, "Do you want to sell %A chicken feathers for %P gold?", Topic=1 +"sell",%1,1<%1,"demon","dust" -> Type=5906, Amount=%1, Price=300*%1, "Do you want to sell %A demon dusts for %P gold?", Topic=1 +"sell",%1,1<%1,"demon","horn" -> Type=5954, Amount=%1, Price=1000*%1, "Do you want to sell %A demon horns for %P gold?", Topic=1 +"sell",%1,1<%1,"enchanted","chicken","wing" -> Type=5891, Amount=%1, Price=20000*%1, "Do you want to sell %A enchanted chicken wings for %P gold?", Topic=1 +"sell",%1,1<%1,"fish","fin" -> Type=5895, Amount=%1, Price=150*%1, "Do you want to sell %A fish fins for %P gold?", Topic=1 +"sell",%1,1<%1,"flask","of","warrior","sweat" -> Type=5885, Amount=%1, Price=10000*%1, "Do you want to sell %A flasks of warrior's sweat for %P gold?", Topic=1 +"sell",%1,1<%1,"green","dragon","scale" -> Type=5920, Amount=%1, Price=100*%1, "Do you want to sell %A green dragon scales for %P gold?", Topic=1 +"sell",%1,1<%1,"hardened","bone" -> Type=5925, Amount=%1, Price=70*%1, "Do you want to sell %A hardened bones for %P gold?", Topic=1 +"sell",%1,1<%1,"heaven","blossom" -> Type=3657, Amount=%1, Price=50*%1, "Do you want to sell %A heaven blossoms for %P gold?", Topic=1 +"sell",%1,1<%1,"holy","orchid" -> Type=5922, Amount=%1, Price=90*%1, "Do you want to sell %A holy orchids for %P gold?", Topic=1 +"sell",%1,1<%1,"honeycomb" -> Type=5902, Amount=%1, Price=40*%1, "Do you want to sell %A honeycombs for %P gold?", Topic=1 +"sell",%1,1<%1,"lizard","scale" -> Type=5881, Amount=%1, Price=120*%1, "Do you want to sell %A lizard scales for %P gold?", Topic=1 +"sell",%1,1<%1,"magic","sulphur" -> Type=5904, Amount=%1, Price=8000*%1, "Do you want to sell %A magic sulphurs for %P gold?", Topic=1 +"sell",%1,1<%1,"perfect","behemoth","fang" -> Type=5893, Amount=%1, Price=250*%1, "Do you want to sell %A perfect behemoth fangs for %P gold?", Topic=1 +"sell",%1,1<%1,"red","dragon","scale" -> Type=5882, Amount=%1, Price=200*%1, "Do you want to sell %A red dragon scales for %P gold?", Topic=1 +"sell",%1,1<%1,"spider","silk" -> Type=5879, Amount=%1, Price=100*%1, "Do you want to sell %A spider silks for %P gold?", Topic=1 +"sell",%1,1<%1,"spirit","container" -> Type=5884, Amount=%1, Price=40000*%1, "Do you want to sell %A spirit containers for %P gold?", Topic=1 +"sell",%1,1<%1,"turtle","shell" -> Type=5899, Amount=%1, Price=90*%1, "Do you want to sell %A turtle shells for %P gold?", Topic=1 +"sell",%1,1<%1,"vampire","dust" -> Type=5905, Amount=%1, Price=100*%1, "Do you want to sell %A vampire dusts for %P gold?", Topic=1 + +Topic=1,"yes",Count(Type)>=Amount -> "Here is your money.", Delete(Type), CreateMoney +Topic=1,"yes" -> "I am sorry, but you do not have one." +Topic=1,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=1 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/frans.npc b/app/SabrehavenServer/data/npc/frans.npc new file mode 100644 index 0000000..fe8e98c --- /dev/null +++ b/app/SabrehavenServer/data/npc/frans.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# frans.npc: Datenbank für den Magieladen-Verkäufer Frans + +Name = "Frans" +Outfit = (0,3114) +Home = [32971,32080,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Beeee Greeeeted %N. What is your neeeed?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, I am veeeerry busy. Unfoooortunately you will have to waiiiit.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"name" -> "I am a FRANS." +"frans" -> "Floating ReeeeAnimated Necromantic Seeeervant ... FRANS." +"job" -> "I am selliiiing ruuuunes, wands, roooods and spellbooooooks." +"sorcerer" -> "Sorcerorssss, druidssss, they all come to ussss." +"druid" -> * +"magic" -> "Is aaaall about magic more or lesssss, isn't it?" +"vladruc" -> "Heeee is the bossss. Better don't messss with him!" +"urghain" -> * +"ferumbras" -> "Wouldn't he beeee the perfect FRANS?" +"market" -> "Yes, that's a market heeeere, smarty ... Nice to seeeee I am not the only one without a braiiiin here." +"excalibug" -> "We FRANSes don't liiiike any bugssss." + + +"offer" -> "What do youuuu think I am? A lousy barberrrr? I'm selliiiing ruuuunes and spellboooooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank ruuuuunes and spell ruuuuunes." +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do youuuu want to buy a blank ruuuune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do youuuu want to buy a spellboooook for %P gold?", Topic=1 + +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do youuuu want to buy a backpack of blank ruuuune for %P gold?", Topic=2 +"bp","blank","rune" -> * + +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do youuuu want to buy %A blank ruuuunes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do youuuu want to buy %A spellboooooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do youuuu want to buy %A backpacks of blank ruuuunes for %P gold?", Topic=2 +%1,1<%1,"bp","blank","rune" -> * + +Topic=1,"yes",CountMoney>=Price -> "And here it isssss! Almost like magiiiic, isn't it?", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, too many buttonssss and not enough gold in your pursssse." +Topic=1 -> "Better not annoy a FRANS if you don't want to deeeeal with him!" + +Topic=2,"yes",CountMoney>=Price -> "And here it isssss! Almost like magiiiic, isn't it?", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=2,"yes" -> "Sorry, too many buttonssss and not enough gold in your pursssse." +Topic=2 -> "Better not annoy a FRANS if you don't want to deeeeal with him!" + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/frederik.npc b/app/SabrehavenServer/data/npc/frederik.npc new file mode 100644 index 0000000..6a5f3e0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/frederik.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Frederik" +Outfit = (128,97-85-77-93-2) +Home = [32345,32808,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hi$",! -> "Hi there and welcome to my little magic shop." +ADDRESS,"hello$",! -> "Hi there and welcome to my little magic shop." +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N! One after the other.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "However." + +"bye" -> "Yeah, bye.", Idle +"farewell" -> * +"magic" -> "I'm selling health and mana fluids, runes, wands, rods and spellbooks." + +"rune" -> "I sell blank runes and spell runes." +"spellbook" -> Type=3059, Amount=1, Price=150, "A spellbook is a nice tool for beginners. Do you want to buy one for %P gold?",Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy one for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=3 +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=3 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?",Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A runes for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=3 +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=3 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"sell","talon" -> Type=3034, Amount=1, Price=320, "Do you want to sell one of the magic gems called talon for %P gold?", Topic=6 +"sell",%1,1<%1,"talon" -> Type=3034, Amount=%1, Price=320*%1, "Do you want to sell %A magic gems called talon for %P gold?", Topic=6 + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, maybe next time." + +Topic=3,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold for an empty vial.", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "Hmm, but next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=5 +"vial" -> * +"flask" -> * +Topic=5,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=5,"yes" -> "You don't have any empty vials." +Topic=5 -> "Hmm, but please keep Tibia litter free." + +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you do not have one." +Topic=6,"yes", Amount>1 -> "Sorry, you do not have so many." +Topic=6 -> "Maybe next time." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/frodo.npc b/app/SabrehavenServer/data/npc/frodo.npc new file mode 100644 index 0000000..d1dba92 --- /dev/null +++ b/app/SabrehavenServer/data/npc/frodo.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# frodo.npc: Datenbank fuer den Wirt Frodo + +Name = "Frodo" +Outfit = (128,58-68-109-131-0) +Home = [32356,32209,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","frodo",! -> "Hello, hello, %N. You heard about the news?" +ADDRESS,"hi$","frodo",! -> * +ADDRESS,"hello$",! -> "Welcome to Frodo's Hut. You heard about the news?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N. I'll be with you in no time.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back from time to time." + +"bye" -> "Please come back from time to time.", Idle +"farewell" -> * +"job" -> "I am the owner of this saloon. I call it Frodo's Hut. I am also selling food." +"saloon" -> * +"hut" -> "I hope you like it." +"name" -> "Just call me Frodo." +"time" -> "It is exactly %T." +"king" -> "Oh, our beloved king! Thanks to him, alcohol is so cheap." +"tibianus" -> * +"quentin" -> "He hardly visits my humble tavern." +"lynda",female -> "A very noble lady." +"lynda",male -> "Just between you and me: What a babe!" +"harkath" -> "Too disciplined to enjoy life." +"army" -> "Hehe. Great customers." +"ferumbras" -> "Uhm, do not mention him. It may scare customers away." +"general" -> "Harkath Bloodblade is the royal general." +"sam" -> "A loud neighbour, I get a lot of complaints about him." +"xodet" -> "I don't know where he gets these fluids. If I could sell them here, the hut would be crowded." +"gorn" -> "Many of his customers visit my Hut, too." +"elane" -> "Can you believe that she actually told her guildfellows that alcohol is a bad thing?" +"muriel" -> "Muriel has never visited this place." +"lungelen" -> "A sorceress, you can find her in their guild sitting befor a book - always!" +"gregor" -> "The knights have sometimes parties here after some arena fights." +"marvik" -> "Marvik seldom leaves his guildhall at all." +"bozo" -> "I am trying to hire him for an evening or two." +"baxter" -> "He's able to drink a bottle or two." +"oswald" -> "I hate him. Each of his visits here ends with a bar brawl." +"sherry" -> "The McRonalds are a nice couple. Donald is a dear friend of mine." +"mcronald" -> * +"donald" -> "He is a little shy. In his youth he dreamed to become a druid." +"lugri" -> "I overheared some conversations about his evilness. That's enough to hope, that I never ever meet him." +"excalibug" -> "Nothing more than a tale for warriors." +"thais" -> "Here in Thais is the center of Tibia." +"tibia" -> "Come on! You know that our world is called Tibia." +"carlin" -> "Many travellers tell funny stories about all the emancipated women in this northern town." +"rain","castle" -> "The king's residence has been renovated lately." +"galuna" -> "She makes excellent arrows and bows." +"hugo" -> "I think some time ago a stranger from Fibula with that name stayed here for some nights." +"todd" -> "That fellow is filthy rich. He rented a room upstairs for months in advance and always orders the best beer and wine i serve." + +"news" -> "Some travelers from Edron told about a great treasure guarded by cruel demons in the dungeons there." +"rumors" -> * + +"buy" -> "I can offer you bread, cheese, ham, or meat." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"satanic" -> "Hmm, I have heard of a 'satanic influence' theory by someone called Newton or something like that... Maybe there's more in the Royal Archives." +"cropwell" -> "No idea who that is, but maybe you'll find something in the Royal Archives..." +"alistair" -> * +"archives" -> "Oh, the Royal Archives are in Rain Castle!" +"dungeon" -> "Ah yes, the graveyard dungeon. All I know is this riddle: His Grave to the south, the tree above, his soul in the shade. No idea what that means, though!" +"graveyard" -> * +"riddle" -> "I heard it when I was a child." +"sunset","homes" -> "The sunset homes are a block of flats south of the harbour." +"one","eyed","stranger" -> "Yes, I remember him. His name was Berfasmur." +"berfasmur" -> "Sorry, he spoke only very little. I know nothing more about him." + +"hengis","wulfson" -> "He is a great bard. He often graced my hut with his presence, songs, and rhymes. I wonder what happened to him lately.", Topic=2 +Topic=2,"died" -> "Oh, by the gods! What do you say happened to him?", Topic=3 +Topic=2,"killed" -> * +Topic=2,"dead" -> * +Topic=2,"death" -> * +Topic=2,"slain" -> * +Topic=3,"killed","cyclops" -> "That's horrible! I am in grief. I will never hear his songs again. I will even miss that strange rhyme he was obsessed with.", Topic=4 +Topic=3,"slain","cyclops" -> * +Topic=3,"cyclops","slay" -> * +Topic=3,"cyclops","kill" -> * +Topic=4,"rhyme" -> "He recitated it that often that I learned it by heart myself. I would recitate it, but I am not skilled in that kind of things.", Topic=5 +Topic=5,"recitate" -> "Uhm. If you insist, but I am so awful. I will stop now and then and wait, so you can tell if I should proceed, ok?", Topic=6 +Topic=6,"yes" -> "Well ok, but don't blame me. Chhrrr... chhrrrr,... it goes like this... chhrrr: and when the dead feast at midnight...", Topic=7 +Topic=7,"proceed" -> "... the ancient enemy will no longer guard the place of his unlucky heir and the living will walk the paths of the old way...", Topic=8 +Topic=8,"proceed" -> "... Death awaits the greedy and the brave alike and many will be mourned until the long lost treasure is unearthed.", Topic=9 +Topic=9,"proceed" -> "That's all. He recitated it when he was in one of his melancholy moods." +} diff --git a/app/SabrehavenServer/data/npc/gabel.npc b/app/SabrehavenServer/data/npc/gabel.npc new file mode 100644 index 0000000..cb0a142 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gabel.npc @@ -0,0 +1,124 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gabel.npc: Datenbank für den Djinnkönig Gabel + +Name = "Gabel" +Outfit = (80,0-0-0-0-0) +Home = [33102,32520,1] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Welcome, human %N, to our humble abode." +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Have patience, %N. In this world every grain of sand has its place and its time. You may have found the place you have been looking for, but it is not yet your time.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, stranger. May Uman open your minds and your hearts to Daraman's wisdom!" + +"bye" -> "Farewell, stranger. May Uman open your minds and your hearts to Daraman's wisdom!", Idle +"farewell" -> * +"name" -> "I am known as Gabel. I have borne this name for as long as I remember, and believe me - that is quite some time." +"gabel" -> "I have often thought about changing this name because... Ah well . Let us talk about more cheerful things." +"job" -> "I am the true leader of all djinn, both in worldly and in spiritual matters. Unfortunately, there are those among my kind who disagree." +"djinn" -> "Once we were a mighty race. I like to think that one day we will return to our former glory, but as long as this tragic war is not won that is not likely to happen." +"war" -> "We had thought the war was over for good when Malor was finally imprisoned. And now he is free again! How could it ever come so far. ...", + "But well - nothing is lost. I have no illusions about Malor's intentions, but I'm not afraid. We have beaten him once and we can beat him again!" +"malor" -> "The accursed usurper is free! I can't believe it! To think that power-hungry cockroach is once again roaming the world! ...", + "I could forgive him his evil schemes if he had not led my people into this tragic fratricidal war!" +"king" -> "Some call me a king, even though I do not like the title. Daraman has taught us to think little of worldly matters such as power or station in life." +"leader" -> * +"daraman" -> "Daraman may have been just a human, but he bore in him the spark of the divine. We have paid a heavy price for following his teachings, but I have never felt any regret for my decision." +"ashta'daramai" -> "This place is a gift of Daraman to the djinn people. Oh, he did not build it himself, of course. It was us Marid who did it. We erected it on the place where once stood my old palace. ...", + "Its serene majesty is a visual expression of the inner peace and light that are bestowed by the great Daraman's teachings." +"abode" -> * +"marid" -> "We, the Marid, are the true inheritors of the djinn legacy. Those errant fools who call themselves the Efreet are nothing but usurpers." +"efreet" -> "Our fallen brethren claim they are different, but I have not given up hope yet that all djinn will be reunited one day! If only they saw the light!" + +"mal'ouquah" -> "The Mal'ouquah is the Efreets' fortress. Malor built it when it was clear that the djinns had definitely split in two fractions. I will personally raze it to the ground once we have finally won this disastrous war!" +"kha'zeel" -> "These majestic mountains were chosen by the gods as a vantage point from which they watched their creation." +"kha'labal" -> "Ah yes - that horrid desert. I still recall how beautiful it was back in the times before the struggle it began. It was a land full of song and bliss - a veritable paradise. But look at it now. It is such a shame." +"orc","king" -> "The power hungry fool released Malor from his prison, and now the evil is upon us once again! He should have known better than to believe Malor's sugar covered lies. ...", + "But what can you expect from a power-crazed, stupid-as-a-brick orc. Nothing but blockheads the lot of them" +"human" -> "For a long time we have despised and oppressed your kind. I still feel ashamed for the things we have done in those dark days. The gods be praised that they sent Daraman to open our eyes. ...", + "I know that one day djinn and humans will live in peaceful co-existence." +"zathroth" -> "The name brings up painful memories. I'd rather not talk about this subject." +"gods" -> "For a long time I found it difficult to love them. But Daraman has opened my eyes." +"tibia" -> "The world of Tibia is like a gemstone carved by the greatest of all craftsmen. It is sad that it took a human to make me realise its perfection." + +"darashia" -> "Darashia was nothing but a forlorn pool of mud last time I passed there. I hear it has now risen to great wealth and glory. Perhaps the Caliph is more open to the true creed than that dangerous fool, the pharaoh of Ankrahmun." +"scarab" -> "The scarabs are ancient beings, as ancient as ourselves. We djinns feel a lot of respect for them." +"edron" -> "I have often heard of the splendid cities the humans have erected on the continent. I would like to visit them one day and send them a message of peace and friendship in the name of my people." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I haven't seen the venerable city in a long time. I would like to visit it again, but I can't help the feeling that I would not be welcome these days." + +"pharaoh" -> "The new pharaoh seems to hold some very eccentric ideas about life and death. I have a feeling that his so-called teachings are nothing but an ignorant perversion of the true creed." +"palace" -> "Stay clear of that place. I have heard bad things about it." +"temple" -> "In these heretic times the temple is devoted to the teachings of that pompous pharaoh. I haven't been there for a long time." +"ascension" -> "Apparently that is what the followers of the pharaoh are striving for. It has to do with his heretical teachings." +"rah" -> "That's just some heretic drivel. Don't ask me about it." +"uthun" -> * +"akh" -> * + +"djema" -> "Poor thing. She's an orphan you know. We took her to us when she was a child, and we have never regretted it. So fresh and lively - she has really brought some life to this place. ...", + "In a way it was an experiment, you know. A human living among djinn. I suppose it worked well, but perhaps too well. ...", + "Now she has grown on me, and I'm loath to let her go. Sometimes I wonder if it was right to make her live in this place after all." +"bo'ques" -> "My good old cook. Of all worldly temptations his food is the greatest." +"baa'leal" -> "He is Malor's lieutenant and the commander-in-chief of all his minions. He is nothing without his master. Never stirred while Malor was gone." +"alesar" -> "Alesar... Alesar. You know, it pains me to even hear this name. When he left I lost both my best smith and a personal friend. I don't know what is worse. ...", + "And the worst is, to this day I have never managed to figure out why he left. I refuse to believe that Malor could bribe him in any way. If only I knew." +"fa'hradin" -> "He is my trusted counsellor and friend. If you would like to help us you should talk to him. ...", + "By the way - don't worry if his behaviour appears a bit odd sometimes. He is incurably eccentric. Always has been. I think it is a job hazard of being a wizard." +"lamp" -> "We djinn use them to sleep. Well, you may find it is a funny notion to sleep in a lamp, but then, for us it seems just as silly to sleep in a longish wooden construction with a fluffy mattress on top." +"fa'hradin","lamp" -> "Ah yes. This lamp was his masterpiece. It was so satisfying to see that dirty little schemer fall for a ploy himself. If only he'd never come back!" +"rata'mari" -> "So you know about him. Hm. Since nobody else knows about him Fa'Hradin must have told you. ...", + "I suppose he had his reasons, but I would appreciate it if you did not tell anybody about him. If Malor found about him, he would start a little rat hunt I guess." + +"permission",QuestValue(283)<3 -> "I am not yet convinced, that we can trust you, %N. Only trusted people are allowed to trade with Haroun and Nah'Bob." +"permission",QuestValue(283)=3 -> "You are welcome to trade with Haroun and Nah'bob whenever you want to, %N!" + +"work",QuestValue(281)<2 -> "So you would like to fight for us, don't you. Hmm. ...", + "That is a noble resolution you have made there, human, but I'm afraid I cannot accept your generous offer at this point of time. ...", + "Do not get me wrong, but I am not the kind of guy to send an inexperienced soldier into certain death! So you might ask around here for a more suitable mission." +"mission",QuestValue(281)<2 -> * + +"report",QuestValue(281)=2,QuestValue(283)=0 -> "Sooo. Fa'hradin has told me about your extraordinary exploit, and I must say I am impressed. ...", + "Your fragile human form belies your courage and your fighting spirit. ...", + "I hardly dare to ask you because you have already done so much for us, but there is a task to be done, and I cannot think of anybody else who would be better suited to fulfill it than you. ...", + "Think carefully, human, for this mission will bring you into real danger. Are you prepared to do us that final favour?", Topic=1 +"spyreport",QuestValue(281)=2,QuestValue(283)=0 -> * +"work",QuestValue(281)=2,QuestValue(283)=0 -> * +"mission",QuestValue(281)=2,QuestValue(283)=0 -> * + +"report",QuestValue(283)=1 -> "You haven't finished your final mission yet. Shall I explain it again to you?", Topic=1 +"spyreport",QuestValue(283)=1 -> * +"work",QuestValue(283)=1 -> * +"mission",QuestValue(283)=1 -> * +"lamp",QuestValue(283)=1 -> * + +Topic=1,"yes" -> "All right. Listen! Thanks to Rata'mari's report we now know what Malor is up to: he wants to do to me what I have done to him - he wants to imprison me in Fa'hradin's lamp! ...", + "Of course, that won't happen. Now, we know his plans. ...", + "But I am aiming at something different. We have learnt one important thing: At this point of time, Malor does not have the lamp yet, which means it is still where he left it. We need that lamp! If we get it back we can imprison him again! ...", + "From all we know the lamp is still in the Orc King's possession! Therefore I want to ask you to enter the well guarded halls over at Ulderek's Rock and find the lamp. ...", + "Once you have acquired the lamp you must enter Mal'ouquah again. Sneak into Malor's personal chambers and exchange his sleeping lamp with Fa'hradin's lamp! ...", + "If you succeed, the war could be over one night later! I and all djinn will be in your debt forever! May Daraman watch over you!", SetQuestValue(283,1) +Topic=1 -> "As you wish." + +"report",QuestValue(283)=2 -> "Have you found Fa'hradin's lamp and placed it in Malor's personal chambers? ", Topic=2 +"spyreport",QuestValue(283)=2 -> * +"work",QuestValue(283)=2 -> * +"mission",QuestValue(283)=2 -> * +"lamp",QuestValue(283)=2 -> * + +Topic=2,"yes" -> "Daraman shall bless you and all humans! You have done us all a huge service! Soon, this awful war will be over! ...", + "Know, that from now on you are considered one of us and are welcome to trade with Haroun and Nah'bob whenever you want to!", SetQuestValue(283,3) +Topic=2 -> "Don't give up! May Daraman watch over you!" +} diff --git a/app/SabrehavenServer/data/npc/gail.npc b/app/SabrehavenServer/data/npc/gail.npc new file mode 100644 index 0000000..3eb9a4d --- /dev/null +++ b/app/SabrehavenServer/data/npc/gail.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gail.npc: Datenbank für die edelsteinhändlerin gail + +Name = "Gail" +Outfit = (140,77-64-52-95-0) +Home = [32621,32738,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Please feel welcome." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment. I'll be with you within a heartbeat.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am buying and selling gems and jewellery." +"name" -> "My name is Gail, nice to meet you." +"time" -> "I am sorry but watches are not that common here. Time has little meaning in this quite boring colony." +"king" -> "The Thaian monarch entrusted Venore with much responsibility to manage this colony. Sadly he did not entrust the entire control in our hands which is a constant reason for all kinds of problems." +"venore" -> "Venore is a beautiful city and its community is built up on commerce. Therefore it serves the needs of everyone who wants to contribute his share to the welfare of the city." +"thais" -> "A somewhat chaotic town. I understand how its growth dictated the shape of its community but I also see the flaws in the outcome." +"carlin" -> "Their independence is just insane viewed from an economic angle. A complete waste of resources." +"edron" -> "Another area that could and should prosper under Venoran guidance. If the king sees how we handle this settlement, he will for sure be impressed enough to allow us more freedom on that promising isle." +"jungle" -> "As soon as we have handled the problem with the natives, wealth is awaiting us, considering the endless resources waiting there to be taken." + +"tibia" -> "There is so much left that still needs to be discovered in this world." + +"kazordoon" -> "Dwarven craftsmen are some of the best in their fields. Sadly they charge quite a lot for their work and it's hard to make some profit with dwarven wares." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Elves are bad customers. We did not manage to break into their market yet." +"elves" -> * +"elfs" -> * +"darama" -> "This continent is a challenge and an opportunity at the same time." +"darashia" -> "People there are somewhat strange. In my opinion their philosophy isn't good for business but the minds of people can be changed if you use the right arguments." +"ankrahmun" -> "Well, I admit I fail to see any profit that could safely be made by trading with that city, but even unsafe profit is a good one. Prices have to be adjusted accordingly to the course." +"ferumbras" -> "One evil sorcerer, no matter how powerful he might be, can have only a certain influence on the market. There are other threats to our profit that are more urgent." +"excalibug" -> "A knight's fairy tale." +"apes" -> "Those animals are one of the worst things we have to face here." +"lizard" -> "There must be some way to leverage their hatred towards those apes." +"dworcs" -> "Luckily, this horrible ugly things stay in their own territory but who knows for how long? We should never show them any sign of weakness." + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, small sapphires, small rubies, small emeralds and small amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting ring, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/galuna.npc b/app/SabrehavenServer/data/npc/galuna.npc new file mode 100644 index 0000000..3338c23 --- /dev/null +++ b/app/SabrehavenServer/data/npc/galuna.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# galuna.npc: Datenbank für die Bogenmacherin Galuna + +Name = "Galuna" +Outfit = (137,40-96-95-96-0) +Home = [32342,32246,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the local fletcher. I am selling bows, crossbows, and ammunition. Do you need anything?" +"fletcher" -> * +"name" -> "I am Galuna, paladin and fletcher." +"paladin" -> "We are feared warriors and good marksmen. Ask Elane if want to know more about the guild." +"elane" -> "She is the leader of all paladins." +"gorn" -> "I supplied him with my goods in the past, now I sell them myself." +"time" -> "Don't bother me. Go and buy a watch." +"tibia" -> "Tibia, a green island. Here it is wunderful to walk into the forests and to hunt with a bow." +"forest" -> * +"thais" -> "We have visitors of all kind in Thais, only elves show up seldom." +"elf" -> "It is rumored that they live in the northeast of Tibia. They are the best in archery." +"elves" -> * + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." +"power","bolt",QuestValue(17538)=0,male -> "Power bolts for you? Ha ha, boy you don't look like a real hunter to me." +"power","bolt",QuestValue(17538)=0,female -> "Power bolts for you? Ha ha, girl you don't look like a real hunter to me." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 +"power","bolt",QuestValue(17538)=1 -> Type=3450, Amount=1, Price=10, "Do you want to buy a power bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 +%1,1<%1,"power","bolt",QuestValue(17538)=1 -> Type=3450, Amount=%1, Price=10*%1, "Do you want to buy %A power bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +"task",QuestValue(17647)=0,paladin -> Amount=17648, "Young paladin, I see you need ammunition but those are too expensive, right. Hmm... I can't give you for free. ...", + "However, if you could kill 50 minotaurs to prove your trustworthy willingness I will reward you the crossbow and 300 bolts. Deal?", Topic=120 + +"task",QuestValue(17648)=50,QuestValue(17647)=1,paladin -> "Well done, %N. Here is your crossbow and bolts!", SetQuestValue(17647,2), SetQuestValue(17649,0), Type=3349, Amount=1, Create(Type), Type=3446, Amount=300, Create(Type) + +"task",QuestValue(17649)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young paladin. Come back once you are done.", SetQuestValue(17649,Amount), SetQuestValue(Amount,0), SetQuestValue(17647,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." + +} diff --git a/app/SabrehavenServer/data/npc/gamel.npc b/app/SabrehavenServer/data/npc/gamel.npc new file mode 100644 index 0000000..e3c37d4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gamel.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Gamel.npc: Datenbank für den Gauner Gamel + +Name = "Gamel" +Outfit = (129,79-132-115-116-0) +Home = [32337,32207,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Pssst! Be silent. Do you wish to buy something?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not right now." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye. Tell others about... my little shop here." + +"bye" -> "Bye. Tell others about... my little shop here.", Idle +"farewell" -> * +"job" -> "I am selling some... things." +"name" -> "Names don't matter." +"gamel" -> "Oh, you know my name. Please don't tell it to the others." + +"rebellion" -> "Uhm... who sent you?", Topic=3 +"berfasmur" -> "Never heard that name!" + +"offer" -> "I sell maces, staffs, daggers and brass helmets." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"things" -> * +"help" -> * + +"staff" -> Type=3289, Amount=1, Price=40, "Do you want to buy it for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy it for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy it for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy it for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy it for %P gold?", Topic=1 + +%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=40*%1, "Do you want to buy %A staffs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Hey, you do not have enough gold." +Topic=1 -> "Maybe we will make the deal another time." + +"magic","crystal" -> Type=3061, Amount=1, "Did you bring me a magic crystal?", Topic=4 +Topic=3,"berfasmur" -> "So, you are a new recruit in the ranks of the rebellion! To proof your worthyness, go and get us a magic crystal." +Topic=4,"yes",Count(Type)>=Amount -> "Brilliant! Bring it to the priest Lugri so that he can cast a deathcurse on the king. The password is 'death to noodles'." +Topic=4,"yes",Count(Type) "Idiot! You don't have the crystal!", Poison(2,10), EffectOpp(9), EffectMe(15) +} diff --git a/app/SabrehavenServer/data/npc/gamon.npc b/app/SabrehavenServer/data/npc/gamon.npc new file mode 100644 index 0000000..a9d2cdf --- /dev/null +++ b/app/SabrehavenServer/data/npc/gamon.npc @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gamon.npc: Möbelverkäufer Gamon in Thais + +Name = "Gamon" +Outfit = (128,97-58-105-120-0) +Home = [32408,32170,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",female,! -> "Well, hello there, Lady, %N welcome to Gamon's humble furniture shop!" +ADDRESS,"hi$",female,! -> * +ADDRESS,"hello$",male,! -> "Nice to meet you, Mister %N! Looking for furniture? You've come to the right place!" +ADDRESS,"hi$",male,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, please. I got a customer here." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,male,! -> "Now where's he gone? I could have sworn..." +VANISH,! -> "Lady? Lady?? Hm. How strange..." + +"bye" -> "You'll come back. They all do.", Idle +"farewell" -> * +"job" -> "I am Thais's foremost furniture salesman." +"news" -> "News? Of course there's news! There's a new furniture man in town, and he's here to stay!" +"how","are","you"-> "Excellent! Never felt better in my life!" +"name" -> "My friends call me Gamon. My fans call me the incredible Gammy!" +"time" -> "Any time's a good time to buy some furniture." +"Thais" -> "Thais is obsessed with its past. Everybody here is so proud of their history. Bah! Thais might have a long history, but it has no idea when it comes to interior decoration." +"Venore" -> "The place where it all happens. That town really rocks! Thais could learn a lot about interior decoration from Venore!" +"power" -> "There are a few rumours about a rebellion, but that is all they are." +"rebellion" -> "Well - a few paranoid souls think that Venore wants to gain independence from Thais. Nothing more than rumours." + +"news" -> "You mean my specials, don't you?" + +"quality" -> "Our furniture is produced by the finest carpenters on the continent using the rare wood of the Venorean marsh willow!" +"marsh","willow" -> "You can't get any better wood in this world. And it has got a nice smell to it, too. There is nothing nicer than a marsh willow campfire." +"king" -> "His Royal Highness will start to appreciate the superior quality of our stock soon enough!" +"sam" -> "I heard rumours he has some special offers for customers who know to ask for the correct things." +"benjamin" -> "He's incredibly slow. Just your average postman, I guess." +"gorn" -> "He sells stuff of inferior quality. Nothing compared to venores high quality goods." +"quentin" -> "That old monk has probably never left that overcrowded town here." +"quest" -> "A quest?! Who needs quests when there is interior decorating!" +"bozo" -> "Bozo! Damn that clown! He keeps making fake orders. It isn't funny to deliver a wardrobe to an address that doesn't even exist, you know!" +"tibia" -> "Tibia is a wonderful place full of business opportunities." +"castle" -> "I've said it a thousand times! That place needs a complete refurbishing!" +"muriel" -> "The sorcerers guild could realy need someone with taste to redecorate it." +"elane" -> "She's pretty, but I am the kind of man who enjoys a long and healthy life." +"marvik" -> "Druids are obsessed with trees and the bane of any carpenter." +"gregor" -> "Those knights know how to party. And after such partys theres allways need for new furniture." +"guild" -> "Now those people really talk business. There is a new era dawning." +"merchants" -> * +"Topsy" -> "Ah, those twins. Strange people they are (*sigh*). Oh, they are great to work with, of course. Excellent quality, competetive prices! But well... (whispers) they give me the creeps!" +"Turvy" -> * +"twins" -> * +"creeps" -> "Yes! I can never figure out which is which. And they are always watching me!" +"watching" -> "Look! They are doing it again! And they are always smiling!" +"smiling" -> "Yes! Smiling! How a professional sales artist such as myself is supposed to work in such an atmosphere is beyond me!" +"artist" -> "Yes! Selling is a form of art! The elaborate combination of rhetoric and acting which serves to create a sublime longing for the infinite, embodied by second class furniture." +"rug" -> "Oh, silly me! Rugs are out of stock at the moment! But we expect a new shipment anytime. Just watch out for the next update!... Of our inventory I mean." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +"stake" -> Type=5941, Amount=1, Price=5000, "Making a stake from a chair? Are you insane??! I won't waste my chairs on you for free! You will have to pay for it, but since I consider your plan a blasphemy, it will cost 5000 gold pieces. Okay?", Topic=142 +Topic=142,"yes",CountMoney>=Price -> "Argh... my heart aches! Alright... a promise is a promise. Here - take this wooden stake, and now get lost.", DeleteMoney, Create(Type), Idle +Topic=142,"yes" -> "You can't even pay for that." +Topic=142,"no" -> "Phew. No chair-killing." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/gatekeeper.npc b/app/SabrehavenServer/data/npc/gatekeeper.npc new file mode 100644 index 0000000..f4257cc --- /dev/null +++ b/app/SabrehavenServer/data/npc/gatekeeper.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gatekeeper.npc: Datenbank fuer das Premium Orakel auf Rookgaard + +Name = "The Gatekeeper" +Outfit = (0,2031) +Home = [32035,32183,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",Level>=8,premium,! -> "%N, ARE YOU PREPARED TO FACE YOUR DESTINY?" +ADDRESS,"hi$",Level>=8,premium,! -> * +ADDRESS,"greet",Level>=8,premium,! -> * + +ADDRESS,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"greet",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Level>=8,premium,! -> "WAIT UNTIL IT IS YOUR TURN!", Queue +BUSY,"hi$",Level>=8,premium,! -> * +BUSY,"greet",Level>=8,premium,! -> * +BUSY,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!" +BUSY,"hi$",! -> * +BUSY,"greet",! -> * +BUSY,! -> NOP +VANISH,! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!" + +"yes",premium -> "IN WHICH TOWN DO YOU WANT TO LIVE: AB'DENDRIEL, KAZORDOON, ANKRAHMUN, PORT HOPE OR DARASHIA?", Topic=1 +"yes" -> "YOU ARE NOT WORTHY!", Idle +"bye",! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!", Idle + -> * + +Topic=1,"Ab'Dendriel" -> Data=1, "IN AB'DENDRIEL! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"kazordoon" -> Data=2, "IN KAZORDOON! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"darashia" -> Data=3, "IN DARASHIA! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"ankrahmun" -> Data=4, "IN ANKRAHMUN! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"port","hope" -> Data=5, "IN PORT HOPE! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 + + +Topic=1,premium -> "AB'DENDRIEL, KAZORDOON, ANKRAHMUN, PORT HOPE OR DARASHIA?", Topic=1 + + +Topic=2,"knight" -> Type=4, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"knight",Amount=1 -> Type=4, Price=3300, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"knight",Amount=2 -> Type=4, Price=3286, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"knight",Amount=3 -> Type=4, Price=3276, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"paladin" -> Type=3, Price=3277, Amount=5, "A PALADIN! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"sorcerer" -> Type=1, Price=3074, Amount=1, "A SORCERER! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"druid" -> Type=2, Price=3066, Amount=1, "A DRUID! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2 -> "KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 + +Topic=3,Data=1,"yes" -> "SO BE IT!", Profession(Type), Town(4), Idle, EffectOpp(11), Teleport(32732,31634,7), EffectOpp(11), Create(Price) +Topic=3,Data=2,"yes" -> "SO BE IT!", Profession(Type), Town(3), Idle, EffectOpp(11), Teleport(32649,31925,11), EffectOpp(11), Create(Price) +Topic=3,Data=3,"yes" -> "SO BE IT!", Profession(Type), Town(6), Idle, EffectOpp(11), Teleport(33213,32454,1), EffectOpp(11), Create(Price) +Topic=3,Data=4,"yes" -> "SO BE IT!", Profession(Type), Town(8), Idle, EffectOpp(11), Teleport(33194,32853,8), EffectOpp(11), Create(Price) +Topic=3,Data=5,"yes" -> "SO BE IT!", Profession(Type), Town(9), Idle, EffectOpp(11), Teleport(32595,32744,6), EffectOpp(11), Create(Price) + + +} diff --git a/app/SabrehavenServer/data/npc/gelagos.npc b/app/SabrehavenServer/data/npc/gelagos.npc new file mode 100644 index 0000000..bc9860a --- /dev/null +++ b/app/SabrehavenServer/data/npc/gelagos.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Gelagos" +Outfit = (128,114-91-85-79-0) +Home = [32363,31624,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/gen-bank.ndb b/app/SabrehavenServer/data/npc/gen-bank.ndb new file mode 100644 index 0000000..62690f3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-bank.ndb @@ -0,0 +1,175 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-post.ndb: Datenbank für generischen Banker +# Verwendete Topics: 91 bis 99 +# Topic 91(96): Gold -> Platinum +# Topic 92(97): Platinum <- Crystal +# Topic 93(98): Gold <- Platinum +# Topic 94(99): Platinum -> Crystal + +"bank" -> "We can change money for you." +"offer" -> "We exchange gold, platinum and crystal coins." +"sell" -> * +"do","you","have" -> * +"buy" -> * +"money" -> * +"change" -> * +"exchange" -> * +"guild" -> "If you are a member of a guild, you can deposit money on its account via guild deposit or check the guild money via guild balance. ...", + "If you are leader or vice leader of a guild, you can also withdraw gold from your guild account via guild withdraw. ...", + "Please keep in mind that we may need some time to process such requests." + +"change","gold",! -> "How many platinum coins do you want to get?", Topic=91 +"exchange","gold",! -> * +"change","platinum",! -> "Do you want to change your platinum coins to gold or crystal?", Topic=95 +"exchange","platinum",! -> * +"change","crystal",! -> "How many crystal coins do you want to change to platinum?", Topic=92 +"exchange","crystal",! -> * +"change" -> "Do you want to exchange gold, platinum or crystal coins?" + +Topic=95,"gold",! -> "How many platinum coins do you want to change to gold?", Topic=93 +Topic=95,"crystal",! -> "How many crystal coins do you want to get?", Topic=94 +Topic=95 -> "Well, can I help you with something else?" + +Topic=91,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %A of your gold coins to %P platinum coins for you?", Topic=96 +Topic=91 -> "Hmm, can I help you with something else?" + +Topic=92,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %A of your crystal coins to %P platinum coins for you?", Topic=97 +Topic=92 -> "Well, can I help you with something else?" + +Topic=93,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %A of your platinum coins to %P gold coins for you?", Topic=98 +Topic=93 -> "Well, can I help you with something else?" + +Topic=94,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %A of your platinum coins to %P crystal coins for you?", Topic=99 +Topic=94 -> "Well, can I help you with something else?" + +Topic=96,"yes",Count(3031)>=Amount -> "Here you are.", Delete(3031), Amount=Price, Create(3035) +Topic=96,"yes" -> "Sorry, you don't have enough gold coins." +Topic=96 -> "Well, can I help you with something else?" + +Topic=97,"yes",Count(3043)>=Amount -> "Here you are.", Delete(3043), Amount=Price, Create(3035) +Topic=97,"yes" -> "Sorry, you don't have so many crystal coins." +Topic=97 -> "Well, can I help you with something else?" + +Topic=98,"yes",Count(3035)>=Amount -> "Here you are.", Delete(3035), Amount=Price, Create(3031) +Topic=98,"yes" -> "Sorry, you don't have so many platinum coins." +Topic=98 -> "Well, can I help you with something else?" + +Topic=99,"yes",Count(3035)>=Amount -> "Here you are.", Delete(3035), Amount=Price, Create(3043) +Topic=99,"yes" -> "Sorry, you don't have so many platinum coins." +Topic=99 -> "Well, can I help you with something else?" + +#Topic=91,%1,0<%1 -> Amount=%1, Price=100*%1, "So I should change %P gold coins to %A platinum coins for you?", Topic=96 +#Topic=91 -> "Hmm, can I help you with something else?" + +#Topic=92,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %P crystal coins to %A platinum coins for you?", Topic=97 +#Topic=92 -> "Well, can I help you with something else?" + +#Topic=93,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %P platinum coins to %A gold coins for you?", Topic=98 +#Topic=93 -> "Well, can I help you with something else?" + +#Topic=94,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %P platinum coins to %A crystal coins for you?", Topic=99 +#Topic=94 -> "Well, can I help you with something else?" + +#Topic=96,"yes",Count(3031)>=Price -> "Here you are.", Create(3035), Amount=Price, Delete(3031) +#Topic=96,"yes" -> "Sorry, you don't have enough gold coins." +#Topic=96 -> "Well, can I help you with something else?" + +#Topic=97,"yes",Count(3043)>=Price -> "Here you are.", Create(3035), Amount=Price, Delete(3043) +#Topic=97,"yes" -> "Sorry, you don't have so many crystal coins." +#Topic=97 -> "Well, can I help you with something else?" + +#Topic=98,"yes",Count(3035)>=Price -> "Here you are.", Create(3031), Amount=Price, Delete(3035) +#Topic=98,"yes" -> "Sorry, you don't have so many platinum coins." +#Topic=98 -> "Well, can I help you with something else?" + +#Topic=99,"yes",Count(3035)>=Price -> "Here you are.", Create(3043), Amount=Price, Delete(3035) +#Topic=99,"yes" -> "Sorry, you don't have so many platinum coins." +#Topic=99 -> "Well, can I help you with something else?" + +# Bank System +"balance" -> Amount=Balance, "Your account balance is %A gold." +"balance",balance>99999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>999999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>9999999 -> Amount=Balance, "You have made ten millions and it still grows! Your account balance is %A gold." +"balance",balance>99999999 -> Amount=Balance, "I think you must be one of the richest inhabitants in the world! Your account balance is %A gold." + +"guild","balance" -> "You are not a member of a guild." +"guild","balance",GuildLevel>0 -> Amount=GuildBalance, "Your guild account balance is %A gold." + +"deposit" -> "You don't have any gold with you." +"deposit",CountMoney>0 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +"deposit","all",CountMoney>0 -> Price=CountMoney, "Would you really like to deposit %P gold?", Topic=82 +"deposit",$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +"deposit","0" -> "You are joking, aren't you??" +"deposit",$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81,$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +Topic=81,"0" -> "You are joking, aren't you?" +Topic=81,$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +Topic=82,"yes",CountMoney>=Price -> "Alright, we have added the amount of %P gold to your balance. You can withdraw your money anytime you want to.", DeleteMoney, Deposit(Price) +Topic=82,"yes" -> "I am inconsolable, but it seems you have lost your gold. I hope you get it back." +Topic=82 -> "As you wish. Is there something else I can do for you?" + +"guild","deposit",GuildLevel<=0 -> "You are not a member of a guild." +"guild","deposit" -> "You don't have any gold in your bank account." +"guild","deposit",Balance>0 -> "Please tell me how much gold it is you would like to deposit for your guild.", Topic=71 +"guild","deposit","all",GuildLevel<=0 -> "You are not a member of a guild." +"guild","deposit","all",Balance>0 -> Price=Balance, "Would you really like to deposit %P gold in to your guild bank account?", Topic=72 +"guild","deposit",$1,0<$1,GuildLevel<=0 -> "You are not a member of a guild." +"guild","deposit",$1,0<$1,Balance>=$1 -> Price=$1, "Would you really like to deposit %P gold in to your guild bank account?", Topic=72 +"guild","deposit",$1,0<$1,Balance<$1 -> "You do not have enough gold in your bank account." +Topic=71,$1,0<$1,Balance>=$1 -> Price=$1, "Would you really like to deposit %P gold in to your guild bank account?", Topic=72 +Topic=71,"0" -> "You are joking, aren't you?" +Topic=71,$1,0<$1,Balance<$1 -> "You do not have enough gold in your bank account." +Topic=71 -> "Please tell me how much gold it is you would like to deposit for your guild.", Topic=71 +Topic=72,"yes",Balance>=Price,GuildLevel>0 -> "Alright, we have added the amount of %P gold to your guild balance. Keep in mind that only authorized person can withdraw from the guild account.", GuildDeposit(Price) +Topic=72,"yes" -> "I am inconsolable, but it seems you do not have enough gold in your bank account anymore." +Topic=72 -> "As you wish. Is there something else I can do for you?" + +"withdraw" -> "Please tell me how much gold you would like to withdraw.", Topic=83 +"withdraw",$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +"withdraw","0" -> "Sure, you want nothing you get nothing!" +"withdraw",$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83,$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +Topic=83,"0" -> "Sure, you want nothing you get nothing!" +Topic=83,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83 -> "Please tell me how much gold you would like to withdraw.", Topic=83 +Topic=84,"yes",Balance>=Price -> "Here you are, %P gold. Please let me know if there is something else I can do for you.", CreateMoney, Withdraw(Price) +Topic=84,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=84 -> "The customer is king! Come back anytime you want to if you wish to withdraw your money." + +"guild","withdraw",GuildLevel<=0 -> "I am sorry but it seems you are currently not in any guild." +"guild","withdraw",GuildLevel=1 -> "I am sorry but you are not eligible to withdraw from the guild account." +"guild","withdraw" -> "Please tell me how much gold you would like to withdraw from your guild account.", Topic=73 +"guild","withdraw",$1,0<$1,GuildLevel<=0 -> "I am sorry but it seems you are currently not in any guild." +"guild","withdraw",$1,0<$1,GuildLevel=1 -> "I am sorry but you are not eligible to withdraw from the guild account." +"guild","withdraw",$1,0<$1,GuildBalance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your guild bank account?", Topic=74 +"guild","withdraw",$1,0<$1,GuildBalance<$1 -> "There is not enough gold on your guild account." +Topic=73,$1,0<$1,GuildBalance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your guild bank account?", Topic=74 +Topic=73,"0" -> "Sure, you want nothing you get nothing!" +Topic=73,$1,0<$1,GuildBalance<$1 -> "There is not enough gold on your guild account." +Topic=73 -> "Please tell me how much gold you would like to withdraw from your guild account..", Topic=73 +Topic=74,"yes",GuildBalance>=Price,GuildLevel>1 -> "Here you are, we withdraw %P gold from your guild account to your personal account. Please let me know if there is something else I can do for you.", GuildWithdraw(Price) +Topic=74,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your guild bank account." +Topic=74 -> "The customer is king! Come back anytime you want to if you wish to withdraw your money." + +"transfer" -> "Please tell me the amount of gold you would like to transfer.", Topic=85 +"transfer","0","to" -> "Please think about it. Okay?" +"transfer",$1,0<$1,"to",Balance<$1 -> "There is not enough gold on your account." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=2 -> Price=$1, "Would you really like to transfer %P gold to %S?", Topic=88 +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=85,$1,0<$1,Balance>=$1 -> Price=$1, "Who would you like transfer %P gold to?", Topic=86 +Topic=85,"0" -> "Please think about it. Okay?" +Topic=85,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=86,Balance>=Price,TransferToPlayerNameState=2 -> "Would you really like to transfer %P gold to %S?", Topic=87 +Topic=86,Balance>=Price,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +Topic=86,Balance>=Price,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=87,"yes",Balance>=Price -> "You have transferred %P gold to %S.", Transfer(Price) +Topic=87,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=87 -> "Ok. What is next?" +Topic=88,"yes",Balance>=Price -> "Very well. You have transferred %P gold to %S.", Transfer(Price) +Topic=88,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=88 -> "Alright, is there something else I can do for you?" + + diff --git a/app/SabrehavenServer/data/npc/gen-post.ndb b/app/SabrehavenServer/data/npc/gen-post.ndb new file mode 100644 index 0000000..75a0d09 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-post.ndb @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-post.ndb: Datenbank für generischen Postler + +"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=35 +"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all tibian citizens." +"offer" -> "I'm selling letters and parcels." + +"letter",QuestValue(250)>0 -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=36 +"parcel",QuestValue(250)>0 -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=37 + +"letter" -> Amount=1, Price=8, "Do you want to buy a letter for %P gold?", Topic=36 +"parcel" -> Amount=1, Price=15, "Do you want to buy a parcel for %P gold?", Topic=37 + +%1,1<%1,"letter",QuestValue(250)>0 -> Amount=%1, Price=5*%1, "Do you want to buy %A letters for %P gold?", Topic=36 + +%1,1<%1,100<%1,"parcel",QuestValue(250)>0 -> Amount=100, Price=10*100, "Do you want to buy %A parcels for %P gold?", Topic=37 +%1,1<%1,"parcel",QuestValue(250)>0 -> Amount=%1, Price=10*%1, "Do you want to buy %A parcels for %P gold?", Topic=37 + +%1,1<%1,"letter" -> Amount=%1, Price=8*%1, "Do you want to buy %A letters for %P gold?", Topic=36 + +%1,1<%1,100<%1,"parcel" -> Amount=100, Price=15*100, "Do you want to buy %A parcels for %P gold?", Topic=37 +%1,1<%1,"parcel" -> Amount=%1, Price=15*%1, "Do you want to buy %A parcels for %P gold?", Topic=37 + + +Topic=35,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +Topic=35 -> "Is there anything else I can do for you?" + +Topic=36,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +Topic=36,"yes" -> "Oh, you have not enough gold to buy a letter." +Topic=36 -> "Ok." + +Topic=37,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +Topic=37,"yes" -> "I am sorry, you have not enough gold to buy a parcel." +Topic=37 -> "Ok." diff --git a/app/SabrehavenServer/data/npc/gen-t-armor-b.ndb b/app/SabrehavenServer/data/npc/gen-t-armor-b.ndb new file mode 100644 index 0000000..d25bfa3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-armor-b.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-armor-b.ndb: Datenbank für generischen Rüstungskauf + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell this coat for %P gold?", Topic=51 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell this jacket for %P gold?", Topic=51 +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=51 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=51 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=51 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=51 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell this knights armor for %P gold?", Topic=51 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500, "Do you want to sell this golden armor for %P gold?", Topic=51 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell this coats for %P gold?", Topic=51 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell this jackets for %P gold?", Topic=51 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell a leather armors for %P gold?", Topic=51 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell a chain armors for %P gold?", Topic=51 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell a brass armors for %P gold?", Topic=51 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell a plate armors for %P gold?", Topic=51 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell this knights armors for %P gold?", Topic=51 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1, "Do you want to sell this golden armors for %P gold?", Topic=51 + + +Topic=51,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=51,"yes" -> "Sorry, you do not have one." +Topic=51,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=51 -> "Maybe next time." + diff --git a/app/SabrehavenServer/data/npc/gen-t-armor-s.ndb b/app/SabrehavenServer/data/npc/gen-t-armor-s.ndb new file mode 100644 index 0000000..d8140cf --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-armor-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTarmorV.ndb: Datenbank für generischen Rüstungsverkauf + +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=21 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=21 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a dublet for %P gold?", Topic=21 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=21 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=21 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=21 +"plate","armor" -> Type=3357, Amount=1, Price=1200,"Do you want to buy a plate armor for %P gold?", Topic=21 + +Topic=21,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=21,"yes" -> "Sorry, you do not have enough gold." +Topic=21 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-distance-s.ndb b/app/SabrehavenServer/data/npc/gen-t-distance-s.ndb new file mode 100644 index 0000000..22eefce --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-distance-s.ndb @@ -0,0 +1,38 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-distance-s.ndb: Datenbank für generischen Fernwaffenverkauf + + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=34 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=34 +"arrow" -> Type=3447, Amount=10, Price=20, "Do you want to buy %A arrows for %P gold?", Topic=34 +"bolt" -> Type=3446, Amount=10, Price=30, "Do you want to buy %A bolts for %P gold?", Topic=34 + +%1,0<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=34 +%1,0<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=34 + +Topic=34,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=34,"yes" -> "Come back, when you have enough money." +Topic=34 -> "Hmm, but next time." + +"task",QuestValue(17647)=0,paladin -> Amount=17648, "Young paladin, I see you need ammunition but those are too expensive, right. Hmm... I can't give you for free. ...", + "However, if you could kill 50 orcs to prove your trustworthy willingness I will reward you the bow and 200 arrows. Deal?", Topic=120 + +"task",QuestValue(17648)=50,paladin -> "Well done, %N. Here is your bow and arrows!", SetQuestValue(QuestValue(17649),51), SetQuestValue(17649,0), Type=3350, Amount=1, Create(Type), Type=3447, Amount=200, Create(Type) + +"task",QuestValue(17649)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young paladin. Come back once you are done.", SetQuestValue(17649,Amount), SetQuestValue(Amount,0), SetQuestValue(17647,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/gen-t-fruit-s.ndb b/app/SabrehavenServer/data/npc/gen-t-fruit-s.ndb new file mode 100644 index 0000000..d9537ce --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-fruit-s.ndb @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTfruitV.ndb: Datenbank für generischen Früchteverkauf + + +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=24 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=24 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=24 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=24 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=24 +"strawberry" -> Type=3591, Amount=1, Price=1, "Do you want to buy a strawberry for %P gold?", Topic=24 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=24 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=24 +"pear" -> Type=3584, Amount=1, Price=4, "Do you want to buy a pear for %P gold?", Topic=24 +"blueberry" -> Type=3588, Amount=1, Price=1, "Do you want to buy a blueberry for %P gold?", Topic=24 + +"white","mushroom" -> Type=3723, Amount=1, Price=10, "Do you want to buy one of the white mushrooms for %P gold?", Topic=24 + + +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=24 +%1,1<%1,"cherry" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherrys for %P gold?", Topic=24 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=24 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=24 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=24 +%1,1<%1,"strawberries" -> Type=3591, Amount=%1, Price=1*%1, "Do you want to buy %A strawberries for %P gold?", Topic=24 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=24 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=24 +%1,1<%1,"pear" -> Type=3584, Amount=%1, Price=4*%1, "Do you want to buy %A pears for %P gold?", Topic=24 +%1,1<%1,"blueberries" -> Type=3588, Amount=%1, Price=1*%1, "Do you want to buy %A blueberries for %P gold?", Topic=24 + + +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=10*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=24 + +Topic=24,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=24,"yes" -> "Sorry, you do not have enough gold." +Topic=24 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-chairs-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-chairs-s.ndb new file mode 100644 index 0000000..bc1b029 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-chairs-s.ndb @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-chairs-s.ndb: Datenbank für generischen Möbelverkauf - Stühle +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"equipment" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"red", "cushioned",! -> Type=2775, Amount=1, Price=40, "You want to buy a red cushioned chair for %P gold?", Topic=81 +"red", "chair",! -> * +"green", "cushioned",! -> Type=2776, Amount=1, Price=40, "You want to buy a green cushioned chair for %P gold?", Topic=81 +"green", "chair",! -> * +"chair" -> "I can offer you wooden chairs, rocking chairs, red cushioned chairs, green cushioned chairs and sofa chairs." +"wooden", "chair" -> Type=2777, Amount=1, Price=15, "You want to buy a wooden chair for %P gold?", Topic=81 +"rocking", "chair" -> Type=2778, Amount=1, Price=25, "You want to buy a rocking chair for %P gold?", Topic=81 +"cushioned", "chair" -> "I can offer you a red cushioned chair or a green cushioned chair." +"sofa", "chair" -> Type=2779, Amount=1, Price=55, "You want to buy a sofa chair for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-containers-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-containers-s.ndb new file mode 100644 index 0000000..892d0bd --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-containers-s.ndb @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-containers-s.ndb: Datenbank für generischen Möbelverkauf - Container +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"containers" -> "I offer drawers, dressers, lockers, crates, chests, boxes, barrels, trunks and troughs." +"drawer" -> Type=2789, Amount=1, Price=18, "You want to buy drawers for %P gold?", Topic=81 +"dresser" -> Type=2790, Amount=1, Price=25, "You want to buy a dresser for %P gold?", Topic=81 +"locker" -> Type=2791, Amount=1, Price=30, "You want to buy a locker for %P gold?", Topic=81 +"crate" -> Type=2471, Amount=1, Price=10, "Do you want to buy a crate for %P gold?", Topic=81 +"chest" -> Type=2472, Amount=1, Price=10, "Do you want to buy a chest for %P gold?", Topic=81 +"box" -> Type=2469, Amount=1, Price=10, "Do you want to buy a box for %P gold?", Topic=81 +"barrel" -> Type=2793, Amount=1, Price=12, "Do you want to buy a barrel for %P gold?", Topic=81 +"trough" -> Type=2792, Amount=1, Price=7, "Do you want to buy a trough for %P gold?", Topic=81 +"trunk" -> Type=2794, Amount=1, Price=10, "Do you want to buy a trunk for %P gold?", Topic=81 +"armor","rack" -> Type=6114, Amount=1, Price=90, "Do you want to buy an armor rack for %P gold?", Topic=81 +"weapon","rack" -> Type=6115, Amount=1, Price=90, "Do you want to buy a weapon rack for %P gold?", Topic=81 +"bookcase",ClientVersion>=790 -> Type=6372, Amount=1, Price=70, "Do you want to buy a bookcase for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-decoration-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-decoration-s.ndb new file mode 100644 index 0000000..17cae22 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-decoration-s.ndb @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-decoration-s.ndb: Datenbank für generischen Möbelverkauf - Dekoration +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"decoration" -> "I can offer water pipes, pendulum clock, telescopes, table lamps, rocking horses, globes and birdcages. I also sell wall hangings." +"water", "pipe" -> Type=2974, Amount=1, Price=40, "You want to buy a water pipe for %P gold?", Topic=81 +"pendulum", "clock" -> Type=2801, Amount=1, Price=75, "You want to buy a pendulum clock for %P gold?", Topic=81 +"telescope" -> Type=2799, Amount=1, Price=70, "You want to buy a telescope for %P gold?", Topic=81 +"table","lamp" -> Type=2798, Amount=1, Price=35, "You want to buy a table lamp for %P gold?", Topic=81 +"rocking","horse" -> Type=2800, Amount=1, Price=30, "You want to buy a rocking horse for %P gold?", Topic=81 +"globe" -> Type=2797, Amount=1, Price=50, "You want to buy a globe for %P gold?", Topic=81 +"birdcage" -> Type=2796, Amount=1, Price=50, "You want to buy a birdcage for %P gold?", Topic=81 +"wall","hangings" -> "I can offer mirrors, paintings and cuckoo clocks." +"cuckoo" -> Type=2664, Amount=1, Price=40, "You want to buy a cuckoo clock for %P gold?", Topic=81 +"painting" -> "Would you like a landscape, a portrait or a still life. What is your choice?" +"portrait" -> Type=2641, Amount=1, Price=50, "You want to buy a portrait picture for %P gold?", Topic=81 +"landscape" -> Type=2639, Amount=1, Price=50, "You want to buy a landscape picture for %P gold?", Topic=81 +"still", "life" -> Type=2640, Amount=1, Price=50, "You want to buy a still life picture for %P gold?", Topic=81 +"mirror" -> "I sell round mirrors, oval mirrors and edged mirrors. Which one might it be?" +"round", "mirror" -> Type=2632, Amount=1, Price=40, "You want to buy a round mirror for %P gold?", Topic=81 +"oval", "mirror" -> Type=2638, Amount=1, Price=40, "You want to buy a oval mirror for %P gold?", Topic=81 +"edged", "mirror" -> Type=2635, Amount=1, Price=40, "You want to buy a edged mirror for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-flowers-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-flowers-s.ndb new file mode 100644 index 0000000..768732b --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-flowers-s.ndb @@ -0,0 +1,23 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-flowers-s.ndb: Datenbank für generischen Möbelverkauf - Pflanzen und Blumen +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"flower" -> "I offer indoor plants, flower bowls, god flowers, honey flowers and potted flowers. What do you need?" +"plant" -> * +"god","flower" -> Type=2981, Amount=1, Price=5, "Do you want to buy god flowers for %P gold?", Topic=81 +"indoor","plant" -> Type=2811, Amount=1, Price=8, "Do you want to buy an indoor plant for %P gold?", Topic=81 +"flower","bowl" -> Type=2983, Amount=1, Price=6, "Do you want to buy a flower bowl for %P gold?", Topic=81 +"honey","flower" -> Type=2984, Amount=1, Price=5, "Do you want to buy a honey flower for %P gold?", Topic=81 +"potted","flower" -> Type=2985, Amount=1, Price=5, "Do you want to buy a potted flower for %P gold?", Topic=81 +# "christmas","tree" -> Type=2812, Amount=1, Price=50, "A christmas tree is a very nice decoration for your house. Unfortunately it passes off in some weeks! Do you want to buy one for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-instruments-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-instruments-s.ndb new file mode 100644 index 0000000..24d4854 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-instruments-s.ndb @@ -0,0 +1,18 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-instruments-s.ndb: Datenbank für generischen Möbelverkauf - Instrumente +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"instruments" -> "I can offer you a piano or a harp. What would you like?" +"piano" -> Type=2807, Amount=1, Price=200, "You want to buy a piano for %P gold?", Topic=81 +"harp" -> Type=2808, Amount=1, Price=50, "You want to buy a harp for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-jungle-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-jungle-s.ndb new file mode 100644 index 0000000..688268e --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-jungle-s.ndb @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-jungle-s.ndb: Datenbank für generischen Möbelverkauf - Bambusmöbel +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"equipment" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"chair" -> "I can offer you tusk chairs, ivory chairs and trunk chairs." +"tusk", "chair" -> Type=2780, Amount=1, Price=25, "You want to buy a tusk chair for %P gold?", Topic=81 +"ivory", "chair" -> Type=2781, Amount=1, Price=25, "You want to buy an ivory chair for %P gold?", Topic=81 +"trunk", "chair" -> Type=2809, Amount=1, Price=20, "You want to buy a trunk chair for %P gold?", Topic=81 + +"table" -> "I can offer you stone tables, tusk tables, bamboo tables and trunk tables." +"stone", "table" -> Type=2786, Amount=1, Price=30, "You want to buy a stone table for %P gold?", Topic=81 +"tusk", "table" -> Type=2787, Amount=1, Price=25, "You want to buy a tusk table for %P gold?", Topic=81 +"bamboo", "table" -> Type=2788, Amount=1, Price=25, "You want to buy a bamboo table for %P gold?", Topic=81 +"trunk", "table" -> Type=2810, Amount=1, Price=20, "You want to buy a trunk table for %P gold?", Topic=81 + +"drawer" -> "I can offer you bamboo drawers." +"bamboo", "drawer" -> Type=2795, Amount=1, Price=20, "You want to buy a bamboo drawer for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-pillows-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-pillows-s.ndb new file mode 100644 index 0000000..bc00031 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-pillows-s.ndb @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-pillows-s.ndb: Datenbank für generischen Möbelverkauf - Kissen +# Verwendete Topics: 81 bis 84 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"pillow" -> "I can offer small pillows, round pillows, square pillows and heart pillows. Which one might it be?" +"small", "pillow" -> "What color would you prefer? Purple, green, red, blue, orange, turquoise or white?", Topic=82 +Topic=82, "purple" -> Type=2386, Amount=1, Price=20, "You want to buy a small, purple pillow for %P gold?", Topic=81 +Topic=82, "green" -> Type=2387, Amount=1, Price=20, "You want to buy a small, green pillow for %P gold?", Topic=81 +Topic=82, "red" -> Type=2388, Amount=1, Price=20, "You want to buy a small, red pillow for %P gold?", Topic=81 +Topic=82, "blue" -> Type=2389, Amount=1, Price=20, "You want to buy a small, blue pillow for %P gold?", Topic=81 +Topic=82, "orange" -> Type=2390, Amount=1, Price=20, "You want to buy a small, orange pillow for %P gold?", Topic=81 +Topic=82, "turquoise" -> Type=2391, Amount=1, Price=20, "You want to buy a small, turquoise pillow for %P gold?", Topic=81 +Topic=82, "white" -> Type=2392, Amount=1, Price=20, "You want to buy a small, white pillow for %P gold?", Topic=81 +"round", "pillow" -> "What color would you prefer? Purple, red, blue or turquoise?", Topic=83 +Topic=83, "blue" -> Type=2398, Amount=1, Price=25, "You want to buy a blue, round pillow for %P gold?", Topic=81 +Topic=83, "purple" -> Type=2400, Amount=1, Price=25, "You want to buy a purple, round pillow for %P gold?", Topic=81 +Topic=83, "red" -> Type=2399, Amount=1, Price=25, "You want to buy a red, round pillow for %P gold?", Topic=81 +Topic=83, "turquoise" -> Type=2401, Amount=1, Price=25, "You want to buy a turquoise, round pillow for %P gold?", Topic=81 +"square", "pillow" -> "What color would you prefer? Red, green, blue or yellow?", Topic=84 +Topic=84, "blue" -> Type=2394, Amount=1, Price=25, "You want to buy a blue, square pillow for %P gold?", Topic=81 +Topic=84, "red" -> Type=2395, Amount=1, Price=25, "You want to buy a red, square pillow for %P gold?", Topic=81 +Topic=84, "green" -> Type=2396, Amount=1, Price=25, "You want to buy a green, square pillow for %P gold?", Topic=81 +Topic=84, "yellow" -> Type=2397, Amount=1, Price=25, "You want to buy a yellow, square pillow for %P gold?", Topic=81 +"heart", "pillow" -> Type=2393, Amount=1, Price=30, "You want to buy a heart pillow for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-pottery-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-pottery-s.ndb new file mode 100644 index 0000000..55e3c8f --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-pottery-s.ndb @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-pottery-s.ndb: Datenbank für generischen Möbelverkauf - Töpfe +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"pottery" -> "I offer vases, coal basins, amphora and large amphora. What do you need?" +"vase" -> Type=2876, Amount=1, Price=3, "Do you want to buy a vase for %P gold?", Topic=81 +"large", "amphora" -> Type=2805, Amount=1, Price=50, "Do you want to buy a large amphora for %P gold?", Topic=81 +"amphora" -> Type=2893, Amount=1, Price=4, "Do you want to buy an amphora for %P gold?", Topic=81 +"coal","basin" -> Type=2806, Amount=1, Price=25, "Do you want to buy a coal basin for %P gold?", Topic=81 +"oven",ClientVersion>=790 -> Type=6371, Amount=1, Price=80, "Do you want to buy an oven for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-statues-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-statues-s.ndb new file mode 100644 index 0000000..6046b9b --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-statues-s.ndb @@ -0,0 +1,19 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-statues-s.ndb: Datenbank für generischen Möbelverkauf - Statuen +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"statue" -> "What statue would you like? A knights statue, a minotaur statue or a goblin statue?" +"knight", "statue" -> Type=2802, Amount=1, Price=50, "Do you want to buy this wonderful statue for %P gold?", Topic=81 +"minotaur", "statue" -> Type=2803, Amount=1, Price=50, "Do you want to buy this frigtening statue for %P gold?", Topic=81 +"goblin", "statue" -> Type=2804, Amount=1, Price=50, "Do you want to buy this disgusting statue for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-tables-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-tables-s.ndb new file mode 100644 index 0000000..45690c8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-tables-s.ndb @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-tables-s.ndb: Datenbank für generischen Möbelverkauf - Tische +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"table" -> "Do you want to buy a small table, a round table, a square table or a big table?" +"small", "table" -> Type=2782, Amount=1, Price=20, "Do you want to buy a small table for %P gold?", Topic=81 +"round", "table" -> Type=2783, Amount=1, Price=25, "Do you want to buy a round table for %P gold?", Topic=81 +"square", "table" -> Type=2784, Amount=1, Price=25, "Do you want to buy a square table for %P gold?", Topic=81 +"big", "table" -> Type=2785, Amount=1, Price=30, "Do you want to buy a big table for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-furniture-tapestries-s.ndb b/app/SabrehavenServer/data/npc/gen-t-furniture-tapestries-s.ndb new file mode 100644 index 0000000..73a264e --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-furniture-tapestries-s.ndb @@ -0,0 +1,26 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-tapestries-s.ndb: Datenbank für generischen Möbelverkauf - Vorhänge +# Verwendete Topics: 81, 85 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"curtain" -> "Well, actually it's better to call them tapestries." +"tapestr" -> "Please tell me what color you would prefer: purple, green, yellow, orange, red, blue or white?", Topic=85 +Topic=85, "purple" -> Type=2644, Amount=1, Price=25, "You want to buy a purple tapestry for %P gold?", Topic=81 +Topic=85, "green" -> Type=2647, Amount=1, Price=25, "You want to buy a green tapestry for %P gold?", Topic=81 +Topic=85, "yellow" -> Type=2650, Amount=1, Price=25, "You want to buy a yellow tapestry for %P gold?", Topic=81 +Topic=85, "orange" -> Type=2653, Amount=1, Price=25, "You want to buy a orange tapestry for %P gold?", Topic=81 +Topic=85, "red" -> Type=2656, Amount=1, Price=25, "You want to buy a red tapestry for %P gold?", Topic=81 +Topic=85, "orange" -> Type=2653, Amount=1, Price=25, "You want to buy a orange tapestry for %P gold?", Topic=81 +Topic=85, "red" -> Type=2656, Amount=1, Price=25, "You want to buy a red tapestry for %P gold?", Topic=81 +Topic=85, "blue" -> Type=2659, Amount=1, Price=25, "You want to buy a blue tapestry for %P gold?", Topic=81 +Topic=85, "white" -> Type=2667, Amount=1, Price=25, "You want to buy a white tapestry for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/app/SabrehavenServer/data/npc/gen-t-gear-s.ndb b/app/SabrehavenServer/data/npc/gen-t-gear-s.ndb new file mode 100644 index 0000000..67ade3c --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-gear-s.ndb @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTgearV.ndb: Datenbank für generischen Ausrüstungsverkauf + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=25 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=25 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=25 +"bag" -> Type=2862, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=25 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=25 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=25 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=25 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=25 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=25 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=25 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=25 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=25 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=25 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=25 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a dwarfish steel crowbar for %P gold?", Topic=25 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you want to' buy a water hose for %P gold?", Topic=25 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=25 +"bucket" -> Type=2873, Amount=1, Price=4, Data=0, "Do you want to buy a bucket for %P gold?", Topic=25 +"bottle" -> Type=2875, Amount=1, Price=3, Data=0, "Do you want to buy a bottle for %P gold?", Topic=25 +%1,0<%1,"torches" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=25 + +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=26 + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=27 +"vial" -> * +"flask" -> * + +Topic=25,"yes",CountMoney>=Price -> "Here it is!", DeleteMoney, Create(Type) +Topic=25,"yes" -> "Sorry, you have no money!" +Topic=25 -> "Perhaps another time." + +Topic=26,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=26,"yes" -> "Sorry, you have no money!" +Topic=26 -> "Perhaps another time." + + +Topic=27,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=27,"yes" -> "You don't have any empty vials." +Topic=27 -> "Hmm, but please keep this place litter free." + diff --git a/app/SabrehavenServer/data/npc/gen-t-gems-s.ndb b/app/SabrehavenServer/data/npc/gen-t-gems-s.ndb new file mode 100644 index 0000000..72f85d7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-gems-s.ndb @@ -0,0 +1,30 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-gems-s.ndb: Datenbank für generischen Edelsteinverkauf + + + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting rings, wedding rings, golden amulets, and ruby necklaces." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=29 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=29 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=29 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=29 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=29 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=29 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=29 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=29 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=29 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=29 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=29 +"gold","convert" -> * + +Topic=29,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=29,"yes" -> "Come back, when you have enough money." +Topic=29 -> "Hmm, but next time." + diff --git a/app/SabrehavenServer/data/npc/gen-t-helm-b.ndb b/app/SabrehavenServer/data/npc/gen-t-helm-b.ndb new file mode 100644 index 0000000..0b32d91 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-helm-b.ndb @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-helm-b.ndb: Datenbank für generischen Helmeinkauf + +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=50 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=50 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=50 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=50 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=50 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "You want sell a iron helmet for %P gold?", Topic=50 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "You want sell a devil's helmet for %P gold?", Topic=50 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "You want sell a warrior helmet for %P gold?", Topic=50 + +"sell",%1,1<%1,"leather","helm" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"chain","helm" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"steel","helm" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"brass","helm" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"viking","helm" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "You want sell %A iron helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "You want sell %A devil's helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "You want sell %A warrior helmets for %P gold?", Topic=50 + + + +Topic=50,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=50,"yes" -> "Sorry, you do not have one." +Topic=50,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=50 -> "Maybe next time." + diff --git a/app/SabrehavenServer/data/npc/gen-t-helm-s.ndb b/app/SabrehavenServer/data/npc/gen-t-helm-s.ndb new file mode 100644 index 0000000..789377a --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-helm-s.ndb @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genThelmV.ndb: Datenbank für generischen Helmverkauf + +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=20 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=20 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=21 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=21 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=21 + + +Topic=20,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=20,"yes" -> "Sorry, you do not have enough gold." +Topic=20 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-legs-s.ndb b/app/SabrehavenServer/data/npc/gen-t-legs-s.ndb new file mode 100644 index 0000000..592be82 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-legs-s.ndb @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTlegsV.ndb: Datenbank für generischen Legsverkauf + +"leather","boot" -> Type=3552, Amount=1, Price=2, "Do you want to buy one of my wonderful leather boots for %P gold?", Topic=23 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=23 +"studded","legs" -> Type=3362, Amount=1, Price=60, "Do you want to buy studded legs for %P gold?", Topic=23 +"chain","legs" -> Type=3558, Amount=1, Price=80, "You want buy chain legs for %P gold?", Topic=23 +"brass","legs" -> Type=3372, Amount=1, Price=195, "You want buy brass legs for %P gold?", Topic=23 + + +Topic=23,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=23,"yes" -> "Sorry, you do not have enough gold." +Topic=23 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-magic-s.ndb b/app/SabrehavenServer/data/npc/gen-t-magic-s.ndb new file mode 100644 index 0000000..7ab35d3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-magic-s.ndb @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-gems-s.ndb: Datenbank für generischen Edelsteinverkauf + +"offer" -> "I'm selling life and mana fluids, runes, and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=30 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=100, "Do you want to buy mana fluid for %P gold?", Topic=30 +"rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=31 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=31 + +%1,0<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=30 +%1,0<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=100*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=30 +%1,0<%1,"runes" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=31 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=34 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=34 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=33 +"bp","blank","rune" -> * + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=34 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=34 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=33 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=32 +"vial" -> * +"flask" -> * + +Topic=31,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=31,"yes" -> "Come back, when you have enough money." +Topic=31 -> "Hmm, but next time." + +Topic=30,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=30,"yes" -> "Come back, when you have enough money." +Topic=30 -> "Hmm, but next time." + +Topic=32,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=32,"yes" -> "You don't have any empty vials." +Topic=32 -> "Hmm, but please keep Tibia litter free." + +Topic=33,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=33,"yes" -> "Come back, when you have enough money." +Topic=33 -> "Hmm, but next time." + +Topic=34,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=34,"yes" -> "Come back, when you have enough money." +Topic=34 -> "Hmm, but next time." diff --git a/app/SabrehavenServer/data/npc/gen-t-meat-s.ndb b/app/SabrehavenServer/data/npc/gen-t-meat-s.ndb new file mode 100644 index 0000000..ecf8944 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-meat-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-tmeat-s.ndb: Datenbank für generischen Fleischverkauf + + +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=39 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=39 + + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=39 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=39 + +Topic=39,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=39,"yes" -> "I am sorry, but you do not have enough gold." +Topic=39 -> "Maybe later." diff --git a/app/SabrehavenServer/data/npc/gen-t-music-s.ndb b/app/SabrehavenServer/data/npc/gen-t-music-s.ndb new file mode 100644 index 0000000..f8ca119 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-music-s.ndb @@ -0,0 +1,17 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTshieldV.ndb: Datenbank für generischen Instrumentenverkauf + +"offer" -> "You can buy a lyre, lute, drum, and simple fanfare." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=38 +"lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=38 +"drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=38 +"simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=38 + +Topic=38,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=38,"yes" -> "Come back, when you have enough money." +Topic=38 -> "Hmm, but next time." diff --git a/app/SabrehavenServer/data/npc/gen-t-runes-free-s.ndb b/app/SabrehavenServer/data/npc/gen-t-runes-free-s.ndb new file mode 100644 index 0000000..14bcb27 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-runes-free-s.ndb @@ -0,0 +1,146 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-runes-free-s.ndb: Datenbank für generischen Runenverkauf - Free Account Runen +# Verwendete Topics: 99 + +"spell","rune" -> "I sell missile runes, explosive runes, field runes, wall runes, bomb runes, healing runes, convince creature runes and chameleon runes." +"missile","rune" -> "I can offer you light magic missile runes, heavy magic missile runes and sudden death runes." +"explosive","rune" -> "I can offer you fireball runes, great fireball runes and explosion runes." +"field","rune" -> "I can offer you fire field runes, energy field runes, poison field runes and destroy field runes." +"wall","rune" -> "I can offer you fire wall runes, energy wall runes and poison wall runes." +"bomb","rune" -> "I can offer you firebomb runes." +"healing","rune" -> "I can offer you antidote runes, intense healing runes and ultimate healing runes." + +"light","magic","missile","rune" -> Type=3174, Data=5, Amount=1, Price=40, "Do you want to buy a light magic missile rune for %P gold?", Topic=99 +"poison","field","rune" -> Type=3172, Data=3, Amount=1, Price=65, "Do you want to buy a poison field rune for %P gold?", Topic=99 +"antidote","rune" -> Type=3153, Data=1, Amount=1, Price=65, "Do you want to buy an antidote rune for %P gold?", Topic=99 +"fire","field","rune" -> Type=3188, Data=3, Amount=1, Price=85, "Do you want to buy a fire field rune for %P gold?", Topic=99 +"intense","healing","rune" -> Type=3152, Data=1, Amount=1, Price=95, "Do you want to buy an intense healing rune for %P gold?", Topic=99 +"fireball","rune" -> Type=3189, Data=2, Amount=1, Price=95, "Do you want to buy a fireball rune for %P gold?", Topic=99 +"destroy","field","rune" -> Type=3148, Data=3, Amount=1, Price=45, "Do you want to buy a destroy field rune for %P gold?", Topic=99 +"heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=1, Price=125, "Do you want to buy a heavy magic missile rune for %P gold?", Topic=99 +"energy","field","rune" -> Type=3164, Data=3, Amount=1, Price=115, "Do you want to buy an energy field rune for %P gold?", Topic=99 +"ultimate","healing","rune" -> Type=3160, Data=1, Amount=1, Price=175, "Do you want to buy an ultimate healing rune for %P gold?", Topic=99 +"convince","creature","rune" -> Type=3177, Data=1, Amount=1, Price=80, "Do you want to buy a convince creature rune for %P gold?", Topic=99 +"great","fireball","rune" -> Type=3191, Data=2, Amount=1, Price=180, "Do you want to buy a great fireball rune for %P gold?", Topic=99 +"chameleon","rune" -> Type=3178, Data=1, Amount=1, Price=210, "Do you want to buy a chameleon rune for %P gold?", Topic=99 +"fire","bomb","rune" -> Type=3192, Data=2, Amount=1, Price=235, "Do you want to buy a firebomb rune for %P gold?", Topic=99 +"poison","wall","rune" -> Type=3176, Data=4, Amount=1, Price=210, "Do you want to buy a poison wall rune for %P gold?", Topic=99 +"explosion","rune" -> Type=3200, Data=3, Amount=1, Price=250, "Do you want to buy an explosion rune for %P gold?", Topic=99 +"fire","wall","rune" -> Type=3190, Data=4, Amount=1, Price=245, "Do you want to buy a fire wall rune for %P gold?", Topic=99 +"sudden","death","rune" -> Type=3155, Data=1, Amount=1, Price=325, "Do you want to buy a sudden death rune for %P gold?", Topic=99 +"energy","wall","rune" -> Type=3166, Data=4, Amount=1, Price=340, "Do you want to buy an energy wall rune for %P gold?", Topic=99 + +"backpack","light","magic","missile","rune" -> Type=3174, Data=5, Amount=1, Price=41*20, "Do you want to buy a backpack of light magic missile rune for %P gold?", Topic=100 +"bp","light","magic","missile","rune" -> * +"backpack","poison","field","rune" -> Type=3172, Data=3, Amount=1, Price=66*20, "Do you want to buy a backpack of poison field rune for %P gold?", Topic=100 +"bp","poison","field","rune" -> * +"backpack","antidote","rune" -> Type=3153, Data=1, Amount=1, Price=66*20, "Do you want to buy a backpack of antidote rune for %P gold?", Topic=100 +"bp","antidote","rune" -> * +"backpack","fire","field","rune" -> Type=3188, Data=3, Amount=1, Price=86*20, "Do you want to buy a backpack of fire field rune for %P gold?", Topic=100 +"bp","fire","field","rune" -> * +"backpack","intense","healing","rune" -> Type=3152, Data=1, Amount=1, Price=96*20, "Do you want to buy a backpack of intense healing rune for %P gold?", Topic=100 +"bp","intense","healing","rune" -> * +"backpack","fireball","rune" -> Type=3189, Data=2, Amount=1, Price=96*20, "Do you want to buy a backpack of fireball rune for %P gold?", Topic=100 +"bp","fireball","rune" -> * +"backpack","destroy","field","rune" -> Type=3148, Data=3, Amount=1, Price=46*20, "Do you want to buy a backpack of destroy field rune for %P gold?", Topic=100 +"bp","destroy","field","rune" -> * +"backpack","heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=1, Price=126*20, "Do you want to buy a backpack of heavy magic missile rune for %P gold?", Topic=100 +"bp","heavy","magic","missile","rune" -> * +"backpack","energy","field","rune" -> Type=3164, Data=3, Amount=1, Price=116*20, "Do you want to buy a backpack of energy field rune for %P gold?", Topic=100 +"bp","energy","field","rune" -> * +"backpack","ultimate","healing","rune" -> Type=3160, Data=1, Amount=1, Price=176*20, "Do you want to buy a backpack of ultimate healing rune for %P gold?", Topic=100 +"bp","ultimate","healing","rune" -> * +"backpack","convince","creature","rune" -> Type=3177, Data=1, Amount=1, Price=81*20, "Do you want to buy a backpack of convince creature rune for %P gold?", Topic=100 +"bp","convince","creature","rune" -> * +"backpack","great","fireball","rune" -> Type=3191, Data=2, Amount=1, Price=181*20, "Do you want to buy a backpack of great fireball rune for %P gold?", Topic=100 +"bp","great","fireball","rune" -> * +"backpack","chameleon","rune" -> Type=3178, Data=1, Amount=1, Price=211*20, "Do you want to buy a backpack of chameleon rune for %P gold?", Topic=100 +"bp","chameleon","rune" -> * +"backpack","fire","bomb","rune" -> Type=3192, Data=2, Amount=1, Price=236*20, "Do you want to buy a backpack of firebomb rune for %P gold?", Topic=100 +"bp","fire","bomb","rune" -> * +"backpack","poison","wall","rune" -> Type=3176, Data=4, Amount=1, Price=211*20, "Do you want to buy a backpack of poison wall rune for %P gold?", Topic=100 +"bp","poison","wall","rune" -> * +"backpack","explosion","rune" -> Type=3200, Data=3, Amount=1, Price=251*20, "Do you want to buy a backpack of explosion rune for %P gold?", Topic=100 +"bp","explosion","rune" -> * +"backpack","fire","wall","rune" -> Type=3190, Data=4, Amount=1, Price=246*20, "Do you want to buy a backpack of fire wall rune for %P gold?", Topic=100 +"bp","fire","wall","rune" -> * +"backpack","sudden","death","rune" -> Type=3155, Data=1, Amount=1, Price=325*20, "Do you want to buy a backpack of sudden death rune for %P gold?", Topic=100 +"bp","sudden","death","rune" -> * +"backpack","energy","wall","rune" -> Type=3166, Data=4, Amount=1, Price=341*20, "Do you want to buy a backpack of energy wall rune for %P gold?", Topic=100 +"bp","energy","wall","rune" -> * + +"envenom","rune" -> "Sorry, but runes of this type can't be purchased here." +"desintegrate","rune" -> * +"poison","bomb","rune" -> * +"soulfire","rune" -> * +"energy","bomb","rune" -> * +"magic","wall","rune" -> * +"animate","dead","rune" -> * +"paralyze","rune" -> * + +%1,1<%1,"light","magic","missile","rune" -> Type=3174, Data=5, Amount=%1, Price=40*%1, "Do you want to buy %A light magic missile runes for %P gold?", Topic=99 +%1,1<%1,"poison","field","rune" -> Type=3172, Data=3, Amount=%1, Price=65*%1, "Do you want to buy %A poison field runes for %P gold?", Topic=99 +%1,1<%1,"antidote","rune" -> Type=3153, Data=1, Amount=%1, Price=65*%1, "Do you want to buy %A antidote runes for %P gold?", Topic=99 +%1,1<%1,"fire","field","rune" -> Type=3188, Data=3, Amount=%1, Price=85*%1, "Do you want to buy %A fire field runes for %P gold?", Topic=99 +%1,1<%1,"intense","healing","rune" -> Type=3152, Data=1, Amount=%1, Price=95*%1, "Do you want to buy %A intense healing runes for %P gold?", Topic=99 +%1,1<%1,"fireball","rune" -> Type=3189, Data=2, Amount=%1, Price=95*%1, "Do you want to buy %A fireball runes for %P gold?", Topic=99 +%1,1<%1,"destroy","field","rune" -> Type=3148, Data=3, Amount=%1, Price=45*%1, "Do you want to buy %A destroy field runes for %P gold?", Topic=99 +%1,1<%1,"heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=%1, Price=125*%1, "Do you want to buy %A heavy magic missile runes for %P gold?", Topic=99 +%1,1<%1,"energy","field","rune" -> Type=3164, Data=3, Amount=%1, Price=115*%1, "Do you want to buy %A energy field runes for %P gold?", Topic=99 +%1,1<%1,"ultimate","healing","rune" -> Type=3160, Data=1, Amount=%1, Price=175*%1, "Do you want to buy %A ultimate healing runes for %P gold?", Topic=99 +%1,1<%1,"convince","creature","rune" -> Type=3177, Data=1, Amount=%1, Price=80*%1, "Do you want to buy %A convince creature runes for %P gold?", Topic=99 +%1,1<%1,"great","fireball","rune" -> Type=3191, Data=2, Amount=%1, Price=180*%1, "Do you want to buy %A great fireball runes for %P gold?", Topic=99 +%1,1<%1,"chameleon","rune" -> Type=3178, Data=1, Amount=%1, Price=210*%1, "Do you want to buy %A chameleon runes for %P gold?", Topic=99 +%1,1<%1,"fire","bomb","rune" -> Type=3192, Data=2, Amount=%1, Price=235*%1, "Do you want to buy %A firebomb runes for %P gold?", Topic=99 +%1,1<%1,"poison","wall","rune" -> Type=3176, Data=4, Amount=%1, Price=210*%1, "Do you want to buy %A poison wall runes for %P gold?", Topic=99 +%1,1<%1,"explosion","rune" -> Type=3200, Data=3, Amount=%1, Price=250*%1, "Do you want to buy %A explosion runes for %P gold?", Topic=99 +%1,1<%1,"fire","wall","rune" -> Type=3190, Data=4, Amount=%1, Price=245*%1, "Do you want to buy %A fire wall runes for %P gold?", Topic=99 +%1,1<%1,"sudden","death","rune" -> Type=3155, Data=1, Amount=%1, Price=325*%1, "Do you want to buy %A sudden death runes for %P gold?", Topic=99 +%1,1<%1,"energy","wall","rune" -> Type=3166, Data=4, Amount=%1, Price=340*%1, "Do you want to buy %A energy wall runes for %P gold?", Topic=99 + +%1,1<%1,"backpack","light","magic","missile","rune" -> Type=3174, Data=5, Amount=%1, Price=41*20*%1, "Do you want to buy %A backpacks of light magic missile runes for %P gold?", Topic=100 +%1,1<%1,"bp","light","magic","missile","rune" -> * +%1,1<%1,"backpack","poison","field","rune" -> Type=3172, Data=3, Amount=%1, Price=66*20*%1, "Do you want to buy %A backpacks of poison field runes for %P gold?", Topic=100 +%1,1<%1,"bp","poison","field","rune" -> * +%1,1<%1,"backpack","antidote","rune" -> Type=3153, Data=1, Amount=%1, Price=66*20*%1, "Do you want to buy %A backpacks of antidote runes for %P gold?", Topic=100 +%1,1<%1,"bp","antidote","rune" -> * +%1,1<%1,"backpack","fire","field","rune" -> Type=3188, Data=3, Amount=%1, Price=86*20*%1, "Do you want to buy %A backpacks of fire field runes for %P gold?", Topic=100 +%1,1<%1,"bp","fire","field","rune" -> * +%1,1<%1,"backpack","intense","healing","rune" -> Type=3152, Data=1, Amount=%1, Price=96*20*%1, "Do you want to buy %A backpacks of intense healing runes for %P gold?", Topic=100 +%1,1<%1,"bp","intense","healing","rune" -> * +%1,1<%1,"backpack","fireball","rune" -> Type=3189, Data=2, Amount=%1, Price=96*20*%1, "Do you want to buy %A backpacks of fireball runes for %P gold?", Topic=100 +%1,1<%1,"bp","fireball","rune" -> * +%1,1<%1,"backpack","destroy","field","rune" -> Type=3148, Data=3, Amount=%1, Price=46*20*%1, "Do you want to buy %A backpacks of destroy field runes for %P gold?", Topic=100 +%1,1<%1,"bp","destroy","field","rune" -> * +%1,1<%1,"backpack","heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=%1, Price=126*20*%1, "Do you want to buy %A backpacks of heavy magic missile runes for %P gold?", Topic=100 +%1,1<%1,"bp","heavy","magic","missile","rune" -> * +%1,1<%1,"backpack","energy","field","rune" -> Type=3164, Data=3, Amount=%1, Price=116*20*%1, "Do you want to buy %A backpacks of energy field runes for %P gold?", Topic=100 +%1,1<%1,"bp","energy","field","rune" -> * +%1,1<%1,"backpack","ultimate","healing","rune" -> Type=3160, Data=1, Amount=%1, Price=176*20*%1, "Do you want to buy %A backpacks of ultimate healing runes for %P gold?", Topic=100 +%1,1<%1,"bp","ultimate","healing","rune" -> * +%1,1<%1,"backpack","convince","creature","rune" -> Type=3177, Data=1, Amount=%1, Price=81*20*%1, "Do you want to buy %A backpacks of convince creature runes for %P gold?", Topic=100 +%1,1<%1,"bp","convince","creature","rune" -> * +%1,1<%1,"backpack","great","fireball","rune" -> Type=3191, Data=2, Amount=%1, Price=181*20*%1, "Do you want to buy %A backpacks of great fireball runes for %P gold?", Topic=100 +%1,1<%1,"bp","great","fireball","rune" -> * +%1,1<%1,"backpack","chameleon","rune" -> Type=3178, Data=1, Amount=%1, Price=211*20*%1, "Do you want to buy %A backpacks of chameleon runes for %P gold?", Topic=100 +%1,1<%1,"bp","chameleon","rune" -> * +%1,1<%1,"backpack","fire","bomb","rune" -> Type=3192, Data=2, Amount=%1, Price=236*20*%1, "Do you want to buy %A backpacks of firebomb runes for %P gold?", Topic=100 +%1,1<%1,"bp","fire","bomb","rune" -> * +%1,1<%1,"backpack","poison","wall","rune" -> Type=3176, Data=4, Amount=%1, Price=211*20*%1, "Do you want to buy %A backpacks of poison wall runes for %P gold?", Topic=100 +%1,1<%1,"bp","poison","wall","rune" -> * +%1,1<%1,"backpack","explosion","rune" -> Type=3200, Data=3, Amount=%1, Price=251*20*%1, "Do you want to buy %A backpacks of explosion runes for %P gold?", Topic=100 +%1,1<%1,"bp","explosion","rune" -> * +%1,1<%1,"backpack","fire","wall","rune" -> Type=3190, Data=4, Amount=%1, Price=246*20*%1, "Do you want to buy %A backpacks of fire wall runes for %P gold?", Topic=100 +%1,1<%1,"bp","fire","wall","rune" -> * +%1,1<%1,"backpack","sudden","death","rune" -> Type=3155, Data=1, Amount=%1, Price=326*20*%1, "Do you want to buy %A backpacks of sudden death runes for %P gold?", Topic=100 +%1,1<%1,"bp","sudden","death","rune" -> * +%1,1<%1,"backpack","energy","wall","rune" -> Type=3166, Data=4, Amount=%1, Price=341*20*%1, "Do you want to buy %A backpacks of energy wall runes for %P gold?", Topic=100 +%1,1<%1,"bp","energy","wall","rune" -> * + +Topic=99,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=99,"yes" -> "Sorry, you don't have enough gold." +Topic=99 -> "As you wish." + +Topic=100,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=100,"yes" -> "Sorry, you don't have enough gold." +Topic=100 -> "As you wish." \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/gen-t-runes-prem-s.ndb b/app/SabrehavenServer/data/npc/gen-t-runes-prem-s.ndb new file mode 100644 index 0000000..f48aa1e --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-runes-prem-s.ndb @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-runes-prem-s.ndb: Datenbank für generischen Runenverkauf - Premium Account Runen +# Verwendete Topics: 99 + +"spell","rune" -> "I sell envenom runes, desintegrate runes, poison bomb runes, energy bomb runes, soulfire runes, magic wall runes, animate dead runes and paralyze runes." + +"envenom","rune" -> Type=3179, Data=3, Amount=1, Price=130, "Do you want to buy an envenom rune for %P gold?", Topic=99 +"desintegrate","rune" -> Type=3197, Data=3, Amount=1, Price=80, "Do you want to buy a desintegrate rune for %P gold?", Topic=99 +"poison","bomb","rune" -> Type=3173, Data=2, Amount=1, Price=170, "Do you want to buy a poison bomb rune for %P gold?", Topic=99 +"soulfire","rune" -> Type=3195, Data=2, Amount=1, Price=210, "Do you want to buy a soulfire rune for %P gold?", Topic=99 +"energy","bomb","rune" -> Type=3149, Data=2, Amount=1, Price=325, "Do you want to buy an energybomb rune for %P gold?", Topic=99 +"magic","wall","rune" -> Type=3180, Data=3, Amount=1, Price=350, "Do you want to buy a magic wall rune for %P gold?", Topic=99 +"animate","dead","rune" -> Type=3203, Data=1, Amount=1, Price=375, "Do you want to buy an animate dead rune for %P gold?", Topic=99 +"paralyze","rune" -> Type=3165, Data=1, Amount=1, Price=700, "Do you want to buy a paralyze rune for %P gold?", Topic=99 + +"backpack","envenom","rune" -> Type=3179, Data=3, Amount=1, Price=131*20, "Do you want to buy a backpack of envenom rune for %P gold?", Topic=100 +"bp","envenom","rune" -> * +"backpack","desintegrate","rune" -> Type=3197, Data=3, Amount=1, Price=81*20, "Do you want to buy a backpack of desintegrate rune for %P gold?", Topic=100 +"bp","desintegrate","rune" -> * +"backpack","poison","bomb","rune" -> Type=3173, Data=2, Amount=1, Price=171*20, "Do you want to buy a backpack of poison bomb rune for %P gold?", Topic=100 +"bp","poison","bomb","rune" -> * +"backpack","soulfire","rune" -> Type=3195, Data=2, Amount=1, Price=211*20, "Do you want to buy a backpack of soulfire rune for %P gold?", Topic=100 +"bp","soulfire","rune" -> * +"backpack","energy","bomb","rune" -> Type=3149, Data=2, Amount=1, Price=326*20, "Do you want to buy a backpack of energybomb rune for %P gold?", Topic=100 +"bp","energy","bomb","rune" -> * +"backpack","magic","wall","rune" -> Type=3180, Data=3, Amount=1, Price=351*20, "Do you want to buy a backpack of magic wall rune for %P gold?", Topic=100 +"bp","magic","wall","rune" -> * +"backpack","animate","dead","rune" -> Type=3203, Data=1, Amount=1, Price=376*20, "Do you want to buy a backpack of animate dead rune for %P gold?", Topic=100 +"bp","animate","dead","rune" -> * +"backpack","paralyze","rune" -> Type=3165, Data=1, Amount=1, Price=701*20, "Do you want to buy a backpack of paralyze rune for %P gold?", Topic=100 +"bp","paralyze","rune" -> * + +%1,1<%1,"envenom","rune" -> Type=3179, Data=3, Amount=%1, Price=130*%1, "Do you want to buy %A envenom runes for %P gold?", Topic=99 +%1,1<%1,"desintegrate","rune" -> Type=3197, Data=3, Amount=%1, Price=80*%1, "Do you want to buy %A desintegrate runes for %P gold?", Topic=99 +%1,1<%1,"poison","bomb","rune" -> Type=3173, Data=2, Amount=%1, Price=170*%1, "Do you want to buy %A poison bomb runes for %P gold?", Topic=99 +%1,1<%1,"soulfire","rune" -> Type=3195, Data=2, Amount=%1, Price=210*%1, "Do you want to buy %A soulfire runes for %P gold?", Topic=99 +%1,1<%1,"energy","bomb","rune" -> Type=3149, Data=2, Amount=%1, Price=325*%1, "Do you want to buy %A energybomb runes for %P gold?", Topic=99 +%1,1<%1,"magic","wall","rune" -> Type=3180, Data=3, Amount=%1, Price=350*%1, "Do you want to buy %A magic wall runes for %P gold?", Topic=99 +%1,1<%1,"animate","dead","rune" -> Type=3203, Data=1, Amount=%1, Price=375*%1, "Do you want to buy %A animate dead runes for %P gold?", Topic=99 +%1,1<%1,"paralyze","rune" -> Type=3165, Data=1, Amount=%1, Price=700*%1, "Do you want to buy %A paralyze runes for %P gold?", Topic=99 + +%1,1<%1,"backpack","envenom","rune" -> Type=3179, Data=3, Amount=%1, Price=131*20*%1, "Do you want to buy %A backpacks of envenom runes for %P gold?", Topic=100 +%1,1<%1,"bp","envenom","rune" -> * +%1,1<%1,"backpack","desintegrate","rune" -> Type=3197, Data=3, Amount=%1, Price=81*20*%1, "Do you want to buy %A backpacks of desintegrate runes for %P gold?", Topic=100 +%1,1<%1,"bp","desintegrate","rune" -> * +%1,1<%1,"backpack","poison","bomb","rune" -> Type=3173, Data=2, Amount=%1, Price=171*20*%1, "Do you want to buy %A backpacks of poison bomb runes for %P gold?", Topic=100 +%1,1<%1,"bp","poison","bomb","rune" -> * +%1,1<%1,"backpack","soulfire","rune" -> Type=3195, Data=2, Amount=%1, Price=211*20*%1, "Do you want to buy %A backpacks of soulfire runes for %P gold?", Topic=100 +%1,1<%1,"bp","soulfire","rune" -> * +%1,1<%1,"backpack","energy","bomb","rune" -> Type=3149, Data=2, Amount=%1, Price=326*20*%1, "Do you want to buy %A backpacks of energybomb runes for %P gold?", Topic=100 +%1,1<%1,"bp","energy","bomb","rune" -> * +%1,1<%1,"backpack","magic","wall","rune" -> Type=3180, Data=3, Amount=%1, Price=351*20*%1, "Do you want to buy %A backpacks of magic wall runes for %P gold?", Topic=100 +%1,1<%1,"bp","magic","wall","rune" -> * +%1,1<%1,"backpack","animate","dead","rune" -> Type=3203, Data=1, Amount=%1, Price=376*20*%1, "Do you want to buy %A backpacks of animate dead runes for %P gold?", Topic=100 +%1,1<%1,"bp","animate","dead","rune" -> * +%1,1<%1,"backpack","paralyze","rune" -> Type=3165, Data=1, Amount=%1, Price=701*20*%1, "Do you want to buy %A backpacks of paralyze runes for %P gold?", Topic=100 +%1,1<%1,"bp","paralyze","rune" -> * + +"light","magic","missile","rune" -> "Sorry, but runes of this type can't be purchased here." +"poison","field","rune" -> * +"antidote","rune" -> * +"fire","field","rune" -> * +"intense","healing","rune" -> * +"fireball","rune" -> * +"destroy","field","rune" -> * +"heavy","magic","missile","rune" -> * +"energy","field","rune" -> * +"ultimate","healing","rune" -> * +"convince","creature","rune" -> * +"great","fireball","rune" -> * +"chameleon","rune" -> * +"fire","bomb","rune" -> * +"poison","wall","rune" -> * +"explosion","rune" -> * +"fire","wall","rune" -> * +"sudden","death","rune" -> * +"energy","wall","rune" -> * + + +Topic=99,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=99,"yes" -> "Sorry, you don't have enough gold." +Topic=99 -> "As you wish." + +Topic=100,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=100,"yes" -> "Sorry, you don't have enough gold." +Topic=100 -> "As you wish." diff --git a/app/SabrehavenServer/data/npc/gen-t-shield-b.ndb b/app/SabrehavenServer/data/npc/gen-t-shield-b.ndb new file mode 100644 index 0000000..39339eb --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-shield-b.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-shield-b.ndb: Datenbank für generischen Schildkauf + +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=52 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=52 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=52 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=52 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=52 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=100, "You want sell a dwarven shield for %P gold?", Topic=52 +"sell","guardians","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell this for %P gold?", Topic=52 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell this for %P gold?", Topic=52 + + +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell those wooden shields for %P gold?", Topic=52 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell those battle shields for %P gold?", Topic=52 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell those steel shields for %P gold?", Topic=52 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell those brass shields for %P gold?", Topic=52 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell those plate shields for %P gold?", Topic=52 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=100*%1, "Do you want sell those dwarven shields for %P gold?", Topic=52 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell those guardian shields for %P gold?", Topic=52 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell those dragon shields for %P gold?", Topic=52 + + + +Topic=52,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=52,"yes" -> "Sorry, you do not have enough gold." +Topic=52 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-shield-s.ndb b/app/SabrehavenServer/data/npc/gen-t-shield-s.ndb new file mode 100644 index 0000000..98174c3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-shield-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTshieldV.ndb: Datenbank für generischen Schildverkauf + +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=22 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=22 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=22 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=22 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=22 +"dwarven","shield" -> Type=3425, Amount=1, Price=500, "Do you want to buy a dwarven shield for %P gold?", Topic=22 + + +Topic=22,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=22,"yes" -> "Sorry, you do not have enough gold." +Topic=22 -> "Maybe you will buy it another time." diff --git a/app/SabrehavenServer/data/npc/gen-t-wands-free-s.ndb b/app/SabrehavenServer/data/npc/gen-t-wands-free-s.ndb new file mode 100644 index 0000000..7c21b09 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-wands-free-s.ndb @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-wands-free-s.ndb: Datenbank für generischen Zauberstabverkauf - Free Account Wands +# Verwendete Topics: 90,91,92 + +"wand" -> "Wands can be wielded by sorcerers only and have a certain level requirement. There are five different wands, would you like to hear about them?", Topic=90 +"rod" -> "Rods can be wielded by druids only and have a certain level requirement. There are five different rods, would you like to hear about them?", Topic=91 + +Topic=90,"yes" -> "The names of the wands are 'Wand of Vortex', 'Wand of Dragonbreath', 'Wand of Plague', 'Wand of Cosmic Energy' and 'Wand of Inferno'. Which one would you like to buy?" +Topic=90,"no" -> "Maybe another time." +Topic=90 -> "Maybe another time." + +Topic=91,"yes" -> "The names of the rods are 'Snakebite Rod', 'Moonlight Rod', 'Volcanic Rod', 'Quagmire Rod', and 'Tempest Rod'. Which one would you like to buy?" +Topic=91,"no" -> "Maybe another time." +Topic=91 -> "Maybe another time." + +sorcerer,"wand","of","vortex",QuestValue(333)<1 -> "Oh, is this your first wand of vortex? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"snakebite","rod",QuestValue(333)<1 -> "Oh, is this your first snakebite rod? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"wand","of","vortex" -> Type=3074, Amount=1, Price=500, "This wand is only for sorcerers of level 7 and above. Would you like to buy a wand of vortex for %P gold?", Topic=92 +"wand","of","dragonbreath" -> Type=3075, Amount=1, Price=1000, "This wand is only for sorcerers of level 13 and above. Would you like to buy a wand of dragonbreath for %P gold?", Topic=92 +"wand","of","plague" -> Type=3072, Amount=1, Price=5000, "This wand is only for sorcerers of level 19 and above. Would you like to buy a wand of plague for %P gold?", Topic=92 +"wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=10000, "This wand is only for sorcerers of level 26 and above. Would you like to buy a wand of cosmic energy for %P gold?", Topic=92 +"wand","of","inferno" -> "Sorry, this wand contains magic far too powerful and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + +"snakebite","rod" -> Type=3066, Amount=1, Price=500, "This rod is only for druids of level 7 and above. Would you like to buy a snakebite rod for %P gold?", Topic=92 +"moonlight","rod" -> Type=3070, Amount=1, Price=1000, "This rod is only for druids of level 13 and above. Would you like to buy a moonlight rod for %P gold?", Topic=92 +"volcanic","rod" -> Type=3069, Amount=1, Price=5000, "This rod is only for druids of level 19 and above. Would you like to buy a volcanic rod for %P gold?", Topic=92 +"quagmire","rod" -> Type=3065, Amount=1, Price=10000, "This rod is only for druids of level 26 and above. Would you like to buy a quagmire rod for %P gold?", Topic=92 +"tempest","rod" -> "Sorry, this rod contains magic far too powerful and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + +%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=500*%1, "This wand is only for sorcerers of level 7 and above. Would you like to buy %A wands of vortex for %P gold?", Topic=92 +%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=1000*%1, "This wand is only for sorcerers of level 13 and above. Would you like to buy %A wands of dragonbreath for %P gold?", Topic=92 +%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=5000*%1, "This wand is only for sorcerers of level 19 and above. Would you like to buy %A wands of plague for %P gold?", Topic=92 +%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=10000*%1, "This wand is only for sorcerers of level 26 and above. Would you like to buy %A wands of cosmic energy for %P gold?", Topic=92 +%1,1<%1,"wand","of","inferno" -> "Sorry, this wand contains far too powerful magic and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + + +%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=500*%1, "This rod is only for druids of level 7 and above. Would you like to buy %A snakebite rods for %P gold?", Topic=92 +%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=1000*%1, "This rod is only for druids of level 13 and above. Would you like to buy %A moonlight rods for %P gold?", Topic=92 +%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=5000*%1, "This rod is only for druids of level 19 and above. Would you like to buy %A volcanic rods for %P gold?", Topic=92 +%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=10000*%1, "This rod is only for druids of level 26 and above. Would you like to buy %A quagmire rods for %P gold?", Topic=92 +%1,1<%1,"tempest","rod" -> "Sorry, this rod contains far too powerful magic and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + + +Topic=92,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=92,"yes" -> "Sorry, you don't have enough gold." +Topic=92 -> "You don't know what you're missing." + +"task",QuestValue(17646)=0,druid -> Amount=17645, "Young druid, I see you wan't your second rod but those are too expensive, right. Hmm... I can't give you one for free. ...", + "However, if you could kill 50 amazons to prove your trustworthy willingness I will reward you the moonlight rod. Deal?", Topic=120 + +"task",QuestValue(17646)=0,sorcerer -> Amount=17645, "Young sorcerer, I see you wan't your second wand but those are too expensive, right. Hmm... I can't give you one for free. ...", + "However, if you could kill 50 amazons to prove your trustworthy willingness I will reward you the wand of dragonbreath. Deal?", Topic=120 + +"task",QuestValue(17645)=50,QuestValue(17646)=1,druid -> "Well done, %N. Here is your moonlight rod!", SetQuestValue(17646,2), SetQuestValue(17644,0), Type=3070, Amount=1,Create(Type) +"task",QuestValue(17645)=50,QuestValue(17646)=1,sorcerer -> "Well done, %N. Here is your wand of dragonbreath!", SetQuestValue(17646,2), SetQuestValue(17644,0), Type=3075, Amount=1,Create(Type) + +"task",QuestValue(17644)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young mage. Come back once you are done.", SetQuestValue(17644,Amount), SetQuestValue(Amount,0), SetQuestValue(17646,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/gen-t-wands-prem-s.ndb b/app/SabrehavenServer/data/npc/gen-t-wands-prem-s.ndb new file mode 100644 index 0000000..07fc81b --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-wands-prem-s.ndb @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-wands-prem-s.ndb: Datenbank für generischen Zauberstabverkauf - Premium Account Wands +# Verwendete Topics: 90,91,92 + +"wand" -> "Wands can be wielded by sorcerers only and have a certain level requirement. There are five different wands, would you like to hear about them?", Topic=90 +"rod" -> "Rods can be wielded by druids only and have a certain level requirement. There are five different rods, would you like to hear about them?", Topic=91 + +Topic=90,"yes" -> "The names of the wands are 'Wand of Vortex', 'Wand of Dragonbreath', 'Wand of Plague', 'Wand of Cosmic Energy' and 'Wand of Inferno'. Which one would you like to buy?" +Topic=90,"no" -> "Maybe another time." +Topic=90 -> "Maybe another time." + +Topic=91,"yes" -> "The names of the rods are 'Snakebite Rod', 'Moonlight Rod', 'Volcanic Rod', 'Quagmire Rod', and 'Tempest Rod'. Which one would you like to buy?" +Topic=91,"no" -> "Maybe another time." +Topic=91 -> "Maybe another time." + +sorcerer,"wand","of","vortex",QuestValue(333)<1 -> "Oh, is this your first wand of vortex? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"snakebite","rod",QuestValue(333)<1 -> "Oh, is this your first snakebite rod? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"wand","of","vortex" -> Type=3074, Amount=1, Price=500, "This wand is only for sorcerers of level 7 and above. Would you like to buy a wand of vortex for %P gold?", Topic=92 +"wand","of","dragonbreath" -> Type=3075, Amount=1, Price=1000, "This wand is only for sorcerers of level 13 and above. Would you like to buy a wand of dragonbreath for %P gold?", Topic=92 +"wand","of","plague" -> Type=3072, Amount=1, Price=5000, "This wand is only for sorcerers of level 19 and above. Would you like to buy a wand of plague for %P gold?", Topic=92 +"wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=10000, "This wand is only for sorcerers of level 26 and above. Would you like to buy a wand of cosmic energy for %P gold?", Topic=92 +"wand","of","inferno" -> Type=3071, Amount=1, Price=15000, "This wand is only for sorcerers of level 33 and above. Would you like to buy a wand of inferno for %P gold?", Topic=92 + +"snakebite","rod" -> Type=3066, Amount=1, Price=500, "This rod is only for druids of level 7 and above. Would you like to buy a snakebite rod for %P gold?", Topic=92 +"moonlight","rod" -> Type=3070, Amount=1, Price=1000, "This rod is only for druids of level 13 and above. Would you like to buy a moonlight rod for %P gold?", Topic=92 +"volcanic","rod" -> Type=3069, Amount=1, Price=5000, "This rod is only for druids of level 19 and above. Would you like to buy a volcanic rod for %P gold?", Topic=92 +"quagmire","rod" -> Type=3065, Amount=1, Price=10000, "This rod is only for druids of level 26 and above. Would you like to buy a quagmire rod for %P gold?", Topic=92 +"tempest","rod" -> Type=3067, Amount=1, Price=15000, "This rod is only for druids of level 33 and above. Would you like to buy a tempest rod for %P gold?", Topic=92 + +%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=500*%1, "This wand is only for sorcerers of level 7 and above. Would you like to buy %A wands of vortex for %P gold?", Topic=92 +%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=1000*%1, "This wand is only for sorcerers of level 13 and above. Would you like to buy %A wands of dragonbreath for %P gold?", Topic=92 +%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=5000*%1, "This wand is only for sorcerers of level 19 and above. Would you like to buy %A wands of plague for %P gold?", Topic=92 +%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=10000*%1, "This wand is only for sorcerers of level 26 and above. Would you like to buy %A wands of cosmic energy for %P gold?", Topic=92 +%1,1<%1,"wand","of","inferno" -> Type=3071, Amount=%1, Price=15000*%1, "This wand is only for sorcerers of level 33 and above. Would you like to buy %A wands of inferno for %P gold?", Topic=92 + +%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=500*%1, "This rod is only for druids of level 7 and above. Would you like to buy %A snakebite rods for %P gold?", Topic=92 +%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=1000*%1, "This rod is only for druids of level 13 and above. Would you like to buy %A moonlight rods for %P gold?", Topic=92 +%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=5000*%1, "This rod is only for druids of level 19 and above. Would you like to buy %A volcanic rods for %P gold?", Topic=92 +%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=10000*%1, "This rod is only for druids of level 26 and above. Would you like to buy %A quagmire rods for %P gold?", Topic=92 +%1,1<%1,"tempest","rod" -> Type=3067, Amount=%1, Price=15000*%1, "This rod is only for druids of level 33 and above. Would you like to buy %A tempest rods for %P gold?", Topic=92 + + +Topic=92,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=92,"yes" -> "Sorry, you don't have enough gold." +Topic=92 -> "You don't know what you're missing." \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/gen-t-weapon-s.ndb b/app/SabrehavenServer/data/npc/gen-t-weapon-s.ndb new file mode 100644 index 0000000..07f2de4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-t-weapon-s.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-weapon-s.ndb: Datenbank für generischen Waffenverkauf + +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, battle axes, sickles, short swords, swords, carlin swords, two handed swords, rapiers, daggers, clubs, morning stars and sabres. What's your choice?" + +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=33 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=33 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=33 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=33 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=33 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=33 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=33 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=33 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=33 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=33 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=33 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=33 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=33 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=33 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=33 +"carlin","sword" -> Type=3283, Amount=1, Price=473, "Do you want to buy one of the excellent carlin swords for %P gold?", Topic=33 + + +Topic=33,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=33,"yes" -> "Sorry, you do not have enough gold." +Topic=33 -> "Maybe you will buy it another time." + diff --git a/app/SabrehavenServer/data/npc/gen-xmas.ndb b/app/SabrehavenServer/data/npc/gen-xmas.ndb new file mode 100644 index 0000000..ac923ca --- /dev/null +++ b/app/SabrehavenServer/data/npc/gen-xmas.ndb @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-xmas.ndb: Datenbank für generische Weihnachtsmänner + +ADDRESS,"hello$",! -> "Merry Christmas, little %N!", Amount=Random(1,10000) +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait! I am ready for you soon!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, little %N!" + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "Ho ho ho! You don't know Santa Claus? Never mind. You may ask me for a present." +"name" -> "Sorry, I don't have time to chat. Please ask for your present." +"thank" -> "You had a few hard times last year, I know. Considering all that you did well and I'm proud of you." +"thank",amount>9000 -> "You're one of my favourite children, little %N. But pssht, don't tell the others! I love all of you, of course." + +"present",QuestValue(217)<3,amount<2000,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3599, Amount=10,Create(Type),Type=6506, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,amount<4000,amount>1999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3599, Amount=10,Create(Type),Type=6507, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,amount<6000,amount>3999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3599, Amount=10,Create(Type),Type=6508, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,amount<8000,amount>5999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3599, Amount=10,Create(Type),Type=2992, Amount=10,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount<9000,amount>7999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3039, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount<9500,amount>8999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3036, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,amount<9982,amount>9499,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=2991, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9983,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3382, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9984,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3419, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9985,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3381, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9986,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3420, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9987,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3279, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9988,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3079, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9989,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3386, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9990,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3567, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9991,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3364, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9992,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3366, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9993,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3389, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9994,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3001, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9995,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3570, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9996,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=5919, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9997,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3553, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9998,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3057, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=9999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=5080, Amount=1,Create(Type),Idle +"present",QuestValue(217)<3,Level>20,amount=10000,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=2993, Amount=1,Create(Type),Idle + + +"present",QuestValue(217)=3 -> "You already got your present! Next please!", Idle + diff --git a/app/SabrehavenServer/data/npc/gentest.ndb b/app/SabrehavenServer/data/npc/gentest.ndb new file mode 100644 index 0000000..b255c70 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gentest.ndb @@ -0,0 +1,6 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gentest.ndb: Datenbank für den test generischer händler + +"news" -> "I am busy. Please ask the citizens for news." +"sell" -> "Visit shopkeepers to buy their fine wares." +"job" -> "I am a trader." diff --git a/app/SabrehavenServer/data/npc/gordon.npc b/app/SabrehavenServer/data/npc/gordon.npc new file mode 100644 index 0000000..0997bb3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gordon.npc @@ -0,0 +1,23 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Gordon" +Outfit = (128,114-37-116-76-0) +Home = [32184,32770,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "A good day to you." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/gorn.npc b/app/SabrehavenServer/data/npc/gorn.npc new file mode 100644 index 0000000..e362735 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gorn.npc @@ -0,0 +1,133 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gorn.npc: Datenbank für den Händler Gorn + +Name = "Gorn" +Outfit = (129,58-68-101-95-0) +Home = [32377,32200,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment of all kinds. Do you need anything?" +"name" -> "I am Gorn. My goods are known all over Tibia." +"time" -> "It is exactly %T. Maybe you want to buy a watch?" + +"food" -> "If you are looking for food, go to Frodo's Hut." +"king" -> "The king supports Tibia's economy a lot." +"tibianus" -> * +"quentin" -> "He advices newcomers to buy at my store. I love that guy!" +"lynda" -> "That's a pretty one." +"harkath" -> "I hardly know him." +"army" -> "Armies are too hierarchical for my taste." +"ferumbras" -> "We had a clash or two in the old days." +"general" -> "I don't like titles." +"sam" -> "Strong as an ox, could armwrestle a minotaur, I bet." +"frodo" -> "Frodo is a jolly fellow." +"elane" -> "Elane is the leader of the paladin guild." +"paladin" -> * +"muriel" -> "You can find Muriel in the sorcerer guild." +"sorcerer" -> * +"gregor" -> "Even the strong knights need my equipment on their travels though Tibia." +"knight" -> * +"marvik" -> "These druids are nice people, you will find them in the east of the town." +"druid" -> * +"bozo" -> "Bah! Go away with this bozoguy." +"baxter" -> "Old Baxter was a rowdy, once. In our youth we shared some adventures and women." +"oswald" -> "This Oswald has not enough to work and too much time to spread rumours." +"sherry" -> "I hardly know the McRonalds." +"donald" -> * +"mcronald" -> * +"lugri" -> "Never heared that name." +"excalibug" -> "I would pay thousands of gold coins for this weapon." +"news" -> "Taxes will increase soon, so buy as much as you can right now." + +"offer" -> "My inventory is large, just have a look at the blackboards." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + +"ammunition" -> "Galuna sells them now in her own shop. Go and ask her about that." +"bow" -> * +"crossbow" -> * +"arrow" -> * +"bolt" -> * +"galuna" -> "In the past she delivered me with all the bows and arrows. She has now her own shop at the paladin guild." +"magic" -> "Magic? Ask a sorcerer or druid about that." +"fluid" -> "Find the magic shop." +"xodet" -> "He owns the magic shop here. But be aware: The prices are enormous." +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=40, "Do you want to buy a machete for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"greeting","card",ClientVersion>=790 -> Type=6386, Amount=1, Price=30, "Do you want to buy a greeting card for %P gold?", Topic=1 +"valentine","card",ClientVersion>=790 -> Type=6538, Amount=1, Price=30, "Do you want to buy a valentine's card for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=40*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"greeting","card",ClientVersion>=790 -> Type=6386, Amount=%1, Price=30*%1, "Do you want to buy %A greeting cards for %P gold?", Topic=1 +%1,1<%1,"valentine","card",ClientVersion>=790 -> Type=6538, Amount=%1, Price=30*%1, "Do you want to buy %A valentine's cards for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/graubart.npc b/app/SabrehavenServer/data/npc/graubart.npc new file mode 100644 index 0000000..2fffbac --- /dev/null +++ b/app/SabrehavenServer/data/npc/graubart.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# graubart.npc : Kapitän Graubart von der Seahawk (Fields) + +Name = "Graubart" +Outfit = (128,98-87-12-114-0) +Home = [32489,31622,07] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N. Looking for work on my ship?" +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N. Looking for work on my ship?" +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and don't forget me!" + +"bye" -> "Good bye and don't forget me!", Idle +"farewell" -> * +"work" -> "I'm sorry, but it is too dangerous nowadays. Too many storms out there. Come back in some months and we will see." +"name" -> "My name is Graubart, captain of the great Seahawk!" +"job" -> "I'm a merchant. I sail all over the world with my ship and trade with many different races!" +"races" -> "You know; elves, dwarfs, lizardmen, minotaurs and many others." +"ship" -> "Ah, my whole proud: My ship named Seahawk. We rode out so many stormy nights together. I think I couldn't live without it." +"seahawk" -> * +"trade" -> "I trade nearly everything, for example weapons, food, water, and even magic runes." +"merchant" -> "A merchant is someone who trades goods with other people and tries to make a little profit. *laughs*" +"weapons" -> "Sorry, sold out." +"food" -> "Sorry, sold out. Ask Bruno." +"water" -> "Sorry, sold out." +"magic","runes"-> "Sorry, sold out." +"bruno" -> "Bruno is one of the best sailors I know. He is nearly as good as me. *laughs loudly*" +"aneus" -> "Hmm, I don't know him very well. But he has a very nice story to tell." +"marlene" -> "Pssst. Marlene is not near right now...? You know... she is a lovely woman, but she talks too much! So I always try to keep distance from her because she can't stop talking." +} diff --git a/app/SabrehavenServer/data/npc/gregor.npc b/app/SabrehavenServer/data/npc/gregor.npc new file mode 100644 index 0000000..d4f5338 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gregor.npc @@ -0,0 +1,138 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gregor.npc: Datenbank fuer den Ritter Gregor + +Name = "Gregor" +Outfit = (131,38-38-38-38-2) +Home = [32407,32202,6] +Radius = 4 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Welcome home, Knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,"hello$",! -> "Greetings, %N. What do you want?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Be careful on your journeys." + +"bye" -> "Be careful on your journeys.", Idle +"farewell" -> * +"job" -> "I am the first knight. I trained some of the greatest heroes of Tibia." +"name" -> "You are joking, eh? Of course, you know me. I am Gregor, the first knight." +"time" -> "It is time to join the Knights!" +"king" -> "Hail to our King!" +"tibianus" -> * +"quentin" -> "I will never understand this peaceful monks and priests." +"lynda" -> "Before she became a priest she won the Miss Tibia contest three times in a row." +"harkath" -> "One of Tibia's greatest warriors and strategists." +"army" -> "I teached many of the guards personally." +"ferumbras" -> "A fine game to hunt. But be careful, he cheats!" +"general" -> "General Harkath Bloodblade, a rolemodel." +"sam" -> "He has the muscles, but lacks the guts." +"gorn" -> "Always concerned with his profit. What a loss! He was adventuring with baxter in the old days." +"frodo" -> "I and my students often share a cask of beer or wine at Frodo's hut." +"elane" -> "A bow might be a fine weapon for someone not strong enough to wield a REAL weapon." +"muriel" -> "Bah, go away with these sorcerer tricks. Only cowards use tricks." +"gregor" -> "A great name, isn't it?" +"marvik" -> "Old Marvik saved life and limb of many of my boys and girls." +"bozo" -> "Some day someone will make something happen to him..." +"baxter" -> "He was an adventurer once." +"oswald" -> "What an idiot." +"sherry" -> "Peaceful farmers." +"donald" -> * +"mcronald" -> * +"lugri" -> "If he would have some guts he would fight for what he's talking about." +"excalibug" -> "Many brave warriors died on the quest to find that fabled weapon." +"news" -> "Times of war are at hand." + +"hero" -> "Of course, you heard of them. Knights are the best fighters in Tibia." +"tibia" -> "Beautiful Tibia. And with our help everyone is safe." +"knight" -> "Knights are the warriors of Tibia. Without us, no one would be safe. Every brave and strong man or woman can join us." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Knights, paladins, sorcerers, and druids." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Muriel, the sorcerer." + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Be careful on your journeys.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"outfit" -> "Only the bravest warriors may wear adorned helmets. They are traditionally awarded after having completed a difficult task for our guild." +"addon" -> * + +"task",QuestValue(17542)=0 -> "You mean, you would like to prove that you deserve to wear such a helmet?", Topic=4 +"mission",QuestValue(17542)=0 -> * +Topic=4,"yes" -> "Well then, listen closely. First, you will have to prove that you are a fierce and restless warrior by bringing me 100 perfect behemoth fangs. ...", + "Secondly, please retrieve a helmet for us which has been lost a long time ago. The famous Ramsay the Reckless wore it when exploring an ape settlement. ...", + "Third, we need a new flask of warrior's sweat. We've run out of it recently, but we need a small amount for the show battles in our arena. ...", + "Lastly, I will have our smith refine your helmet if you bring me royal steel, an especially noble metal. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=5 +Topic=4 -> "However." +Topic=5,"yes" -> "Alright then. Come back to me once you have collected 100 perfect behemoth fangs.", SetQuestValue(17542,1), SetQuestValue(17594,1) +Topic=5 -> "Maybe another time." + +"behemoth","fang",QuestValue(17542)=1 -> Type=5893, Amount=100, "Have you really managed to fulfill the task and brought me 100 perfect behemoth fangs?", Topic=6 +"mission",QuestValue(17542)=1 -> * +"task",QuestValue(17542)=1 -> * +Topic=6,"yes",Count(Type)>=Amount,Knight -> "I'm deeply impressed, brave Knight %N. Now, please retrieve Ramsay's helmet.", Delete(Type), SetQuestValue(17542,2) +Topic=6,"yes",Count(Type)>=Amount -> "I'm deeply impressed, %N. Even if you are not a knight, you certainly possess knight qualities. Now, please retrieve Ramsay's helmet.", Delete(Type), SetQuestValue(17542,2) +Topic=6,"yes" -> "You don't have that many." +Topic=6 -> "Maybe another time." + +"reckless","helmet",QuestValue(17542)=2 -> Type=5924, Amount=1, "Did you recover the helmet of Ramsay the Reckless?", Topic=7 +"mission",QuestValue(17542)=2 -> * +"task",QuestValue(17542)=2 -> * +Topic=7,"yes",Count(Type)>=Amount,Knight -> "Good work, brave Knight %N! Even though it is damaged, it has a lot of sentimental value. Now, please bring me warrior's sweat.", Delete(Type), SetQuestValue(17542,3) +Topic=7,"yes",Count(Type)>=Amount -> "Good work, %N! Even though it is damaged, it has a lot of sentimental value. Now, please bring me warrior's sweat.", Delete(Type), SetQuestValue(17542,3) +Topic=7,"yes" -> "You don't have it." +Topic=7 -> "Maybe another time." + +"warriors","sweat",QuestValue(17542)=3 -> Type=5885, Amount=1, "Were you able to get hold of a flask with pure warrior's sweat?", Topic=8 +"mission",QuestValue(17542)=3 -> * +"task",QuestValue(17542)=3 -> * +Topic=8,"yes",Count(Type)>=Amount,Knight -> "Now that is a pleasant surprise, brave Knight %N! There is only one task left now: Obtain royal steel to have your helmet refined.", Delete(Type), SetQuestValue(17542,4) +Topic=8,"yes",Count(Type)>=Amount -> "Now that is a pleasant surprise, %N! There is only one task left now: Obtain royal steel to have your helmet refined.", Delete(Type), SetQuestValue(17542,4) +Topic=8,"yes" -> "You don't have it." +Topic=8 -> "Maybe another time." + +"royal","steel",QuestValue(17542)=4 -> Type=5887, Amount=1, "Ah, have you brought the royal steel?", Topic=9 +"mission",QuestValue(17542)=4 -> * +"task",QuestValue(17542)=4 -> * +Topic=9,"yes",Count(Type)>=Amount,Knight -> "You truly deserve to wear an adorned helmet, brave Knight %N. Please talk to Sam and tell him I sent you. I'm sure he will be glad to refine your helmet.", Delete(Type), SetQuestValue(17542,5) +Topic=9,"yes",Count(Type)>=Amount -> "You truly deserve to wear an adorned helmet, %N. Please talk to Sam and tell him I sent you. I'm sure he will be glad to refine your helmet.", Delete(Type), SetQuestValue(17542,5), SetExpiringQuestValue(17544, 7200000) +Topic=9,"yes" -> "You don't have it." +Topic=9 -> "Maybe another time." + +"helmet",QuestValue(17542)=5 -> "Please talk to Sam and tell him I sent you. I'm sure he will be glad to refine your helmet." +"mission",QuestValue(17542)=5 -> * +"task",QuestValue(17542)=5 -> * + +"helmet",QuestValue(17542)=6 -> "I see Sam did a great work with your helmet!" +"mission",QuestValue(17542)=6 -> "Sorry, but I don't have any task for you now." +"task",QuestValue(17542)=6 -> * +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsabdendriel.npc b/app/SabrehavenServer/data/npc/grizzlyadamsabdendriel.npc new file mode 100644 index 0000000..7e57a77 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsabdendriel.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32700,31638,6] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsankrahmun.npc b/app/SabrehavenServer/data/npc/grizzlyadamsankrahmun.npc new file mode 100644 index 0000000..8d96c1e --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsankrahmun.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [33132,32830,7] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamscarlin.npc b/app/SabrehavenServer/data/npc/grizzlyadamscarlin.npc new file mode 100644 index 0000000..b6e434f --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamscarlin.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32348,31786,5] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsdarashia.npc b/app/SabrehavenServer/data/npc/grizzlyadamsdarashia.npc new file mode 100644 index 0000000..8403470 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsdarashia.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [33229,32395,7] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsedron.npc b/app/SabrehavenServer/data/npc/grizzlyadamsedron.npc new file mode 100644 index 0000000..5bb4330 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsedron.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [33180,31810,7] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamskazordoon.npc b/app/SabrehavenServer/data/npc/grizzlyadamskazordoon.npc new file mode 100644 index 0000000..39e5882 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamskazordoon.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32644,31894,9] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamslibertybay.npc b/app/SabrehavenServer/data/npc/grizzlyadamslibertybay.npc new file mode 100644 index 0000000..f48f04d --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamslibertybay.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32334,32854,7] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsporthope.npc b/app/SabrehavenServer/data/npc/grizzlyadamsporthope.npc new file mode 100644 index 0000000..44a912a --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsporthope.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32693,32767,6] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsthais.npc b/app/SabrehavenServer/data/npc/grizzlyadamsthais.npc new file mode 100644 index 0000000..0d47d83 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsthais.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32359,32232,7] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grizzlyadamsvenore.npc b/app/SabrehavenServer/data/npc/grizzlyadamsvenore.npc new file mode 100644 index 0000000..976ff79 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grizzlyadamsvenore.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Grizzly Adams" +Outfit = (144,97-97-94-97-3) +Home = [32929,32077,10] +Radius = 3 + +Behaviour = { +@"killing-tasks.ndb" +} diff --git a/app/SabrehavenServer/data/npc/grof.npc b/app/SabrehavenServer/data/npc/grof.npc new file mode 100644 index 0000000..3b60ac4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/grof.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# grof.npc: Datenbank für die Stadtwache am Nordtor + +Name = "Grof, the guard" +Outfit = (131,19-19-19-19-0) +Home = [32373,32184,7] +Radius = 3 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/app/SabrehavenServer/data/npc/guards-carlin.ndb b/app/SabrehavenServer/data/npc/guards-carlin.ndb new file mode 100644 index 0000000..b1f88e4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/guards-carlin.ndb @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-carlin.ndb: Datenbank für die Stadtwachen von Carlin + +ADDRESS,"hello$",! -> "LONG LIVE THE QUEEN!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy right now!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Hrmpf!" + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"farewell" -> * +"news" -> "I am busy. Please ask the citizens for news." +"how","are","you" -> "I am healthy and vigilant." +"sell" -> "Visit Carlin's shopkeepers to buy their fine wares." +"queen" -> "Queen Eloise is our beloved sovereign!" +"leader" -> * +"job" -> "It's my duty to protect our fair city." +"army" -> "Of course, we guards are members of the army." +"guard" -> "I am a guard and proud of it." +"name" -> "It's Miss Bonecrusher to you!" +"bonecrusher" -> "The bonecrusher family has been serving in the army of Carlin for generations. My sister Bunny is the general of our army." +"army" -> "Ask my sister Bunny for details." +"castle" -> "The castle is in the northwest corner of the city." +"green","ferrets" -> "Brave warriors, indeed." +"knights","of","noodles" -> "WOOF! WOOF! Go away with theese puny king's puppys." +"secret","police" -> "Ask a higher offical about that." +"city" -> "Behave well, while in the city, or we'll get you! Do you want to know where to find a shop or a guild?" +"shop" -> "There's a smith, a provisioner, a tavern, a magic shop, and the royal post office, of course." +"guild" -> "In the city you will find the guildhouses of the Knights, the Paladins, the Druids, and the Sorcerers." +"scum" -> "We will get rid of all scum." +"barbara" -> "Fine warrioress, even if not of our family." +"fenbala" -> * +"bunny" -> "My sister is in charge of our mighty army." +"busty" -> "We are Bonecrushers. Mess with one of us, and we all will come for you!" +"bambi" -> * +"blossom" -> * +"banor" -> "Praise Banor! May the great warrior be with us!" +"graveyard" -> "Stay away from the graveyard, it's haunted!" +"crypt" -> * +"haunted" -> "Strange things happen in our graveyard, and sometimes there are .. noises." +"noise" -> "I never heared them myself, but people, one can trust, did." +"male$" -> "Bah! Who cares about males? Let them do males' work, cleaning the sewers for example." +"sewer" -> "Sewers are filthy and disgusting. We let the men take care of them." +"rebellion" -> "What a joke. The men have no guts for a rebellion." +"resistance" -> * +"tod$" -> "There once was an adventurer with this name in town, made some trouble, got kicked out. No big deal." +"ghostlands" -> "In theory the ghostlands are of limits. But we don't enforce that. Anyone stupid enough to go there will meet his deserved fate." + +"fletcher" -> "You can find the weapon and armour shops just west of the towncenter." +"smith" -> * +"weapon" -> * +"armor" -> * +"sarina" -> "Sarina is our provisioner. You can find her in the towncenter, south of the weaponshops." +"provision" -> * +"dane" -> "Dane runs the local tavern and hotel. You can find it at the southwest beach of Carlin." +"tavern" -> * +"liane" -> "Liane is a kind person. She runs the post office in the town's center." +"post" -> * +"depot" -> "We have some depots in our town. You can't miss them." +"lea$" -> "Lea is the head of the local sorcerers guild. You can find it south of the towncenter at the magic shop of Rachel." +"sorcerer" -> * +"legola" -> "Legola runs the local paladins guild, it's near the westgate." +"paladin" -> * +"padreia" -> "Padreia is the greatest druid of the continent. You find the guild of the benevolent druids in the southwest of the city." +"druid" -> * +"trisha" -> "Trisha is the leader of our knights guild. It is in the north of the town, near the gate." +"knight" -> * +"rachel" -> "Rachel sells equipment for all magic users in her shop. There is also the sorcerer guild in the second floor." +"magic" -> * +"spell" -> * + +"fuck" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) diff --git a/app/SabrehavenServer/data/npc/guards-darama.ndb b/app/SabrehavenServer/data/npc/guards-darama.ndb new file mode 100644 index 0000000..c16d00f --- /dev/null +++ b/app/SabrehavenServer/data/npc/guards-darama.ndb @@ -0,0 +1,41 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-darama.ndb: Datenbank für die Stadtwachen von Darama + +ADDRESS,"hello$",! -> "Daraman's blessings!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings!" + +"bye" -> "Daraman's blessings!", Idle +"news" -> "The evil of Drefia is strong in these days." +"how","are","you"-> "I am on guard and my soul is strong." +"sell" -> "Go to the bazaar in the middle of the town." +"buy" -> * +"caliph" -> "Our leader is enlightened by Daraman, thrice praised be his name!" +"leader" -> * +"job" -> "I am a protector of the people of Darashia." +"army" -> "This information is confidential." +"guard" -> * +"daraman" -> "The ancient prophet brought our people here from a place long forgotten." +"pilgrim" -> "Darama led his people on a pilgrimage to the holy lands of Darama to escape temptations and distractions." +"necromancer" -> "It's said they fled from the corrupted continent of the thaian empire after losing some battles." +"city" -> "This city is lovely Darashia, pearl of Darama." +"darashia" -> * +"darama" -> "Darashia is the heart of Darama. In the northeast, across the Devourer, there is the dark pyramid. In the west, across the Plague Spike, are the cursed ruins of Drefia." +"plague","spike" -> "Mountain of Poison, Scorpions Rock, it's known by many names. It's to the west of Darashia." +"devourer" -> "The great desert devoured many of the first pilgrims. We learned the ways of the devourer and thus conquered it." +"pyramid" -> "The minotaurs took shelter in the ancient pyramid, which our people avoid for the dark powers that might be present there." +"drefia" -> "The ruins of Drefia in the far west are the hideout of the vile necromancer cult that once corrupted this city." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) diff --git a/app/SabrehavenServer/data/npc/guards-thais.ndb b/app/SabrehavenServer/data/npc/guards-thais.ndb new file mode 100644 index 0000000..46e80d8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/guards-thais.ndb @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-thais.ndb: Datenbank für die Stadtwachen von Thais + +ADDRESS,"hello$",! -> "LONG LIVE THE KING!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy right now!." +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Hrmpf!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "I am busy. Please ask the citizens for news." +"how","are","you"-> "I am healthy and vigilant." +"sell" -> "Visit Tibia's shopkeepers to buy their fine wares." +"tibianus" -> "Tibianus III is our beloved king!" +"king" -> * +"leader" -> * +"job" -> "It's my duty to protect the city." +"army" -> "Of course we guards are members of the army." +"guard" -> "I am a guard and proud of it." +"battlegroups" -> "Ask higher officials about that, please." +"castle" -> "The castle is at the west of the city." +"dogs", "of", "war" -> "Brave warriors, indeed." +"knights", "of", "noodles" -> "Every guard dreams of becoming one of them one day." +"red", "guard" -> "We of the red guard are the special forces." +"secret", "police" -> "Ask a higher offical about that." +"silver", "guard" -> "Only the best of the best serve as silver guards." +"city" -> "Behave while in the city or we get you! Do you want to know where to find a shop or a guild?" +"shop" -> "There's a smith, a provisioner, and a tavern." +"guild" -> "In the city you will find the guildhouses of the knights, paladins, druids, and sorcerers." +"scum" -> "We will get rid of all scum." +"stutch" -> "He is a soldier in the silver guard." +"harsky" -> * +"baxter" -> "He is a role model for us." +"bozo" -> "The royal jester." +"harkath$" -> "The royal general. A warrior worth Banor's blessings." +"bloodblade$" -> * +"general$" -> * +"banor" -> "Praise Banor! May the great warrior be with us!" +"sam" -> "Sam is our blacksmith. You'll find him north of the main crossroads. His shop is to the left." +"smith" -> * +"weapon" -> * +"armor" -> * +"gorn" -> "Gorn is our provisioner. You'll find him north of the main crossroads. His shop is to the right." +"provision" -> * +"frodo" -> "Frodo runs the local tavern. You'll find it at the main crossroads to the north-west." +"tavern" -> * +"benjamin" -> "Benjamin was a brave fighter. He runs the post office in the west of the city." +"post" -> * +"depot" -> "The depot is at the post office in the west of the city." +"muriel" -> "Muriel is the head of the local sorcerers' guild. You'll find it in the south-west of the city" +"sorcerer" -> * +"elane" -> "Elane is responsible for the local paladins' guild. It's in the west of the town, directly south of the post office." +"paladin" -> * +"marvik" -> "Marvik is the great druid of the local guild. You'll find him by climbing up the citywalls at the east." +"druid" -> * +"gregor" -> "The high knight of the knights' guild. It is in north-east of the town." +"knight" -> * + +"fuck" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) diff --git a/app/SabrehavenServer/data/npc/guards-venore.ndb b/app/SabrehavenServer/data/npc/guards-venore.ndb new file mode 100644 index 0000000..317e1b3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/guards-venore.ndb @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-venore.ndb: Datenbank für die Stadtwachen von Venore + +ADDRESS,"hello$",! -> "LONG LIVE THE KING!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "Not now, I am busy!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "Look for the Hard Rock Tavern to learn the latest news." +"how","are","you" -> "That's classified information!" +"sell" -> "Ask the merchants of the city. There are more then enough." +"buy" -> * +"tibianus" -> "Tibianus III is our beloved king!" +"king" -> * +"leader" -> * +"job" -> "I am a protector of the people of Venore." +"army" -> "This information is confidential." +"guard" -> * +"army" -> "All guards are members of the army." +"guard" -> "I am a guard and proud of it." +"battlegroups" -> "I doubt you have the security clearance to ask that." +"castle" -> "The castle is in Thais, the crown of the kingdom." +"dogs","of","war" -> "They are our rolemodells." +"knights","of","noodles" -> "Every guard dreams of becoming one of them one day." +"red","guard" -> "We of the red guard are the special forces and town guards." +"secret","police" -> "This information is confidential." +"silver","guard" -> "Only the best of the best serve as silver guards." +"city" -> "This city, a member of the Thaian kingdom, is under the protection of the Thaian army." + +"venore" -> "The harbour is to the north, the weapon market in the south, the general market to the west, and the bank to the east. You will find other shops and the Hard Rock Tavern in the center." +"swampel" -> "Those elves hide in the swamps and are trying to kill all humans in this area." +"amazon" -> "They are the best example for the results of the Carlin madness." +"swamptroll" -> "This hideous creatures are even more ugly than the normal trolls. They are treacherous and use several poisons." +"swamp" -> "The swamp is a dangerous place and full of monsters, not to mention all those swampelves living at shadowthorn, amazons, and swamptrolls." +"monsters" -> "The swamp is full of nasty snakes and there's a dragon breeding ground somewhere in the swamps." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) diff --git a/app/SabrehavenServer/data/npc/gundralph.npc b/app/SabrehavenServer/data/npc/gundralph.npc new file mode 100644 index 0000000..cb961ff --- /dev/null +++ b/app/SabrehavenServer/data/npc/gundralph.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gundralph.npc: Datenbank für den Zauberlehrer Gundralph + +Name = "Gundralph" +Outfit = (9,0-0-0-0-0) +Home = [33268,31849,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, be welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye." + +"bye" -> "Goodbye", Idle +"job" -> "I am a teacher for some powerful Spells." +"name" -> "They call me Gundralph." +"time" -> "Let me see, it's %T." +"king" -> "Unfortunately, I never met King Tibianus III in person." +"tibianus" -> * +"army" -> "They live in the castle to the west." +"ferumbras" -> "How low can a sorceror sink." +"excalibug" -> "An awesome weapon if it exists." +"thais" -> "I see Thais as a lost course." +"tibia" -> "The world is so big and we have only so little time to travel." +"carlin" -> "Carlin is a fine place for druids." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I have heard nothing of intrest lately, sorry." +"rumors" -> * + +"spellbook" -> "Please ask the stationer in the west tower for that." +"spell" -> "I have 'Ultimate Light', 'Soulfire', 'Magic Wall', 'Cancel Invisibility', and 'Undead Legion'. Are you interested?" + +"ultimate","light",Sorcerer -> String="Ultimate Light", Price=1600, "Do you want to learn the spell 'Ultimate Light' for %P gold?", Topic=1 +"ultimate","light",Druid -> * +"ultimate","light" -> "I'm sorry, but this spell is only for druids and sorcerers." +"soulfire",Sorcerer -> String="Soulfire", Price=1800, "Do you want to learn the spell 'Soulfire' for %P gold?", Topic=1 +"soulfire",Druid -> * +"soulfire" -> "I'm sorry, but this spell is only for druids and sorcerers." +"magic","wall",Sorcerer -> String="Magic Wall", Price=2100, "Do you want to learn the spell 'Magic Wall' for %P gold?", Topic=1 +"magic","wall" -> "I'm sorry, but this spell is only for sorcerers." +"undead","legion",Druid -> String="Undead Legion", Price=2000, "Do you want to learn the spell 'Undead Legion' for %P gold?", Topic=1 +"undead","legion" -> "I'm sorry, but this spell is only for druids." +"cancel","invisibility",Sorcerer -> String="Cancel Invisibility", Price=1600, "Do you want to learn the spell 'Cancel Invisibility' for %P gold?", Topic=1 +"cancel","invisibility" -> "I'm sorry, but this spell is only for sorcerers." + +Topic=1,"yes",SpellKnown(String)=1 -> "Hmm, you already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "Hmm, you need to advance to level %A to learn this spell." +Topic=1,"yes",CountMoney "Hmm, you don't have enough money to pay my service." +Topic=1,"yes" -> "Voila, from now on you can cast this spell. Use your knowledge wisely.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/gurbasch.npc b/app/SabrehavenServer/data/npc/gurbasch.npc new file mode 100644 index 0000000..625cca5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/gurbasch.npc @@ -0,0 +1,67 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gurbasch.npc: Datenbank für den Kapitän Gurbasch + +Name = "Gurbasch" +Outfit = (66,0-0-0-0-0) +Home = [33313,31989,15] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","kazordoon",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) +BUSY,"bring","me","to","kazordoon",Premium,CountMoney>=160,! -> Price=160, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) +ADDRESS,"bring","me","to","kazordoon",Premium,QuestValue(250)>2,CountMoney>=150,! -> Price=150, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) +ADDRESS,"bring","me","to","kazordoon",Premium,CountMoney>=160,! -> Price=160, "Full steam ahead %N!", Queue, DeleteMoney, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) + +BUSY,"hello$",! -> "Don't hurry.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "No patience, these brats!" + +"bye" -> "Until next time.", Idle +"farewell" -> * +"fare","thee","well" -> "Thou art truly in possession of fine manners. Blessings upon you!", EffectOpp(13) +"job" -> "As should be quite obvious, I am operating a steamship." +"work" -> * +"name" -> "I am Gurbasch Firejuggler, son of the machine, of the Molten Rock." +"tibia" -> "Tibia? Just don't ask." +"ship" -> "It is indeed something we dwarfs may be proud of: a ship operating by steam power." +"steamship" -> * +"captain" -> "Captain" +"technomancer" -> "A technomancer wields power over incredible machines, as his knowledge is his magic." +"inventors" -> "You know, elves may be intelligent, but they are too lazy to invent. Really." +"inventions" -> * +"sell" -> "I am not a vendor." +"buy" -> * +"thais" -> "How do you expect me to go there? Fly? Hm, wait... no, sorry." +"ab'dendriel" -> * +"carlin" -> * +"venore" -> * +"senja" -> * +"folda" -> * +"vega" -> * +"ice","islands" -> * +"darashia" -> * +"darama" -> * +"cormaya" -> "Hey, we ARE at Cormaya! Must be the cavemadness..." +"beer" -> "Ah, you got some? Nah, beer only tastes fine in Kazordoon. If you have brought it from there, it tastes foul now, I guess." +"dwarf" -> "We are an old and proud race, although we posess the best inventions." +"brodrosch" -> "He is my brother working the Kazordoon steamship." +"elves" -> "Have one elf onboard a ship, and you are doomed." +"elf" -> * + +"kazordoon" -> Price=160, "Do you want to go to Kazordoon? And try the beer there? %P gold?", Topic=1 +"passage" -> * + +"kazordoon",QuestValue(250)>2 -> Price=150, "Do you want to go to Kazordoon? And try the beer there? %P gold?", Topic=1 +"passage",QuestValue(250)>2 -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic=1,"yes" -> "You don't have enough money." +} diff --git a/app/SabrehavenServer/data/npc/habdel.npc b/app/SabrehavenServer/data/npc/habdel.npc new file mode 100644 index 0000000..be874ee --- /dev/null +++ b/app/SabrehavenServer/data/npc/habdel.npc @@ -0,0 +1,132 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Habdel.npc: Datenbank für den Waffenhändler Habdel + +Name = "Habdel" +Outfit = (146,95-2-0-58-1) +Home = [33225,32434,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! See the fine weapons I sell." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will have finished soon %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and please come back soon.", Idle + +"bye" -> "Good bye. Come back soon.", Idle +"job" -> "I sell weapons that are as lethal as the bite of the desertlion and as quick as the sandwasp." +"shop" -> * +"name" -> "My name is Habdel Ibn Haqui." +"time" -> "Don't worry, there is enough time left to finish our deal." +"help" -> "I sell and buy weapons. Just ask what you need or tell me what you offer." +"monster" -> "With my weapons you have to fear the monsters no longer and you will brave any danger or dungeon!" +"dungeon" -> * +"drefia" -> "Even the undead will fall a second time for the weapons you buy from me." +"thanks" -> "You are welcome." +"thank","you" -> * +"do","you","sell" -> "Which of my powerful weapons do you need?" +"do","you","have" -> * +"offer" -> "My offers are light and heavy weapons." +"weapon" -> * +"light" -> "I have clubs, daggers, spears, swords, maces, rapiers, morning stars, and sabres. What's your choice?" +"light","weapon" -> * +"heavy" -> "I have the best two handed swords in Tibia. I also sell battle hammers and battle axes. What's your choice?" +"heavy","weapon" -> * +"armor" -> "I sell only weapons. For armor, ask Azil in the other shop." +"shield" -> * +"helmet" -> * +"trousers" -> * +"legs" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "Do you want to buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "Do you want to buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "Do you want to buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "Do you want to sell a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "Do you want to sell a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "Do you want to sell a war hammer for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "Do you want to sell %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Do you want to sell %A clubs for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Do you want to sell %A spears for %P gold", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "Do you want to sell %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "Do you want to sell %A war hammers for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Fine. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +"addon",female -> "My scimitar? Yes, that is a true masterpiece. Only a true man can wear it." +"outfit",female -> * + +"addon",QuestValue(17555)=0,male -> "My scimitar? Yes, that is a true masterpiece. Of course I could make one for you, but I have a small request. Would you fulfill a task for me?", Topic=3 +"outfit",QuestValue(17555)=0,male -> * +"mission",QuestValue(17555)=0,male -> * +"task",QuestValue(17555)=0,male -> * +Topic=3,"yes" -> "Listen, um... I know that Ishina has been wanting a comb for a long time... not just any comb, but a mermaid's comb. She said it prevents split ends... or something. ...", + "Do you think you could get one for me so I can give it to her? I really would appreciate it.", Topic=4 +Topic=3 -> "Maybe another time." +Topic=4,"yes" -> "Brilliant! I will wait for you to return with a mermaid's comb then.", SetQuestValue(17555,1), SetQuestValue(17594,1) +Topic=4 -> "Maybe another time." + +"comb",QuestValue(17555)=1,male -> Type=5945, Amount=1, "Have you brought a mermaid's comb for Ishina?", Topic=5 +"mission",QuestValue(17555)=1,male -> * +"task",QuestValue(17555)=1,male -> * +Topic=5,"yes",Count(Type)>=Amount -> "Yeah! That's it! I can't wait to give it to her! Oh - but first, I'll fulfil my promise: Here is your scimitar! Thanks again!", Delete(Type), SetQuestValue(17555,2), AddOutfitAddon(146,1), AddOutfitAddon(150,1), EffectOpp(13) +Topic=5,"yes" -> "You don't have it." +Topic=5 -> "Maybe another time." + +"mission",QuestValue(17555)=2 -> "Sorry but I don't have any tasks for you." +"task",QuestValue(17555)=2 -> * +} diff --git a/app/SabrehavenServer/data/npc/hagor.npc b/app/SabrehavenServer/data/npc/hagor.npc new file mode 100644 index 0000000..db838cb --- /dev/null +++ b/app/SabrehavenServer/data/npc/hagor.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hagor.npc: Datenbank für den alten Abenteurer Hagor (Desert) + +Name = "Hagor" +Outfit = (129,19-58-105-94-0) +Home = [32654,32151,10] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, adventurer %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"name" -> "My name is Hagor, the old hunter." +"job" -> "I travel through the lands of Tibia and now Jakundaf Desert since years." +"tibia" -> "The gods made this world full of fascinating secrets and I will search them till my end." +"thais" -> "Thais... It's a big city in Middle-Tibia. Lots of people live there." +"carlin" -> "Is this the city to the north? I heard rumours about it." +"king" -> "Tibianus is our king. To be honest, I didn't hear much of him till now." +"weapon" -> "Weapons? Do you have some? You better know how to use them!" +"help" -> "I'd really like to help you. Could you specify your request?" +"time" -> "Time has no meaning to me." +"sword" -> "Do you mean any sword in particular? Or just any sword?" +"desert" -> "Yes, it's big, isn't it?" +"excalibug" -> "I heared rumours that there is a sword called so. I don't know if it exists." +"fight" -> "Fighting is an art. Know it and you will be strong, ignore it and you will die soon!" +"guild" -> "There are many different guilds in Tibia. They come and go, come and go..." +"god" -> "There are a lot of gods we believe in. Maybe you should check out different books to find out something about them." +"way" -> "Which way are you looking for in particular?" +"door" -> "Which door are you talking about? If it is locked, maybe you should try to find a key for it!" +"secret" -> "There are many secrets. But I fear, I can't tell you much about them. They are also secret to me..." +"treasure" -> "Someone told me - I can't remember who it was - that there was a treasure hidden nearby." +"book" -> "Yes, I really can recommend reading books. It might help you find what you are looking for!" +"gharonk" -> "Gharonk is a very old language, only spoken by a few people. It's not a very complex language, but that does not mean that it is easy to understand!" +"offer" -> "Go ahead. I don't sell or buy anything!" +"exit" -> "Yes, there is an exit for these dungeons. Just find the teleporter." +"library" -> "There is a library in here, right. I assume you are talking about this library. It's locked, as far as I know. But somewhere there has to be a key... maybe the librarian knows more?" +"roll" -> "Oh, yes, I love them!" + +"morrin" -> "Ah, I remember that man. We made a deal, guess about what.", Topic=1 +Topic=1,"key" -> "Right! We can make the same deal if you give a fresh delicious roll. Do you have any?", Topic=2 +Topic=2,"yes",Count(3601)>=1 -> "Oh, fine! Here you are.", Amount=1, Delete(3601), Data=4022, Create(2969) +Topic=2,"yes" -> "Hey, you do not have one!" +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/hairycles.npc b/app/SabrehavenServer/data/npc/hairycles.npc new file mode 100644 index 0000000..7365dd1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hairycles.npc @@ -0,0 +1,192 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hairycles.npc: Datenbank für den Affen Hairycles + +Name = "Hairycles" +Outfit = (117,0-0-0-0-0) +Home = [32826,32574,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(293)>11,! ->"Be greeted, friend of the ape people. If you want to trade, just ask for my offers. If you are injured, ask for healing." +ADDRESS,"hi$",QuestValue(293)>11,! -> * + +ADDRESS,"hello$",! -> "Oh! Hello! Hello! Did not notice!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait! Wait! Time I no have!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "Me fine, me fine." +"advice" -> "You stay away from other apes. We not like foreigners. Especially with so little hair." +"job" -> "Me great wizard. Me great doctor. Me know many plants. Me old and me have seen many things." +"name" -> "Me is Hairycles." +"time" -> "You look to suns or moon and time you know." +"help" -> "Me not help you can. Other apes would get mad at me." +"jungle" -> "Jungle is dangerous. Jungle also provides us food. Take care when in jungle and safe you be." +"city" -> "City now our is. Chasing away evil snakemen." +"snakemen" -> "Evil snakemen mean to apes and making them work and holding them captive since apes can think. But then Spartaky came." +"spartaky" -> "He great ape was. He fled to jungle, taught other apes of snakemen secrets. Came back with other apes and together we chased snake people away. Made city our home." +"port","hope" -> "Strange hairless ape people there live. We go and get funny things from strange people." +"ape","people" -> "We be kongra, sibang and merlkin. Strange hairless ape people live in city called Port Hope." +"kongra" -> "Kongra verry strong. Kongra verry angry verry fast. Take care when kongra comes. Better climb on highest tree." +"sibang" -> "Sibang verry fast and funny. Sibang good gather food. Sibang know jungle well." +"merlkin" -> "Merlkin we are. Merlkin verry wise, merlkin learn many things quick. Teach other apes things a lot. Making heal and making magic." +"magic" -> "We see many things and learning quick. Merlkin magic learn quick, quick. We just watch and learn. Sometimes we try and learn." +"weapon" -> "We weapons not need much. Take what is around we do. Tools we more need." +"tools" -> "Lot of tools snakemen left when run away. But tools go break. New tools we get where we find. Like taking banana." +"tibia" -> "Me know Tibia is all we see." + +"heal" -> "You look for food and rest." +"heal$",QuestValue(293)>11,Burning>0 -> "You are burning. Me will help you.", Burning(0,0), EffectOpp(15) +"heal$",QuestValue(293)>11,Poison>0 -> "You are poisoned. Me will help you.", Poison(0,0), EffectOpp(14) +"heal$",QuestValue(293)>11,HP<50 -> "You are looking really bad. Let Hairycles heal wounds.", HP=50, EffectOpp(13) + + +"offers",QuestValue(293)<11 -> "Me nothing have to offer you now. Perhaps ask later, when we know better." + +"banana" -> "Banana is good. Is magic fruit. Banana makes happy. Banana means life. Banana is everything." +"language" -> "Strange hairless ape in loincloth came here. Zantar his name was. Brought many banana. We him liked. He here lived. Taught Hairycles funny language." +# "mission" -> "Perhaps help you can. But not now. Later you ask again. Me fire light when help is needed." + +"mission" ,QuestValue(293)<1 -> "These are dire times for our people. Problems plenty are in this times. But me people not grant trust easy. Are you willing to prove you friend of ape people?", topic=1 +topic=1,"no" -> "Hairycles sad is now. But perhaps you will change mind one day." +topic=1,"yes" -> "To become friend of ape people a long and difficult way is. We do not trust easy but help is needed. Will you listen to story of Hairycles?", topic=2 + +"no", topic=2 -> "Hairycles thought better of you." +"yes", topic=2 -> "So listen, little ape was struck by plague. Hairycles not does know what plague it is. That is strange. Hairycles should know. But Hairycles learnt lots and lots ...", "Me sure to make cure so strong to drive away all plague. But to create great cure me need powerful components ...", "Me need whisper moss. Whisper moss growing south of human settlement is. Problem is, evil little dworcs harvest all whisper moss immediately ...", "Me know they hoard some in their underground lair. My people raided dworcs often before humans came. So we know the moss is hidden in east of upper level of dworc lair ...", "You go there and take good moss from evil dworcs. Talk with me about mission when having moss.",SetQuestValue(293,1) + +topic=2 -> "Uh?" + +"mission",QuestValue(293)=1, QuestValue(294)=0 -> "Please hurry. Bring me whisper moss from dworc lair. Make sure it is from dworc lair! Take it yourself only! If you need to hear background of all again, ask Hairycles for background." +"background",QuestValue(293)=1 -> "So listen, little ape was struck by plague. Hairycles not does know what plague it is. That is strange. Hairycles should know. But Hairycles learnt lots and lots ...", "Me sure to make cure so strong to drive away all plague. But to create great cure me need powerful components ...", "Me need whisper moss. Whisper moss growing south of human settlement is. Problem is, evil little dworcs harvest all whisper moss immediately ...", "Me know they hoard some in their underground lair. My people raided dworcs often before humans came. So we know the moss is hidden in east of upper level of dworc lair ...", "You go there and take good moss from evil dworcs. Talk with me about mission when having moss." + +"mission",QuestValue(293)=1, QuestValue(294)=1 -> Type=4827,Amount=1,"Oh, you brought me whisper moss? Good hairless ape you are! Can me take it?",topic=3 + +"no", topic=3 -> "Strange being you are! Our people need help!", idle +"yes", topic=3,Count(Type) "Stupid, you no have the moss me need. Go get it. It's somewhere in dworc lair. If you lost it, they might restocked it meanwhile. If you need to hear background of all again, ask Hairycles for background.",SetQuestValue(294,0) + +"yes",Count(Type)>=Amount, topic=3 -> "Ah yes! That's it. Thank you for bringing mighty whisper moss to Hairycles. It will help but still much is to be done. Just ask for other mission if you ready.",Delete(Type) ,SetQuestValue(293,2) + +"mission",QuestValue(293)=2 -> "Whisper moss strong is, but me need liquid that humans have to make it work ...", "Our raiders brought it from human settlement, it's called cough syrup. Go ask healer there for it.",SetQuestValue(293,3) + + +"mission",QuestValue(293)=3 -> Type=4828,Amount=1,"You brought me that cough syrup from human healer me asked for?", topic=4 + +"no", topic=4 -> "Please hurry, urgent it is!" +"yes", topic=4,Count(Type) "No no, not right syrup you have. Go get other, get right health syrup." +"yes", topic=4,Count(Type)>=Amount -> "You so good! Brought syrup to me! Thank you, will prepare cure now. Just ask for mission if you want help again.",Delete(Type),SetQuestValue(293,4) + +"mission",QuestValue(293)=4 -> "Little ape should be healthy soon. Me so happy is. Thank you again! But me suspect we in more trouble than we thought. Will you help us again?", topic=5 + +"no", topic=5 -> "Me sad. Me expected better from you!", idle +"yes", topic=5 -> "So listen, please. Plague was not ordinary plague. That's why Hairycles could not heal at first. It is new curse of evil lizard people ...", "I think curse on little one was only a try. We have to be prepared for big strike ...", "Me need papers of lizard magician! For sure you find it in his hut in their dwelling. It's south east of jungle. Go look there please! Are you willing to go?", topic=6 +"no", topic=6 -> "Me sad. Me expected better from you!", idle +"yes", topic=6 -> "Good thing that is! Report about your mission when have scroll.",SetQuestValue(293,5) +"mission",QuestValue(293)=5 -> Type=4831,Amount=1,"You got scroll from lizard village in south east?", topic=7 + +"no", topic= 7 -> "That's bad news. If you lost it, only way to get other is to kill holy serpents. But you can't go there so you must ask adventurers who can." + +#### Einschub, notwendige Erklärung +"holy", "serpent" -> "Ugly beasts that are holy to lizard people. Only found in ancient temple under Banuta. But me can not allow you to go there." + +"yes", topic= 7,QuestValue(295)=0,Count(Type) "No! That not scroll me looking for. Silly hairless ape you are. Go to village of lizards and get it there on your own!" +"yes", topic= 7,QuestValue(295)=1,Count(Type) "Oh, you seem to have lost scroll? That's bad news. If you lost it, only way to get other is to kill holy serpents. But you can't go there so you must ask adventurers who can." +"yes", topic= 7,QuestValue(295)=1,Count(Type)>=Amount ->"You brought scroll with lizard text? Good! I will see what text tells me! Come back when ready for other mission.",Delete(Type),SetQuestValue(293,6), Idle + +"mission",QuestValue(293)=6 -> "Ah yes that scroll. Sadly me not could read it yet. But the holy banana me insight gave! In dreams Hairycles saw where to find solution ...", "Me saw a stone with lizard signs and other signs at once. If you read signs and tell Hairycles, me will know how to read signs ...", "You go east to big desert. In desert there city. East of city under sand hidden tomb is. You will have to dig until you find it, so take shovel ...", "Go down in tomb until come to big level and then go down another. There you find a stone with signs between two huge red stones ...", "Read it and return to me. Are you up to that challenge?", topic=8 + +"no", topic=8 -> "Me sad. Me expected better from you!", idle +"yes", topic=8 -> "Good thing that is! Report about mission when you have read those signs.",SetQuestValue(293,7) + +"mission",QuestValue(293)=7, QuestValue(296)=0 -> "You still don't know signs on stone, go and look for it in tomb east in desert." +"mission",QuestValue(293)=7, QuestValue(296)=1 -> "Ah yes, you read the signs in tomb? Good! May me look into your mind to see what you saw?", topic=9 + +"no", topic=9 -> "Me need to see it in your mind, other there is no way to proceed." +"yes", topic=9 -> EffectOpp(13),"Oh, so clear is all now! Easy it will be to read the signs now! Soon we will know what to do! Thank you again! Ask for mission if you feel ready.",SetQuestValue(293,8), idle + + +"mission",QuestValue(293)=8 -> "So much there is to do for Hairycles to prepare charm that will protect all ape people ...", "You can help more. To create charm of life me need mighty token of life! Best is egg of a regenerating beast as a hydra is ...", "Bring me egg of hydra please. You may fight it in lair of Hydra at little lake south east of our lovely city Banuta! You think you can do?", topic=10 + +"no", topic=10 -> "Me sad. Me expected better from you!", idle +"yes", topic=10 -> "You brave hairless ape! Get me hydra egg. If you lose egg, you probably have to fight many, many hydras to get another.",SetQuestValue(293,9) + +"mission",QuestValue(293)=9 -> Type=4839,Amount=1,"You bring Hairycles egg of hydra?", topic=11 +"no", topic=11 -> "Please hurry. Hairycles not knows when evil lizards strike again." +"yes", topic=11,Count(Type) "You not have egg of hydra. Please get one!" +"yes", topic=11,Count(Type)>=Amount -> "Ah, the egg! Mighty warrior you be! Thank you. Hairycles will put it at safe place immediately.",Delete(Type),SetQuestValue(293,10), idle + +"mission",QuestValue(293)=10 -> "Last ingredient for charm of life is thing to lure magic. Only thing me know like that is mushroom called witches' cap. Me was told it be found in isle called Fibula, where humans live ...", "Hidden under Fibula is a secret dungeon. There you will find witches' cap. Are you willing to go there for good ape people?", topic=12 + +"no", topic=12 -> "Me sad. Me expected better from you!", idle +"yes", topic=12 -> "Long journey it will take, good luck to you.",SetQuestValue(293,11) + +"mission",QuestValue(293)=11 -> Type=4829,Amount=1,"You brought Hairycles witches' cap from Fibula?", topic=18 +"no", topic=18 -> "Please try to find me a witches' cap on Fibula." +"yes", topic=18,Count(Type) "Not right mushroom you have. Find me a witches' cap on Fibula!" +"Yes", topic=18,Count(Type)>=Amount -> "Incredible, you brought a witches' cap! Now me can prepare mighty charm of life. Yet still other missions will await you, friend.",SetQuestValue(293,12),Delete(Type) +####(begrüßung ändert sich nun) + + +"mission",QuestValue(293)=12 -> "Mighty life charm is protecting us now! But my people are still in danger. Danger from within ...", "Some of my people try to mimic lizards to become strong. Like lizards did before, this cult drinks strange fluid that lizards left when fled ...", "Under the city still the underground temple of lizards is. There you find casks with red fluid. Take crowbar and destroy three of them to stop this madness. Are you willing to do that?", topic=13 + +"no", topic=13 -> "Me sad. Please reconsider." +"yes", topic=13 -> "Hairycles sure you will make it. Good luck, friend.", SetQuestValue(293,13) + +"mission",QuestValue(293)=13,QuestValue(297)<3 -> "Please destroy three casks in the complex beneath Banuta, so my people will come to senses again." +"mission",QuestValue(293)=13,QuestValue(297)>2 -> "You do please Hairycles again, friend. Me hope madness will not spread further now. Perhaps you are ready for other mission.", SetQuestValue(293,14) + +"mission",QuestValue(293)=14 -> "Now that the false cult was stopped, we need to strengthen the spirit of my people. We need a symbol of our faith that ape people can see and touch ...", "Since you have proven a friend of the ape people I will grant you permission to enter the forbidden land ...", "To enter the forbidden land in the north-east of the jungle, look for a cave in the mountains east of it. There you will find the blind prophet ...", "Tell him Hairycles you sent and he will grant you entrance ...", "Forbidden land is home of Bong. Holy giant ape big as mountain. Don't annoy him in any way but look for a hair of holy ape ...", "You might find at places he has been, should be easy to see them since Bong is big ...", "Return a hair of the holy ape to me. Will you do this for Hairycles?", topic=14 + +"no", topic=14 -> "Me sad. Please reconsider." +"yes", topic=14 -> "Hairycles proud of you. Go and find holy hair. Good luck, friend.", SetQuestValue(293,15) + +"mission",QuestValue(293)=15,QuestValue(298)=0 -> "Get a hair of holy ape from forbidden land in east. Speak with blind prophet in cave." +"mission",QuestValue(293)=15,QuestValue(298)=1 -> Type=4832,Amount=1,"You brought hair of holy ape?", topic=15 + +"no", topic=15 -> "Go to forbidden land in east to find hair." +"yes", topic=15,Count(Type) "You no have hair. You lost it? Go and look again.", SetQuestValue(298,0) +"yes", topic=15,Count(Type)>=Amount -> "Incredible! You got a hair of holy Bong! This will raise the spirit of my people. You are truly a friend. But one last mission awaits you.",SetQuestValue(293,16),Delete(Type) + +"mission",QuestValue(293)=16 -> "You have proven yourself a friend, me will grant you permission to enter the deepest catacombs under Banuta which we have sealed in the past ...", "Me still can sense the evil presence there. We did not dare to go deeper and fight creatures of evil there ...", "You may go there, fight the evil and find the monument of the serpent god and destroy it with hammer me give to you ...", "Only then my people will be safe. Please tell Hairycles, will you go there?", topic=16 + +"no", topic=16 -> "Me sad. Please reconsider." +"yes", topic=16 -> "Hairycles sure you will make it. Just use hammer on all that looks like snake or lizard. Tell Hairycles if you succeed with mission.", Create(4835), SetQuestValue(293,17) + +"mission",QuestValue(293)=17,QuestValue(299)=0 -> "Me know its much me asked for but go into the deepest catacombs under Banuta and destroy the monument of the serpent god." +"mission",QuestValue(293)=17,QuestValue(299)=1 -> "Finally my people are safe! You have done incredible good for ape people and one day even me brethren will recognise that ...", "I wish I could speak for all when me call you true friend but my people need time to get accustomed to change ...", "Let us hope one day whole Banuta will greet you as a friend. Perhaps you want to check me offers for special friends... or shamanic powers.",SetQuestValue(293,18) +"mission",QuestValue(293)=18 -> "No more missions await you right now, friend. Perhaps you want to check me offers for special friends." + +### ACHTUNG TOPIC 18 OBEN VERWENDET +"offer",QuestValue(293)>11,QuestValue(293)<17 -> "Me offer tasty bananas." +"offer",QuestValue(293)>17 -> "Me offer tasty bananas. Me also sell statues of holy apes of wisdom. Statue of no talking, statue of no hearing, statue of no seeing." +"furniture",QuestValue(293)>17 -> * +"goods",QuestValue(293)>17 -> * +"do","you","sell",QuestValue(293)>17 -> * +"do","you","have",QuestValue(293)>17 -> * + +"statue",QuestValue(293)>17 -> "Me sell statues of holy apes of wisdom. Statue of no speaking, statue of no hearing, statue of no seeing." +"speaking",QuestValue(293)>17 -> Type=5088, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"seeing",QuestValue(293)>17 -> Type=5086, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"hearing",QuestValue(293)>17 -> Type=5087, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"banana",QuestValue(293)>11 -> Type=3587, Amount=1, Price=2, "You want buy this banana for %P gold?", Topic=81 +%1,1<%1,"banana",QuestValue(293)>11 -> Type=3587, Amount=%1, Price=%1*2, "You want buy %A bananas for %P gold?", Topic=81 + + +Topic=81,"yes",CountMoney>=Price -> "Here is what you want.", DeleteMoney, Create(Type) +Topic=81,"yes" -> "Me sorry, you no money." +Topic=81 -> "As you whish, but no better in whole jungle you will find." + +"outfit",QuestValue(293)>17,QuestValue(17570)=0 -> "Me truly proud of you, friend. You learn many about plants, charms and ape people. Me want grant you shamanic power now. You ready?", Topic=82 +"shamanic","powers",QuestValue(293)>17,QuestValue(17570)=0 -> * +Topic=82,"yes" -> "Friend of the ape people! Take my gift and become me apprentice! Here is shaman clothing for you!", AddOutfit(158), AddOutfit(154), SetQuestValue(17570,1), EffectOpp(13) +Topic=82 -> "Maybe next time." + +"outfit",QuestValue(17570)>0 -> "Be greeted, friend %N of the ape people!" +"shamanic","powers",QuestValue(17570)>0 -> * + +} diff --git a/app/SabrehavenServer/data/npc/halif.npc b/app/SabrehavenServer/data/npc/halif.npc new file mode 100644 index 0000000..1612742 --- /dev/null +++ b/app/SabrehavenServer/data/npc/halif.npc @@ -0,0 +1,95 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Halif.npc: Datenbank für den Händler Halif + +Name = "Halif" +Outfit = (128,95-2-10-131-0) +Home = [33217,32423,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, you child of wealth and generousity. What wise decision to buy my wares." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, please could you wait a moment until I finish this deal.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, may Daraman bless your travels." + +"bye" -> "Good bye, may Daraman bless your travels.", Idle +"job" -> "Oh, I guess your cleverness already made the profession of the humble equipment tradesman obvious to you." +"light" -> "I sell torches, candlesticks, candelabras, and oil, o seeker of enlightment." +"name" -> "I am Halif Ibn Onor, known as Halif the honest." +"time" -> "I would love to tell you the time, but I can not make the watchmaker's kids starve as a gazelle in the heart of the desert." +"food" -> "I am deeply sorry but you have to look for that elsewhere." + +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, watches, fishing rods and sixpacks of worms. Of course, I sell lightsources, too." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2858, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2866, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 +"water","hose" -> Type=2901, Data=1, Amount=1, Price=40, "Do you want to buy a water hose for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2858, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2866, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Data=1, Amount=%1, Price=40*%1, "Do you want to buy %A water hoses for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 + +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "I hope it will serve you well, my prized customer.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "My twelve starving children don't allow me to sell it for less, o grandmaster of haggling." +Topic=1 -> "What a pity." + +Topic=3,"yes",Count(Type)>=Amount -> "I hardly can explain my wife I gave you that much money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you own none." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=3 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/hamilton.npc b/app/SabrehavenServer/data/npc/hamilton.npc new file mode 100644 index 0000000..2750129 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hamilton.npc @@ -0,0 +1,23 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Hamilton" +Outfit = (128,98-37-116-76-0) +Home = [32200,32792,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "A good day to you." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/hanna.npc b/app/SabrehavenServer/data/npc/hanna.npc new file mode 100644 index 0000000..17eae58 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hanna.npc @@ -0,0 +1,98 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hanna.npc: Datenbank für die Juwelierin Hanna + +Name = "Hanna" +Outfit = (136,113-65-0-96-2) +Home = [32407,32219,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a jeweler. Maybe you want to have a look at my wonderful offers." +"name" -> "I am Hanna." +"time" -> "Currently it is %T." + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +"addon",QuestValue(18501)=0 -> "Pretty, isn't it? My friend Amber taught me how to make it, but I could help you with one if you like. What do you say?", Topic=3 +"hat",QuestValue(18501)=0 -> * +Topic=3,"yes" -> "Okay, here we go, listen closely! I need a few things... a basic hat of course, maybe a legion helmet would do. Then about 100 chicken feathers...", + "and 50 honeycombs as glue. That's it, come back to me once you gathered it!", SetQuestValue(18501,1), SetQuestValue(17594,1) +Topic=3 -> "Maybe another time." + +"addon",QuestValue(18501)=1 -> "Oh, you're back already? Did you bring a legion helmet, 100 chicken feathers and 50 honeycombs?", Topic=4 +"hat",QuestValue(18501)=1 -> * +Topic=4,"yes",Count(3374)>=1,Count(5890)>=100,Count(5902)>=50 -> "Great job! That must have taken a lot of work. Okay, you put it like this... then glue like this... here!", DeleteAmount(3374,1), DeleteAmount(5890,100), DeleteAmount(5902,50), SetQuestValue(18501,2), AddOutfitAddon(136,2), AddOutfitAddon(128,2), EffectOpp(13) +Topic=4,"yes" -> "You don't have required ingredients." +Topic=4 -> "Maybe another time." + +"addon",QuestValue(18501)>1 -> "I hope you enjoy the hat!" +"hat",QuestValue(18501)>1 -> * +} diff --git a/app/SabrehavenServer/data/npc/hardek.npc b/app/SabrehavenServer/data/npc/hardek.npc new file mode 100644 index 0000000..60dfd51 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hardek.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Hardek.npc: Datenbank für den Aufkäufer Hardek + +Name = "Hardek" +Outfit = (129,96-86-63-115-0) +Home = [32272,32339,7] +Radius = 20 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need my services?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N, I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Visit me whenever you want to sell something." + +"bye" -> "Good bye. Visit me whenever you want to sell something.", Idle +"farewell" -> * +"job" -> "I am buying some weapons and armors." +"forestaller" -> * +"name" -> "I am Hardek, the forestaller." +"time" -> "It is %T." +"help" -> "I buy stuff. If you want to sell something, offer it to me, and we'll see if it catches my interest." +"thanks" -> "You are welcome." +"thank","you" -> * + +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=31, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","guardian","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell a guardian shield for %P gold?", Topic=2 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell a dragon shield for %P gold?", Topic=2 + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell a coat for %P gold?", Topic=2 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell a jacket for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell a knight armor for %P gold?", Topic=2 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500, "Do you want to sell a golden armor for %P gold?", Topic=2 + +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "Do you want to sell a devil helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "Do you want to sell a warrior helmet for %P gold?", Topic=2 + +"sell","leather","legs" -> Type=3559, Amount=1, Price=1, "Do you want to sell leather legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "Do you want to sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "Do you want to sell knight legs for %P gold?", Topic=2 + +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "Do you want to sell a longsword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000, "Do you want to sell a fire sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 + +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=31*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell %A guardians shields for %P gold?", Topic=2 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=2 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell %A coats for %P gold?", Topic=2 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell %A jackets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell %A knight armors for %P gold?", Topic=2 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1, "Do you want to sell %A golden armors for %P gold?", Topic=2 + +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "Do you want to sell %A devil helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "Do you want to sell %A warrior helmets for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=1*%1, "Do you want to sell %A leather legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "Do you want to sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "Do you want to sell %A knight legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "Do you want to sell %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1, "Do you want to sell %A fireswords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/haroun.npc b/app/SabrehavenServer/data/npc/haroun.npc new file mode 100644 index 0000000..93153e3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/haroun.npc @@ -0,0 +1,204 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# haroun.npc: Datenbank für den Maridhändler Haroun (Magische Gegenstände, Marid) + +Name = "Haroun" +Outfit = (80,0-0-0-0-0) +Home = [33108,32525,4] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Be greeted, human %N. How can a humble djinn be of service?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Please wait, human %N. I'll be with you in a minute.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Farewell! May the serene light of the enlightened one rest shine on your travels.", Idle +"farewell" -> * +"name" -> "My name is Haroun. At your service." +"haroun" -> "That is the name my honourable father chose for me." +"job" -> "I am a merchant. Though I have not chosen this position for myself I have accepted the task with humility and devotion. ...", + "If you have the permission of Gabel to trade with me, I can sell you some useful magical equipment." +"permission" -> "I am not allowed to trade with you unless Gabel gave you the permission to trade with us." + +"trade" -> "It is my mission to trade items on behalf of our community. ...", + "Just ask me for my current offers." +"merchant" -> * +"gabel" -> "He is our leader. Not because he desires power or distinction, of course, but because the community needs a guide in spiritual and in worldly matters. ...", + "He has accepted this burden with the modesty that is required of a true follower of Daraman." +"king" -> "We do not have kings. Of the members of our community, Gabel is closest to being what you would consider a leader, but he would resent being called by that presumptuous title." +"djinn" -> "That is my race. I like to compare it to iron. It has the potential to be forged into the finest steel, but it does need a lot of work and devotion. ...", + "Without that it is nothing but worthless scrap metal. Like our master said in the scriptures: Forge thyself! Be a blade of the true creed! (Book VI, chapter 14, verses 3 and 4)." +"efreet" -> "Daraman has taught us restraint. But I am sure one day those apostates will pay dearly for their sinful rebellion!" +"marid" -> "We are the chosen ones. Those who walk the path to enlightenment with serenity and humility." +"malor" -> "That filthy heretic! To me he is the very embodiment of all the evilness and the baseness we djinn must leave behind." +"mal'ouquah" -> "That is the name of the Efreets' hideout. It certainly is one filthy hotbed of vice and degeneration." +"ashta'daramai" -> "This is the name of the humble abode you are currently visiting. After Daraman had shown him the true way Gabel decided to tear down his former palace and to erect the present structure. ...", + "Its name is meant as a tribute to the invaluable service Daraman has done to our race." +"human" -> "Back in the dark days we used to be enemies, the humans and us. But then a human came and radically changed the way we see things. ...", + "He taught us to see your race as brothers. Of course... of course we still have lessons to learn, I suppose." + +"zathroth" -> "An evil, evil god. He was the creator of my race, but he rejected us because we were not evil enough and far too independent to his taste. For this reason the djinn do not worship him anymore, not even those depraved efreet. ...", + "It is difficult, you know. We djinn had to learn to find our own way, to create a destiny of our own. And although we are not evil by nature the fact that we were created by this fiend was lying like a curse on us. ...", + "For a long time we walked in darkness, filled with hate of ourselves and of all creation. But then everything changed. A human came and made us realise how blind we were. ...", + "Praise the gods who have spoken to us through Daraman! That human taught us how we could overcome our grim heritage and heal the scars that marred our souls. Humility and asceticism are the keys to a fuller existence. ...", + "The task may be tedious, but the price that awaits the disciples of the true creed is worth it all." + +"tibia" -> "Despite all the evil that stalks this world it is still a source of endless marvel and joy to me. Like the master said: ...", + "'Look at the world and reflect in admiration. For there is peace in a sunset and wisdom in the ocean's neverending melody.' (Book I, chapter 3, verses 2 and 3)." +"daraman" -> "How could my unworthy tongue ever find words to praise the enlightened one the way he deserves to be praised? Daraman took the veil from our eyes. He made us realise the extent of our misery. ...", + "However, he did much more than that. He showed us a way out. He gave meaning to our lives. The service he has done to my race will never be forgotten." +"darashia" -> "I understand the humans living there are self-indulgent and decadent. I am sure Daraman would disapprove of their ways." +"scarab" -> "The scarab is an ancient species. It's chitinous shell can be turned into excellent armour." +"edron" -> "They say the humans have founded impressive communities to the north. I hope their spiritual integrity matches their entrepreneurial spirit." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "Ankrahmun has a venerable tradition, but now that heretic fool has acceeded to the throne it has degenerated into a breeding place for wicked heresy." +"pharaoh" -> "I have heard many a gruesome stories about this presumptuous fool, who calls himself a pharaoh. Such hubris! Daraman himself, who was certainly the most divine mortal that ever walked Tibia, would never have dared to call himself a god." +"palace" -> "I am a peaceful djinn, but with all those stories I have heard I really feel like visiting the palace and to cleanse this place from all the evil and the misbelief infesting that place." +"ascension" -> "I have heard this is what the pharaoh has promised his followers. It is sad to see this wicked sinner is still allowed to spread his heresy!" +"rah" -> "Another one of the pharaoh's confused ideas." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "The Kha'zeel mountains have been created by the gods as a vantage point from where they could watch their creation. ...", + "Isn't it uplifting to think that even now we are standing on ground that was blessed by the great creators." +"kha'labal" -> "Kha'labal. It makes me sad just to hear that name. Long, long time ago this used to be a garden, you know? ...", + "You should have seen it then. You should have seen the springs and the meadows and the flowers. It was a paradise. . Oh, this terrible war." +"melchior" -> "I remember Malchior. I used to meet him regularly, but strictly in matters of business. He was not really a likeable fellow then. ...", + "All money and profit he was, and not as quite as clever as he thought. He made quite a pretty penny by doing business both with us and the efreet. ...", + "He thought we wouldn't notice, but the truth is we just chose to look the other way." +"alesar" -> "Who? Yes, I... I have never... I mean, I don't know who that is." +"fa'hradin" -> "Fa'hradin is a learned man, and this alone is enough to make me respect him. However, I sometimes can't help the feeling that he is lacking spiritual strength." +"bo'ques" -> "Bo'ques is a cook. Cooking! What a useless craft. If I were Gabel I would have little use for a cook such as him, but perhaps not all djinns are as spiritual as Daraman would have them be." +"lamp" -> "The lamp is a djinn's resting place of choice." + +"weapon" -> "I'm afraid I do not trade with weapons or armour. Nah'bob only deals with magical equipment." +"armor" -> * +"armour" -> * +"wares" -> "I only deal with magical equipment. Our range of goods include amulets, rings, wands and some special items." +"offer" -> * +"goods" -> * +"equipment" -> * +"magical" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"amulets" -> "I'm selling and buying bronze amulets, stone skin amulets, elven amulets and garlic necklaces." +"rings" -> "I'm selling and buying stealth rings, power rings, sword rings, axe rings, and club rings." +"wands" -> "I'm buying wands of vortex, wands of dragonbreath, wands of plague, wands of cosmic energy and wands of inferno as well as magic light wands." +"rods" -> "I'm not interested in rods." +"special" -> "I'm selling and buying magic light wands. I'm currently also looking for mind stones, life crystals and orbs." + +"light" -> Type=3046, Amount=1, Price=120, "Do you want to buy a magic light wand for %P gold?", Topic=10 +"sword","ring" -> Type=3091, Amount=1, Price=500, "Do you want to buy a sword ring for %P gold?", Topic=10 +"axe","ring" -> Type=3092, Amount=1, Price=500, "Do you want to buy an axe ring for %P gold?", Topic=10 +"club","ring" -> Type=3093, Amount=1, Price=500, "Do you want to buy a club ring for %P gold?", Topic=10 +"stone","skin","amulet" -> Type=3081, Amount=1, Price=5000, "Do you want to buy a stone skin amulet for %P gold?", Topic=10 +"elven","amulet" -> Type=3082, Amount=1, Price=500, "Do you want to buy an elven amulet for %P gold?", Topic=10 +"garlic","necklace" -> Type=3083, Amount=1, Price=100, "Do you want to buy a garlic necklace for %P gold?", Topic=10 +"bronze","amulet" -> Type=3056, Amount=1, Price=100, "Do you want to buy a bronze amulet for %P gold?", Topic=10 +"stealth","ring" -> Type=3049, Amount=1, Price=5000, "Do you want to buy a stealth ring for %P gold?", Topic=10 +"power","ring" -> Type=3050, Amount=1, Price=100, "Do you want to buy a power ring for %P gold?", Topic=10 + +%1,1<%1,"light" -> Type=3046, Amount=%1, Price=120*%1, "Do you want to buy %A magic light wands for %P gold?", Topic=10 +%1,1<%1,"power","ring" -> Type=3050, Amount=%1, Price=100*%1, "Do you want to buy %A power rings for %P gold?", Topic=10 +%1,1<%1,"bronze","amulet" -> Type=3056, Amount=%1, Price=100*%1, "Do you want to buy %A bronze amulets for %P gold?", Topic=10 +%1,1<%1,"sword","ring" -> Type=3091, Amount=%1, Price=500*%1, "Do you want to buy %A sword rings for %P gold?", Topic=10 +%1,1<%1,"axe","ring" -> Type=3092, Amount=%1, Price=500*%1, "Do you want to buy %A axe rings for %P gold?", Topic=10 +%1,1<%1,"club","ring" -> Type=3093, Amount=%1, Price=500*%1, "Do you want to buy %A club rings for %P gold?", Topic=10 +%1,1<%1,"elven","amulet" -> Type=3082, Amount=%1, Price=500*%1, "Do you want to buy %A elven amulets for %P gold?", Topic=10 +%1,1<%1,"garlic","necklace" -> Type=3083, Amount=%1, Price=100*%1, "Do you want to buy %A garlic necklaces for %P gold?", Topic=10 +%1,1<%1,"stone","skin","amulet" -> Type=3081, Amount=%1, Price=5000*%1, "Do you want to buy %A stone skin amulets for %P gold?", Topic=10 +%1,1<%1,"stealth","ring" -> Type=3049, Amount=%1, Price=5000*%1, "Do you want to buy %A stealth rings for %P gold?", Topic=10 + +"sell","light" -> Type=3046, Amount=1, Price=35, "Do you want to sell a magic light wand for %P gold?", Topic=11 +"sell","sword","ring" -> Type=3091, Amount=1, Price=100, "Do you want to sell a sword ring for %P gold?", Topic=11 +"sell","axe","ring" -> Type=3092, Amount=1, Price=100, "Do you want to sell an axe ring for %P gold?", Topic=11 +"sell","club","ring" -> Type=3093, Amount=1, Price=100, "Do you want to sell a club ring for %P gold?", Topic=11 +"sell","stone","skin","amulet" -> Type=3081, Amount=1, Price=500, "Do you want to sell a stone skin amulet for %P gold?", Topic=11 +"sell","elven","amulet" -> Type=3082, Amount=1, Price=100, "Do you want to sell an elven amulet for %P gold?", Topic=11 +"sell","garlic","necklace" -> Type=3083, Amount=1, Price=50, "Do you want to sell a garlic necklace for %P gold?", Topic=11 +"sell","bronze","amulet" -> Type=3056, Amount=1, Price=50, "Do you want to sell a bronze amulet for %P gold?", Topic=11 +"sell","stealth","ring" -> Type=3049, Amount=1, Price=200, "Do you want to sell a stealth ring for %P gold?", Topic=11 +"sell","power","ring" -> Type=3050, Amount=1, Price=50, "Do you want to sell a power ring for %P gold?", Topic=11 +"sell","mind","stone" -> Type=3062, Amount=1, Price=100, "Do you want to sell a mind stone for %P gold?", Topic=11 +"sell","life","crystal" -> Type=3061, Amount=1, Price=50, "Do you want to sell a life crystal for %P gold?", Topic=11 +"sell","orb" -> Type=3060, Amount=1, Price=750, "Do you want to sell an orb for %P gold?", Topic=11 + +"sell",%1,1<%1,"light" -> Type=3046, Amount=%1, Price=35*%1, "Do you want to sell %A magic light wands for %P gold?", Topic=11 +"sell",%1,1<%1,"power","ring" -> Type=3050, Amount=%1, Price=50*%1, "Do you want to sell %A power rings for %P gold?", Topic=11 +"sell",%1,1<%1,"bronze","amulet" -> Type=3056, Amount=%1, Price=50*%1, "Do you want to sell %A bronze amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"sword","ring" -> Type=3091, Amount=%1, Price=100*%1, "Do you want to sell %A sword rings for %P gold?", Topic=11 +"sell",%1,1<%1,"axe","ring" -> Type=3092, Amount=%1, Price=100*%1, "Do you want to sell %A axe rings for %P gold?", Topic=11 +"sell",%1,1<%1,"club","ring" -> Type=3093, Amount=%1, Price=100*%1, "Do you want to sell %A club rings for %P gold?", Topic=11 +"sell",%1,1<%1,"elven","amulet" -> Type=3082, Amount=%1, Price=100*%1, "Do you want to sell %A elven amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"garlic","necklace" -> Type=3083, Amount=%1, Price=50*%1, "Do you want to sell %A garlic necklaces for %P gold?", Topic=11 +"sell",%1,1<%1,"stone","skin","amulet" -> Type=3081, Amount=%1, Price=500*%1, "Do you want to sell %A stone skin amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"stealth","ring" -> Type=3049, Amount=%1, Price=200*%1, "Do you want to sell %A stealth rings for %P gold?", Topic=11 +"sell",%1,1<%1,"mind","stone" -> Type=3062, Amount=%1, Price=100*%1, "Do you want to sell %A mind stones for %P gold?", Topic=11 +"sell",%1,1<%1,"life","crystal" -> Type=3061, Amount=%1, Price=50*%1, "Do you want to sell %A life crystals for %P gold?", Topic=11 +"sell",%1,1<%1,"orb" -> Type=3060, Amount=%1, Price=750*%1, "Do you want to sell %A orbs for %P gold?", Topic=11 + +"sell","wand","of","vortex" -> Type=3074, Amount=1, Price=100, "Do you want to sell a wand of vortex for %P gold?", Topic=11 +"sell","wand","of","dragonbreath" -> Type=3075, Amount=1, Price=200, "Do you want to sell a wand of dragonbreath for %P gold?", Topic=11 +"sell","wand","of","plague" -> Type=3072, Amount=1, Price=1000, "Do you want to sell a wand of plague for %P gold?", Topic=11 +"sell","wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=2000, "Do you want to sell a wand of cosmic energy for %P gold?", Topic=11 +"sell","wand","of","inferno" -> Type=3071, Amount=1, Price=3000, "Do you want to sell a wand of inferno for %P gold?", Topic=11 + +"sell",%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=100*%1, "Do you want to sell %A wands of vortex for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=200*%1, "Do you want to sell %A wands of dragonbreath for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=1000*%1, "Do you want to sell %A wands of plague for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=2000*%1, "Do you want to sell %A wands of cosmic energy for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","inferno" -> Type=3071, Amount=%1, Price=3000*%1, "Do you want to sell %A wands of inferno for %P gold?", Topic=11 + +Topic=10,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Thank you. May it bring you luck!", DeleteMoney, Create(Type) +Topic=10,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=10 -> "I understand. Perhaps another time then." + +Topic=11,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Thank you. Here is your money.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one." +Topic=11,"yes",Amount>1 -> "You do not have that many." +Topic=11 -> "I understand. Perhaps another time then." + +"fighting","spirit" -> Type=3392, Amount=2, "I need two royal helmets to extract one container of fighting spirit. Would you like me to perform the extraction?", Topic=12 +Topic=12,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=12,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5884, Amount=1, Create(Type) +Topic=12,"yes" -> "You do not have one." +Topic=12,"yes",Amount>1 -> "You do not have that many." +Topic=12 -> "I understand. Perhaps another time then." + +"magic","sulphur" -> Type=3280, Amount=3, "I need three fire sword to extract one magic sulphur. Would you like me to perform the extraction?", Topic=13 +Topic=13,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=13,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5904, Amount=1, Create(Type) +Topic=13,"yes" -> "You do not have one." +Topic=13,"yes",Amount>1 -> "You do not have that many." +Topic=13 -> "I understand. Perhaps another time then." + +"warrior","sweat" -> Type=3369, Amount=4, "I need four warrior helmets to extract one flask of warrior's sweat. Would you like me to perform the extraction?", Topic=14 +Topic=14,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=14,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5885, Amount=1, Create(Type) +Topic=14,"yes" -> "You do not have one." +Topic=14,"yes",Amount>1 -> "You do not have that many." +Topic=14 -> "I understand. Perhaps another time then." + +"chicken","wing" -> Type=3079, Amount=1, "I need one pair of boots of haste to extract one enchanted chicken wing. Would you like me to perform the extraction?", Topic=15 +Topic=15,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=15,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5891, Amount=1, Create(Type) +Topic=15,"yes" -> "You do not have one." +Topic=15,"yes",Amount>1 -> "You do not have that many." +Topic=15 -> "I understand. Perhaps another time then." + +} diff --git a/app/SabrehavenServer/data/npc/harsky.npc b/app/SabrehavenServer/data/npc/harsky.npc new file mode 100644 index 0000000..acf54ea --- /dev/null +++ b/app/SabrehavenServer/data/npc/harsky.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# harsky.npc: Datenbank für den Wachmann Harsky + +Name = "Harsky" +Outfit = (131,79-79-79-79-0) +Home = [32312,32170,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","king",! -> "HAIL TO THE KING!" +ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","tibianus",! -> "HAIL TO THE TIBIANUS!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","king",! -> "Wait for your audience!" +BUSY,"hail$","king",! -> "Wait for your audience!" +BUSY,"salutations$","king",! -> "Wait for your audience!" +BUSY,"hi$","king",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/app/SabrehavenServer/data/npc/helor.npc b/app/SabrehavenServer/data/npc/helor.npc new file mode 100644 index 0000000..4d318e5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/helor.npc @@ -0,0 +1,103 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# helor.npc: Datenbank für den Paladinlehrer Helor + +Name = "Helor" +Outfit = (134,57-79-95-98-0) +Home = [32572,32753,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted in the name of the gods, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Learn to show patience.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods watch over you." + +"bye" -> "May the gods watch you.", Idle +"farewell" -> * +"job" -> "I am a paladin and a teacher." +"name" -> "My name is Helor." +"time" -> "It is %T right now." +"king" -> "Our king will learn about the things happening here and he will be not amused." +"venore" -> "Those tradesmen would gladly sell their souls. And they would sell them cheap." +"thais" -> "Thais has its mistakes but it's a town's people that form a society and it's its people that have to be blamed for a society's failure." +"carlin" -> "In their own way they are still following the word of the gods." +"edron" -> "There are certain problems in Edron for sure and the defection of some of the knights was a great loss and caused much shame. But we are growing on the obstacles we have to overcome." +"jungle" -> "The jungle is a challenge and even here in this city you can feel its corruptive influence. It's always lurking to crush the ones that are weak in body or mind." + +"tibia" -> "The face of the world was sculpted by conflicts of the gods and the mortals." + +"kazordoon" -> "Dwarves abandoned the gods because they are shortsighted. They are lost people." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The believes of the elves are just a pack of lies to comfort their vanity. Only the gods have the power to elevate us beyond the restrictions of our mortal form. The elves' vanity will lead them to nothing." +"elves" -> * +"elfs" -> * +"darama" -> "It's up to us to fulfil the will of the gods even here at this remote continent." +"darashia" -> "The people there are not evil, they just follow a terribly wrong philosophy." +"ankrahmun" -> "An abnormality leading an abnormal cult. The day will come where our forces are strong enough to cleanse the city and the minds of the people." +"ferumbras" -> "Evil has many faces. He is only one of them." +"excalibug" -> "A weapon that should be used to slay evil wherever it shows its ugly face." +"apes" -> "They are intelligent enough to raid Port Hope in order to steal tools, so unlike other animals they are responsible for their wrongdoing and should be punished." +"lizard" -> "The lizards are aggressive enemies. It's obvious they never heard about our gods and their ideals." +"dworcs" -> "They are just another breed of orcs and they will be treated like them." +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "May the gods watch you.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/app/SabrehavenServer/data/npc/herbert.npc b/app/SabrehavenServer/data/npc/herbert.npc new file mode 100644 index 0000000..c5d75c7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/herbert.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Herbert" +Outfit = (132,78-93-16-113-0) +Home = [32332,32839,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "At last! The audience arrives!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking to a customer, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Alas! The crowds are gone and curtains fall! Was it but a dream?" + +"bye" -> "Thank you! Thank you! Sorry, no autographs today!", Idle +"farewell" -> * + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/hl.npc b/app/SabrehavenServer/data/npc/hl.npc new file mode 100644 index 0000000..1aa6d56 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hl.npc @@ -0,0 +1,352 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hl.npc: Datenbank für den Aufkäufer im Kriminellencamp + +Name = "H.L." +Outfit = (131,12-76-0-95-0) +Home = [32643,32212,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Hmmm. I buy weapons, armor, and other stuff. What do you want, %N?", Data=3303 +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "I don't serve brats. Sod off!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "HEY! Wait in the queue, %N.", Queue +BUSY,"hi$",male,! -> * +BUSY,"hello$",female,! -> "I don't serve brats. Sod off!" +BUSY,"hi$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "Bye" + +"bye",female -> "Get lost, stinky dragon.", Idle +"bye",male -> "Bye.", Idle +"job" -> "I buy all kinds of armory and weapons." +"name" -> "Won't tell you." +"h.l." -> "That's me." +"snake","eye" -> "Boss of the tavern. He's alright." +"boss" -> "Snake Eye isn't my boss." +"tavern" -> "Drink and eat there. What else do you do in a tavern!" +"brat" -> "Bah. Women are not good for fighting. I don't need them. And I don't like them." +"women" -> * +"woman" -> * + +"god" -> "Forget the gods" +"gods" -> * +"durin" -> "Forget Durin. He's the worst anyway." +"steve" -> "Forget Steve." +"guido" -> "Forget Guido." +"stephan" -> "Forget Stephan." +"cip" -> "Forget about Cip." + +"tibia" -> "Tibia. At least there's one good place in Tibia. Here!" +"thais" -> "Ha! Thais. I lived there. You know, I was in the royal army. But it's all wrong. I deserted." +"royal","army" -> "Good fighter training. But for the wrong cause." +"training" -> "Yes. Good training." +"cause" -> "Don't want to talk about it." +"talk" -> "I said, I do not want to talk about it", Topic=1 +Topic=1,"talk" -> "Ok. Get lost!", Idle + +"kazordoon" -> "Dwarfs are good people. I like them." +"dwarfs" -> "I like them." +"ab'dendriel" -> "Elves. Hate them." +"edron" -> "Might be a good place to live. But I'm afraid that the people are Thais friendly." +"king" -> "The king should be dead." +"ruler" -> "Tibia doesn't need a ruler." +"tibianus" -> "Hang him." + +"wild","warrior" -> "Yeah. I'm a wild warrior. Well, to be honest, I left them. They became too aggressive. Attacking everyone is not good." +"camp" -> "Most people in the camp are no wild warriors." +"hid" -> "Wild warriors have always something to hide." +"key" -> "What key? Show me!" +"key",Count(2970)>0 -> "Oh. that's a new key. Hmmm. Must be for the new hideout." +"hideout" -> "I left the wild warriors, while we - well - they planned a new hideout." +"new" -> "It's somewhere in the woods, of course. I don't know where." +"woods" -> "The woods are good to hide." +"building" -> "You mean our old building in the southwest?", Topic=2 +Topic=2,"yes" -> "That's the old hideout. It's interesting down there. Lots of security mechanics and traps. But it collapsed partly." +Topic=2,"no" -> "Sorry." + +"mechanics" -> "Yes. Security doors driven by POWERFUL machines. But I have no idea how it works.", Topic=7 +"machines" -> * +"traps" -> "Be careful out there." +"collapsed" -> "Yes. That's why we - well - they planned a new hideout. But I think they left the vault in the old hideout." +"vault" -> "Good stuff in there, I think." +"stuff" -> "Ahm. I don't know what it is. Sorry." +Topic=7, "broken" -> "Hmmm. Let me think. I think, you need something big. And steel reinforced. A barrel, maybe." +Topic=7, "damaged" -> * +Topic=7, "repair" -> * + +"sell" -> "I buy nearly everything. Just ask." + +# Ankauf von Waffen, Nummern 0-57 +"sell","sword" -> Type=3264, Amount=1, Price=7 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","battle","axe" -> Type=3266, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dagger" -> Type=3267, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","hand","axe" -> Type=3268, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","halberd" -> Type=3269, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","club" -> Type=3270, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","spike","sword" -> Type=3271, Amount=1, Price=25 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","rapier" -> Type=3272, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","sabre" -> Type=3273, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","axe" -> Type=3274, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","double","axe" -> Type=3275, Amount=1, Price=70 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","hatchet" -> Type=3276, Amount=1, Price=7 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","spear" -> Type=3277, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","magic","longsword" -> Type=3278, Amount=1, Price=460, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","war","hammer" -> Type=3279, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","fire","sword" -> Type=3280, Amount=1, Price=335, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","giant","sword" -> Type=3281, Amount=1, Price=100, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","morning","star" -> Type=3282, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","ice","rapier" -> Type=3284, Amount=1, Price=250, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","longsword" -> Type=3285, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","mace" -> Type=3286, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","throwing","star" -> Type=3287, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","magic","sword" -> Type=3288, Amount=1, Price=350, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","staff" -> Type=3289, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","silver","dagger" -> Type=3290, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","knife" -> Type=3291, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","combat","knife" -> Type=3292, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","sickle" -> Type=3293, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","short","sword" -> Type=3294, Amount=1, Price=3 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","bright","sword" -> Type=3295, Amount=1, Price=280, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","warlord","sword" -> Type=3296, Amount=1, Price=360, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","serpent","sword" -> Type=3297, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","throwing","knife" -> Type=3298, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","poison","dagger" -> Type=3299, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","katana" -> Type=3300, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","broadsword" -> Type=3301, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","lance" -> Type=3302, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","great","axe" -> Type=3303, Amount=1, Price=300, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","crowbar" -> Type=3304, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","sickle" -> Type=3306, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","scimitar" -> Type=3307, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","machete" -> Type=3308, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","thunder","hammer" -> Type=3309, Amount=1, Price=450, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","iron","hammer" -> Type=3310, Amount=1, Price=9 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","clerical","mace" -> Type=3311, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","silver","mace" -> Type=3312, Amount=1, Price=270, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","obsidian","lance" -> Type=3313, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","naginata" -> Type=3314, Amount=1, Price=80 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","guardian","halberd" -> Type=3315, Amount=1, Price=120, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","orcish","axe" -> Type=3316, Amount=1, Price=12 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","barbarian","axe" -> Type=3317, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","axe" -> Type=3318, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","stonecutter","axe" -> Type=3319, Amount=1, Price=320, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","fire","axe" -> Type=3320, Amount=1, Price=280, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=7*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=25*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=70*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=7*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"magic","longsword" -> Type=3278, Amount=%1, Price=460*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=335*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"giant","sword" -> Type=3281, Amount=%1, Price=100*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=250*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"magic","sword" -> Type=3288, Amount=%1, Price=350*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"silver","dagger" -> Type=3290, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"knife" -> Type=3291, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"combat","knife" -> Type=3292, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"knives" -> Type=3291, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"combat","knives" -> Type=3292, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=3*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"bright","sword" -> Type=3295, Amount=%1, Price=280*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"warlord","sword" -> Type=3296, Amount=%1, Price=360*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"throwing","knife" -> Type=3298, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"throwing","knives" -> Type=3298, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"poison","dagger" -> Type=3299, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"katana" -> Type=3300, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"broadsword" -> Type=3301, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","lance" -> Type=3302, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"great","axe" -> Type=3303, Amount=%1, Price=300*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","sickle" -> Type=3306, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"scimitar" -> Type=3307, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"thunder","hammer" -> Type=3309, Amount=%1, Price=450*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"iron","hammer" -> Type=3310, Amount=%1, Price=9*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"clerical","mace" -> Type=3311, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"silver","mace" -> Type=3312, Amount=%1, Price=270*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"naginata" -> Type=3314, Amount=%1, Price=80*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"guardian","halberd" -> Type=3315, Amount=%1, Price=120*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"orcish","axe" -> Type=3316, Amount=%1, Price=12*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"barbarian","axe" -> Type=3317, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","axe" -> Type=3318, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"stonecutter","axe" -> Type=3319, Amount=%1, Price=320*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"fire","axe" -> Type=3320, Amount=%1, Price=280*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +# Ankauf von Waffen (Bows), Nummern 1-2 +"sell","crossbow" -> Type=3349, Amount=1, Price=20 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","bow" -> Type=3350, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 + +"sell",%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=20*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 + +# Ankauf von Ruestung, Nummern 0-39 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=4 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=80 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","plate","armor" -> Type=3357, Amount=1, Price=110, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","chain","armor" -> Type=3358, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","armor" -> Type=3359, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","armor" -> Type=3360, Amount=1, Price=580, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","leather","armor" -> Type=3361, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","studded","legs" -> Type=3362, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","scale","legs" -> Type=3363, Amount=1, Price=180, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","legs" -> Type=3364, Amount=1, Price=120, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","helmet" -> Type=3365, Amount=1, Price=420, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","magic","plate","armor" -> Type=3366, Amount=1, Price=720, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=12 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","winged","helmet" -> Type=3368, Amount=1, Price=320, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=75 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","armor" -> Type=3370, Amount=1, Price=140, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","legs" -> Type=3371, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","legs" -> Type=3372, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","strange","helmet" -> Type=3373, Amount=1, Price=55 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","legion","helmet" -> Type=3374, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","soldier","helmet" -> Type=3375, Amount=1, Price=16 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","scale","armor" -> Type=3377, Amount=1, Price=75 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","armor" -> Type=3378, Amount=1, Price=18 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","doublet" -> Type=3379, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","rose","armor" -> Type=3380, Amount=1, Price=140, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","armor" -> Type=3381, Amount=1, Price=210, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","legs" -> Type=3382, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","armor" -> Type=3383, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","helmet" -> Type=3384, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","helmet" -> Type=3385, Amount=1, Price=70 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","scale","mail" -> Type=3386, Amount=1, Price=280, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","demon","helmet" -> Type=3387, Amount=1, Price=95 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","demon","armor" -> Type=3388, Amount=1, Price=195, "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","demon","legs" -> Type=3389, Amount=1, Price=84 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","horned","helmet" -> Type=3390, Amount=1, Price=155, "I buy this for %P gold. Is that ok?", Topic=6 + +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=4*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=80*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=110*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=580*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"studded","legs" -> Type=3362, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","scale","legs" -> Type=3363, Amount=%1, Price=180*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","legs" -> Type=3364, Amount=%1, Price=120*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","helmet" -> Type=3365, Amount=%1, Price=420*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"magic","plate","armor" -> Type=3366, Amount=%1, Price=720*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=12*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"winged","helmet" -> Type=3368, Amount=%1, Price=320*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=75*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=140*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"strange","helmet" -> Type=3373, Amount=%1, Price=55*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"legion","helmet" -> Type=3374, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"soldier","helmet" -> Type=3375, Amount=%1, Price=16*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"scale","armor" -> Type=3377, Amount=%1, Price=75*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=18*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"rose","armor" -> Type=3380, Amount=%1, Price=140*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","armor" -> Type=3381, Amount=%1, Price=210*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","legs" -> Type=3382, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","helmet" -> Type=3385, Amount=%1, Price=70*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","scale","mail" -> Type=3386, Amount=%1, Price=280*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"demon","helmet" -> Type=3387, Amount=%1, Price=95*%1 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"demon","armor" -> Type=3388, Amount=%1, Price=195*%1, "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"demon","legs" -> Type=3389, Amount=%1, Price=84*%1 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"horned","helmet" -> Type=3390, Amount=%1, Price=155*%1, "I buy this for %P gold. Is that ok?", Topic=6 + +# Ankauf von Schilden, Nummern 0-25 +"sell","steel","shield" -> Type=3409, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","plate","shield" -> Type=3410, Amount=1, Price=25 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","shield" -> Type=3411, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","battle","shield" -> Type=3413, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","mastermind","shield" -> Type=3414, Amount=1, Price=550, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","guardian","shield" -> Type=3415, Amount=1, Price=150, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=115, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","unholy","shield" -> Type=3417, Amount=1, Price=520, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","beholder","shield" -> Type=3418, Amount=1, Price=79 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","shield" -> Type=3419, Amount=1, Price=109, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","demon","shield" -> Type=3420, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","shield" -> Type=3421, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","great","shield" -> Type=3422, Amount=1, Price=480, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","blessed","shield" -> Type=3423, Amount=1, Price=650, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","ornamented","shield" -> Type=3424, Amount=1, Price=45 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=55 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","shield" -> Type=3426, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","rose","shield" -> Type=3427, Amount=1, Price=49 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","tower","shield" -> Type=3428, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","black","shield" -> Type=3429, Amount=1, Price=5 , "Bah. That's disgusting. But I take it for %P gold. Ok?", Topic=6 +"sell","copper","shield" -> Type=3430, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","viking","shield" -> Type=3431, Amount=1, Price=35 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","ancient","shield" -> Type=3432, Amount=1, Price=49 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","griffin","shield" -> Type=3433, Amount=1, Price=59 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","vampire","shield" -> Type=3434, Amount=1, Price=119, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=25*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"mastermind","shield" -> Type=3414, Amount=%1, Price=550*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"guardian","shield" -> Type=3415, Amount=%1, Price=150*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=115*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"unholy","shield" -> Type=3417, Amount=%1, Price=520*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=79*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","shield" -> Type=3419, Amount=%1, Price=109*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"demon","shield" -> Type=3420, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","shield" -> Type=3421, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"great","shield" -> Type=3422, Amount=%1, Price=480*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"blessed","shield" -> Type=3423, Amount=%1, Price=650*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"ornamented","shield" -> Type=3424, Amount=%1, Price=45*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=55*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"rose","shield" -> Type=3427, Amount=%1, Price=49*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"tower","shield" -> Type=3428, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"black","shield" -> Type=3429, Amount=%1, Price=5*%1 , "Bah. That's disgusting. But I take it for %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=35*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=49*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"griffin","shield" -> Type=3433, Amount=%1, Price=59*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"vampire","shield" -> Type=3434, Amount=%1, Price=119*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +#Ankauf +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you don't have one." +Topic=6,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=6 -> "Ok, then not." +} diff --git a/app/SabrehavenServer/data/npc/hofech.npc b/app/SabrehavenServer/data/npc/hofech.npc new file mode 100644 index 0000000..27cbdbb --- /dev/null +++ b/app/SabrehavenServer/data/npc/hofech.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hofech.npc: Datenbank für den Möbelhändler Hofech in Darashia + +Name = "Hofech" +Outfit = (128,95-2-2-57-0) +Home = [33270,32441,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Have a look at my wares, my friend." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, just a moment, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Hofech Ibn Kalith." +"job" -> "I am selling furniture and equipment to grace the homes." +"time" -> "It's %T my friend." +"caliph" -> "The caliph has the finest home and interior decoration in Darashia." +"kazzan" -> * +"daraman" -> "The prophet is alive! Both in the heavens and in our our hearts." +"ferumbras" -> "I don't know about such a person, my friend." +"excalibug" -> "I think if that sword really existed, it would make a splendid decoration for any wall." +"thais" -> "The wood we are using to make our exquisite furniture is partly supplied by Thais." +"tibia" -> "One day I will take my flying carpet to see the whole world." +"carlin" -> "I was not there yet. But I plan to travel there one day." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are extraordinarily cheap, given the fine quality of our stock. Just look at that table!" +"carpet" -> "No no, I don't sell any carpets." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/hoggle.npc b/app/SabrehavenServer/data/npc/hoggle.npc new file mode 100644 index 0000000..094a5c4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hoggle.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hoggle.npc: Datenbank fuer den Fischer Hoggle + +Name = "Hoggle" +Outfit = (128,20-46-88-94-0) +Home = [32537,32143,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my humble home!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I'm talking to someone?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am just a poor fisherman." +"name" -> "I am Hoggle. I live in this house." +"time" -> "No, this is not the time to go fishing." +"mountain" -> "Yes, there is a mountain to the north, but it's of no interest. There isn't any fish on it." +"food" -> "If you are hungry you can go downstairs, perhaps you will find some fish. You can also take some shoes if you want." +"map" -> "If you go north-west you will find Lubo and his adventurer shop. I think he sells maps." +"fisherman" -> "It's a very hard job, cause without a boat I have to swim and fish at the same time!" +"boat" -> "My boat sunk. I thought it would be more aerodynamic with holes in it." +"fish" -> "I think they can talk, but they are wise enough to be silent. Once I saw a mermaid." +"mermaid" -> "I saw one! She had the body of a fish, and also the head of a fish. Amazing!" +"stupid" -> "My mom always said, stupid is who stupid does." +"secret" -> "Can you keep a secret? I think fish can't breath on land!" +"thais" -> "I know this city. Sometimes I sell fish to Frodo." +"frodo" -> "He buys my fish." +"finger" -> "No, fish don't have fingers." +"pet","name" -> "Once there was a magician who named all his creatures like their species read backward." +"carlin" -> "There are stories about a city behind the mountain, but why should I go there? There is enough fish here." +} diff --git a/app/SabrehavenServer/data/npc/hugo.npc b/app/SabrehavenServer/data/npc/hugo.npc new file mode 100644 index 0000000..67d62b0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/hugo.npc @@ -0,0 +1,103 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hugo.npc: Datenbank für den Besitzer des Modehauses Hugo Chief + +Name = "Hugo" +Outfit = (130,14-81-80-93-0) +Home = [32951,32103,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Perhaps we can chat later, %N." +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"name" -> "I am known as Hugo Chief." +"hugo" -> "Well it's not my real name. I took it because people think it's scaring and manly. I hate people doubting my manhood for being a tailor, you know." +"real","name" -> "Uhm, well it's Oscar Savage, but who can become famous, especially as an artist, with a name like that I ask you?" +"job" -> "I am tailor and designer extraordinaire." +"warehouse" -> "I would call it a 'wearhouse'." + +"uniform",QuestValue(233)=1 -> "A new uniform for the post officers? I am sorry but my dog ate the last dress pattern we used. You need to supply us with a new dress pattern." +"dress","pattern",QuestValue(233)=1 -> "It was ... wonderous beyond wildest imaginations! I have no clue where Kevin Postner got it from. Better ask him.",SetQuestvalue(233,2) +"dress","pattern",QuestValue(233)>1,QuestValue(233)<9 -> "I already told you to ask your boss about that issue." +"dress","pattern",QuestValue(233)=9 -> "By the gods of fashion! Didn't it do that I fed the last dress pattern to my poor dog? Will this mocking of all which is taste and fashion never stop?? Ok, ok, you will get those ugly, stinking uniforms and now get lost, fashion terrorist.",SetQuestvalue(233,10), Idle + +"uniform" -> "I don't get it, what uniforms you are talking about." + +"time" -> "Sorry, a watch would ruin my stylish outfit." +"king" -> "He does not care much about us, we don't care much about him. I consider that a fair deal." +"tibianus" -> * +"army" -> "I think they should not wear that ugly armor in town. I will see to assure that will be changed soon." +"ferumbras" -> "The ferumbras-bad-ass-fashion is incredibly outdated since years." +"excalibug" -> "I don't care for such fairytales." +"thais" -> "Thais is kind of a fashion hell. If there was an award for the most ugly citizens, it would go to Thais." +"tibia" -> "A world filled with ugly dressed people needs the skills of a fashion-hero." +"uglyness" -> * +"punished" -> * +"fashion","hero" -> "One day Captain Catwalk will punish all crimes to fashion and bring all ugly people to justice." +"captain","catwalk" -> "He's a supermodel! No one knows his secret identity!" +"supermodel" -> "A model with incredible superpowers. Do you think they call them supermodels for nothing?" +"carlin" -> "Women should know better than to hide in ugly armor. Like all followers of ugliness they will be punished one day." +"news" -> "My newest models are top secret, sorry." +"tax" -> "I don't care about such mundane things like 'taxes'." +"privilege" -> "The city was granted a few privileges by the king. I can't even tell which. They don't affect me that much." +"gambling" -> "I too love to gamble now and then in the Hard Rock tavern." + +"addon",QuestValue(17563)=0 -> "I think I'm having an innovative vision! I feel that people are getting tired of attempting to look wealthy and of displaying their treasures. ...", + "A really new and innovative look would be - the 'poor man's look'! I can already see it in front of me... yes... a little ragged... but not too shabby! ...", + "I need material right now! Argh - the vision starts to fade... please hurry, can you bring me some stuff?", Topic=1 +"outfit",QuestValue(17563)=0 -> * +Topic=1,"yes" -> "Good! Listen, I need the following material - first, 20 pieces of brown cloth, like the worn and ragged ghoul clothing. ...", + "Secondly, 50 pieces of minotaur leather. Third, I need bat wings, maybe 10. And 30 heaven blossoms, the flowers elves cultivate. ...", + "Have you noted down everything and will help me gather the material?", Topic=2 +Topic=1 -> "Maybe another time." +Topic=2,"yes" -> "Terrific! What are you waiting for?! Start right away to gather 20 pieces of brown cloth and come back once you have them!", SetQuestValue(17563,1), SetQuestValue(17594,1) +Topic=2 -> "Maybe another time." + +"brown","cloth",QuestValue(17563)=1 -> Type=5913, Amount=20, "Ah! Have you brought 20 pieces of brown cloth?", Topic=3 +"mission",QuestValue(17563)=1 -> * +"task",QuestValue(17563)=1 -> * +Topic=3,"yes",Count(Type)>=Amount -> "Yes, yes, that's it! Very well, now I need 50 pieces of minotaur leather to continue.", Delete(Type), SetQuestValue(17563,2) +Topic=3,"yes" -> "You don't have that many." +Topic=3 -> "Maybe another time." + +"minotaur","leather",QuestValue(17563)=2 -> Type=5878, Amount=50, "Were you able to obtain 50 pieces of minotaur leather?", Topic=4 +"mission",QuestValue(17563)=2 -> * +"task",QuestValue(17563)=2 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Great! This leather will suffice. Now, please, the 10 bat wings.", Delete(Type), SetQuestValue(17563,3) +Topic=4,"yes" -> "You don't have that many." +Topic=4 -> "Maybe another time." + +"bat","wing",QuestValue(17563)=3 -> Type=5894, Amount=10, "Did you get me the 10 bat wings?", Topic=5 +"mission",QuestValue(17563)=3 -> * +"task",QuestValue(17563)=3 -> * +Topic=5,"yes",Count(Type)>=Amount -> "Hooray! These bat wings are ugly enough. Now the last thing: Please bring me 30 heaven blossoms to neutralise the ghoulish stench.", Delete(Type), SetQuestValue(17563,4) +Topic=5,"yes" -> "You don't have that many." +Topic=5 -> "Maybe another time." + +"heaven","blossom",QuestValue(17563)=4 -> Type=5921, Amount=30, "Is this the lovely smell of 30 heaven blossoms?", Topic=6 +"mission",QuestValue(17563)=4 -> * +"task",QuestValue(17563)=4 -> * +Topic=6,"yes",Count(Type)>=Amount -> "This is it! I will immediately start to work on this outfit. Come back in a day or something... then my new creation will be born!", Delete(Type), SetQuestValue(17563,5), SetExpiringQuestValue(17564, 86400000) +Topic=6,"yes" -> "You don't have that many." +Topic=6 -> "Maybe another time." + +"addon",ExpiringQuestValue(17564)>0 -> "Please be patient! I am still working on this outfit details. Come back later, okey?" +"outfit",ExpiringQuestValue(17564)>0 -> * + +"outfit",ExpiringQuestValue(17564)<0,QuestValue(17563)=5 -> "Eureka! Alas, the poor man's outfit is finished, but... to be honest... it turned out much less appealing than I expected. However, you can have it if you want, okay?", Topic=7 +"mission",ExpiringQuestValue(17564)<0,QuestValue(17563)=5 -> * +"task",ExpiringQuestValue(17564)<0,QuestValue(17563)=5 -> * +Topic=7,"yes" -> "Here you go. Maybe you enjoy it after all.", AddOutfit(157), AddOutfit(153), SetQuestValue(17563,6), EffectOpp(13) +Topic=7 -> "If you change your mind I will keep this outfit for you later." + +"mission",QuestValue(17563)>5 -> "I don't have any taks for you right now." +"task",QuestValue(17563)>5 -> * +} diff --git a/app/SabrehavenServer/data/npc/humgolf.npc b/app/SabrehavenServer/data/npc/humgolf.npc new file mode 100644 index 0000000..a4eb715 --- /dev/null +++ b/app/SabrehavenServer/data/npc/humgolf.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# humgolf.npc: Datenbank für den Rotwormzähmer Humgolf (Zwergenstadt) + +Name = "Humgolf" +Outfit = (69,0-0-0-0-0) +Home = [32598,31880,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "%N, good day .. or night, whatever." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N. We talk later if you insist.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye", Idle +"farewell" -> * +"job" -> "I am chief rotwormtamer of Kazordoon. I sell rotworms and buy meat and tasty, fresh rats for my worms." +"name" -> "I am Humgolf Molesight, Son of Earth, from the Molten Rock." +"tibia" -> "More nice beneath this noisy green surface." +"kazordoon" -> "I like the mines best." +"big","old" -> "The mountain seems tasty as far as my worms are concerned." +"worms" -> "They are so cute and so intelligent." +"humans" -> "They are not cute and not intelligent." +"orcs" -> "They are even more stupid and ugly than minotaurs." +"minotaurs" -> "They are stupid and ugly." +"elves" -> "They are not worth to be mentioned." +"geomancer" -> "They have an understanding of worms." +"god" -> "The worm does not care about gods, why should I?" +"earth" -> "Home of the worms, home of my people, too." +"fire" -> "Where earth is giving, fire is taking. That's the way of the elements." +"buy","rotworm" -> "Do you want to buy a rotworm?", topic=1 + +Topic=1,"yes" -> "Hey, you don't own a drilling licence. No deal!" +Topic=1 -> "You will regret that." + +"drilling","licence" -> "I am not allowed to sell worms to people without a formular 007 licence to drill or a 0815 artist licence." +"007" -> * +"0815" -> "It's a special licence for artists. It was only used once as I sold a white worm to Frietsiek and Yor." +"artist","licence" -> * + +"life" -> "Can't say I like it much." +"plant" -> "Only a rotting plant is a good plant." +"citizen" -> "Many noisy pepole down here are scaring my worms." +"kroox" -> "Poor guy, has lost his drilling licence for drinking." +"jimbin" -> "His tavern is too crowded and too bright for a dwarf with taste like me." +"maryza" -> "Don't like the way she looks at my worms." +"bezil" -> "Always chatting. How can someone talk that much?" +"nezil" -> * +"uzgod" -> "We are trading now and then. Fine dwarf he is." +"etzel" -> "Does a worm need spells to work his kind of magic? I do neither." +"motos" -> "That guy is a monster! I despise rotwormkillers." +"general" -> * +"durin" -> "If he'd live today he'd be a rotwormtamer like me." +"duria" -> "Thinks she's to good to talk to rotwormtamers." +"emperor" -> "The emperor should spend more money on rotworm husbandry." +"kruzak" -> * +"pyromancer" -> "Hotheads." +"technomancer" -> "GO AWAY! I heard they think of replacing worms with machines. That is an OUTRAGE!" +"army" -> "They should remember old dwarfish rotworm tactics. Think like a worm and the battle is almost won." +"colossus" -> "Never was up there to look at it." +"ferumbras" -> "A true enemy of the worms." +"excalibug" -> "Silly fairy tale." +"news" -> "Who needs news if the old things are still good enough?" +"monster" -> "Unwormish creatures they are." +"stone","golem" -> "Too hard to be gnawed away by even the finest worm." +"help" -> "I am here to help the worms, not the fools." +"quest" -> "What by the worm are you talking about?" +"task" -> * +"what","do" -> * +"gold" -> "Gold is one of the things my worms can unearth." +"money" -> * +"equipment" -> "If you own a good worm you need nothing else." +"time" -> "Time does not matter to a dwarf who understands the ways of the worm." + +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with that waterthing!" +"sell","rat" -> Type=3994, Amount=1, Price=2, "Do you have a fresh rat for sale?", Topic=2 + + +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A pieces of meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A pieces of ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "Do you have %A fresh rats for sale?", Topic=2 + + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"no" -> "Then not." +} diff --git a/app/SabrehavenServer/data/npc/humphrey.npc b/app/SabrehavenServer/data/npc/humphrey.npc new file mode 100644 index 0000000..83bb3ee --- /dev/null +++ b/app/SabrehavenServer/data/npc/humphrey.npc @@ -0,0 +1,73 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# humphrey.npc: Datenbank für den druiden humphrey + +Name = "Humphrey" +Outfit = (133,0-96-101-76-0) +Home = [32359,31683,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, child of nature." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am a guardian of nature. Also I am the keeper of the embrace of tibia, one of the five blessings." +"name" -> "My name is Humphrey." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking that bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you recrive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. They are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "You can ask for the blessing of spiritual shielding the whiteflower temple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarfen priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "I will give to you the embrace of tibia, but you will have to make a sacrifice. Are you prepared to pay 10.000 gold for the blessing?.",Price=10000, Topic=5 + +"fire" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(105) > 0 -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes" -> "So receive the embrace of tibia, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(105,1), Bless(2) +Topic=5 -> "Fine. You are free to decline my offer." + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." + +} + diff --git a/app/SabrehavenServer/data/npc/hyacinth.npc b/app/SabrehavenServer/data/npc/hyacinth.npc new file mode 100644 index 0000000..c00277e --- /dev/null +++ b/app/SabrehavenServer/data/npc/hyacinth.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hyacinth.npc: Datenbank fuer den Druiden Hyacinth + +Name = "Hyacinth" +Outfit = (130,11-123-123-94-0) +Home = [32137,32171,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May Crunor bless you." + +"bye" -> "May Crunor bless you.", Idle +"farewell" -> * +"how","are","you" -> "Thanks to the gods, I am fine." +"sell" -> "I just sell some revitalizing life fluids." +"job" -> "I am a druid and healer, a follower of Crunor." +"name" -> "I am Hyacinth." +"time" -> "Time does not matter to me." +"help" -> "I can only sell life fluids, ask Cipfried for further help." +"monster" -> "Most of the so called monsters of this isle are just creatures of the gods. On the mainland there are some beasts that truly are monstrous." +"dungeon" -> "The dungeons are dangerous for unexperienced adventurers." +"sewer" -> "I rarely visit the town." +"god" -> "As far as I know there is a library in the village. Teach yourself about the gods." +"king" -> "I don't care about kings, queens, and the like." +"obi" -> "A greedy and annoying person as most people are." +"seymour" -> "He has some inner devils that torture him." +"dallheim" -> "A man of the sword." +"cipfried" -> "His healing powers equal even mine." +"amber" -> "I never talked to her longer." +"weapon" -> "I don't care much about weapons." +"magic" -> "I am one of the few magic users on this isle. But I sense a follower of the dark path of magic hiding somewhere in the depths of the dungeons." +"spell" -> "I can't teach you magic. On the mainland you will learn your spells soon enough." +"tibia" -> "It is shaped by the will of the gods, so we don't have to question it." + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=1 +%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=1 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=2 +"vial" -> * +"flask" -> * +Topic=2,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=2,"yes" -> "You don't have any empty vials." +Topic=2 -> "Hmm, but please keep Tibia litter free." +} diff --git a/app/SabrehavenServer/data/npc/imalas.npc b/app/SabrehavenServer/data/npc/imalas.npc new file mode 100644 index 0000000..d97de92 --- /dev/null +++ b/app/SabrehavenServer/data/npc/imalas.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# imalas.npc: Datenbank für den Nahrungsmittelhändler Imalas + +Name = "Imalas" +Outfit = (128,115-10-39-114-0) +Home = [32334,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I'm a shopkeeper. You can buy food here." +"name" -> "My name is Imalas." +"time" -> "Sorry, I have no watch." +"ghostlands" -> "Sorry I know nothing more then it has to be a horrible place and that scares me enough." + +"offer" -> "Just have a look at my blackboard." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=2, "Do you want to buy a carrot for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you want to buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you want to buy a roll for %P gold?", Topic=1 +"brown","bread" -> Type=3602, Amount=1, Price=3, "Do you want to buy a brown bread for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 + +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=2*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you want to buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"brown","bread" -> Type=3602, Amount=%1, Price=3*%1, "Do you want to buy %A brown breads for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/imbul.npc b/app/SabrehavenServer/data/npc/imbul.npc new file mode 100644 index 0000000..9cab034 --- /dev/null +++ b/app/SabrehavenServer/data/npc/imbul.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Imbul.npc: Datenbank für den Fährman Imbul + +Name = "Imbul" +Outfit = (128,95-2-63-115-0) +Home = [32558,32781,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I'm a ferryman. If you want me to transport you to the other end of the city, feel free to ask me for a passage." +"name" -> "My name is Imbul." +"time" -> "Sorry, I don't own a watch." +"king" -> "It must be fun to be a king." +"venore" -> "Here are many people from Venore. I used to live there but I lost my job and took the chance to come here." +"thais" -> "It's where the king lives. The roads there must be made of gold or marble at least." +"carlin" -> "It's a city, I know that." +"edron" -> "Edron is some isle where the richest knights and sorcerers live. There they are not annoyed of the constant begging of poor people." +"jungle" -> "We already lost many settlers to the jungle. No one knows who is next." + +"tibia" -> "That is our world, yes." + +"kazordoon" -> "I think those dwarves came from Kazordoon." +"dwarves" -> "The dwarves that live here are searching for gold." +"dwarfs" -> * +"ab'dendriel" -> "What is that?" +"elves" -> "The elven queen is the most beautiful woman in Tibia." +"elfs" -> * +"darama" -> "Many came here to make their fortune. But it might take a while to become rich." +"darashia" -> "That's somewhere behind that mountain." +"ankrahmun" -> "I heard it's a ghost town or something like that." +"ferumbras" -> "Why have some magicians to become evil?" +"excalibug" -> "I never heard about that." +"apes" -> "They stole my paddle once." +"lizard" -> "If you follow the river far enough upcountry, you might see a lizardman. But be careful, they'll attack you as soon as they catch sight of you." +"dworcs" -> "They are all murderers and cannibals." + + +"trip" -> "I can bring you either to the east end of Port Hope or to the centre of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"east" -> Price=7, "Do you seek a passage to the east end of Port Hope for %P gold?", Topic=1 +"cent" -> Price=7, "Do you seek a passage to the centre of Port Hope for %P gold?", Topic=2 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + +#"trip" -> Price=7, "Would you like to travel to the other end of Port Hope or to the centre of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * + +#Topic=1,"end",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"centre",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"end",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +#Topic=1,"end",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"centre",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +#Topic=1,"centre",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "Did the dworcs have your brain for supper? I was asking you WHERE you want to travel!" + + +} diff --git a/app/SabrehavenServer/data/npc/irea.npc b/app/SabrehavenServer/data/npc/irea.npc new file mode 100644 index 0000000..d504f66 --- /dev/null +++ b/app/SabrehavenServer/data/npc/irea.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# irea.npc: Datenbank für die Bognerin Irea (Elfenstadt) + +Name = "Irea" +Outfit = (64,0-0-0-0-0) +Home = [32688,31610,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I sell bows, arrows, crossbows and bolts. I also teach some spells." +"name" -> "I am known as Irea." +"time" -> "That's unimportant." + +"carlin" -> "The druids of Carlin seek our guidance now and then." +"thais" -> "I don't understand what interest this humans form a far away land have in our town." +"venore" -> "Their traders are verry intrusive." +"roderick" -> "What is this humans use at all? I don't understabd it." +"olrik" -> "His trade seems to be the delivery of messages and items." + +"elves" -> "Humans or dwarfs will never understand us." +"dwarfs" -> "Bearded, heavy, and small." +"humans" -> "Humans have so little time to learn." +"troll" -> "I despise them." + +"cenath" -> "I often listen to their tales." +"kuridai" -> "They provide us with tools and metal." +"deraisim" -> "My people love the woods." +"abdaisim" -> "One day we will be reunited." +"teshial" -> "They have left so long ago." +"ferumbras" -> "Who is that?" +"crunor" -> "The master of nature. He nurtures us and is our benevolent protector." +"excalibug" -> "Our people have a bugfarm in the southeast of Ab'Dendriel." +"news" -> "My news are not for your ears." + +"magic" -> "I teach spells to create enchanted arrows." +"spell" -> "I teach 'Conjure Arrow', 'Poison Arrow', and 'Explosive Arrow'." + +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 + +"conjure","arrow" -> "I'm sorry, but this spell is only for paladins." +"poison","arrow" -> * +"explosive","arrow" -> * + +"bow" -> Type=3350, Amount=1, Price=350, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=450, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=350*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=450*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Hey, you do not have enough gold." +Topic=1 -> "Maybe we will make the deal another time." + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have the gold to pay me." +Topic=3,"yes" -> "You have learned it now.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." +} diff --git a/app/SabrehavenServer/data/npc/irmana.npc b/app/SabrehavenServer/data/npc/irmana.npc new file mode 100644 index 0000000..147aa11 --- /dev/null +++ b/app/SabrehavenServer/data/npc/irmana.npc @@ -0,0 +1,122 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Irmana" +Outfit = (140,78-90-13-15-3) +Home = [32952,32109,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the house of fashion, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"farewell" -> * +"fashion" -> "I'm currently interested in monster materials like cloth and leather. I can also fabricate cloth." +"fabricate" -> "I can fabricate green cloth from green tunics, red cloth from red robes and blue cloth from mystic turbans." + +"sell","ape","fur" -> Type=5883, Amount=1, Price=120, "Do you want to sell a ape fur for %P gold?", Topic=1 +"sell","blue","piece","of","cloth" -> Type=5912, Amount=1, Price=200, "Do you want to sell a blue piece of cloth for %P gold?", Topic=1 +"sell","brown","piece","of","cloth" -> Type=5913, Amount=1, Price=100, "Do you want to sell a brown piece of cloth for %P gold?", Topic=1 +"sell","green","dragon","leather" -> Type=5877, Amount=1, Price=100, "Do you want to sell a green dragon leather for %P gold?", Topic=1 +"sell","green","piece","of","cloth" -> Type=5910, Amount=1, Price=200, "Do you want to sell a green piece of cloth for %P gold?", Topic=1 +"sell","lizard","leather" -> Type=5876, Amount=1, Price=150, "Do you want to sell a lizard leather for %P gold?", Topic=1 +"sell","minotaur","leather" -> Type=5878, Amount=1, Price=80, "Do you want to sell a minotaur leather for %P gold?", Topic=1 +"sell","red","dragon","leather" -> Type=5948, Amount=1, Price=200, "Do you want to sell a red dragon leather for %P gold?", Topic=1 +"sell","red","piece","of","cloth" -> Type=5911, Amount=1, Price=300, "Do you want to sell a red piece of cloth for %P gold?", Topic=1 +"sell","simple","dress" -> Type=3568, Amount=1, Price=50, "Do you want to sell a simple dress for %P gold?", Topic=1 +"sell","spool","of","yarn" -> Type=5886, Amount=1, Price=1000, "Do you want to sell a spool of yarn for %P gold?", Topic=1 +"sell","white","piece","of","cloth" -> Type=5909, Amount=1, Price=100, "Do you want to sell a white piece of cloth for %P gold?", Topic=1 +"sell","yellow","piece","of","cloth" -> Type=5914, Amount=1, Price=150, "Do you want to sell a yellow piece of cloth for %P gold?", Topic=1 + +"sell",%1,1<%1,"ape","fur" -> Type=5883, Amount=%1, Price=120*%1, "Do you want to sell %A ape furs for %P gold?", Topic=1 +"sell",%1,1<%1,"blue","piece","of","cloth" -> Type=5912, Amount=%1, Price=200*%1, "Do you want to sell %A blue pieces of cloth for %P gold?", Topic=1 +"sell",%1,1<%1,"brown","piece","of","cloth" -> Type=5913, Amount=%1, Price=100*%1, "Do you want to sell %A brown pieces of cloth for %P gold?", Topic=1 +"sell",%1,1<%1,"green","dragon","leather" -> Type=5877, Amount=%1, Price=100*%1, "Do you want to sell %A green dragon leathers for %P gold?", Topic=1 +"sell",%1,1<%1,"green","piece","of","cloth" -> Type=5910, Amount=%1, Price=200*%1, "Do you want to sell %A green pieces of cloth for %P gold?", Topic=1 +"sell",%1,1<%1,"lizard","leather" -> Type=5876, Amount=%1, Price=150*%1, "Do you want to sell %A lizard leathers for %P gold?", Topic=1 +"sell",%1,1<%1,"minotaur","leather" -> Type=5878, Amount=%1, Price=80*%1, "Do you want to sell %A minotaur leathers for %P gold?", Topic=1 +"sell",%1,1<%1,"red","dragon","leather" -> Type=5948, Amount=%1, Price=200*%1, "Do you want to sell %A red dragon leathers for %P gold?", Topic=1 +"sell",%1,1<%1,"red","piece","of","cloth" -> Type=5911, Amount=%1, Price=300*%1, "Do you want to sell %A red pieces of cloth for %P gold?", Topic=1 +"sell",%1,1<%1,"simple","dress" -> Type=3568, Amount=%1, Price=50*%1, "Do you want to sell %A simple dress for %P gold?", Topic=1 +"sell",%1,1<%1,"spool","of","yarn" -> Type=5886, Amount=%1, Price=1000*%1, "Do you want to sell %A spool of yarns for %P gold?", Topic=1 +"sell",%1,1<%1,"white","piece","of","cloth" -> Type=5909, Amount=%1, Price=100*%1, "Do you want to sell %A white pieces of cloth for %P gold?", Topic=1 +"sell",%1,1<%1,"yellow","piece","of","cloth" -> Type=5914, Amount=%1, Price=150*%1, "Do you want to sell %A yellow pieces of cloth for %P gold?", Topic=1 + +Topic=1,"yes",Count(Type)>=Amount -> "Thank you. Here is your money.", Delete(Type), CreateMoney +Topic=1,"yes" -> "You do not have one." +Topic=1,"yes",Amount>1 -> "You do not have that many." +Topic=1 -> "Perhaps another time then." + +"red","robe" -> Type=3566, Amount=1, "Do you want to fabricate %A red robe for red piece of cloth?", Topic=12 +Topic=12,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5911, Amount=1, Create(Type) +Topic=12,"yes" -> "You do not have one." +Topic=12,"yes",Amount>1 -> "You do not have that many." +Topic=12 -> "Perhaps another time then." + +"mystic","turban" -> Type=3574, Amount=1, "Do you want to fabricate %A mystic turban for blue piece of cloth?", Topic=13 +Topic=13,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5912, Amount=1, Create(Type) +Topic=13,"yes" -> "You do not have one." +Topic=13,"yes",Amount>1 -> "You do not have that many." +Topic=13 -> "Perhaps another time then." + +"green","tunic" -> Type=3563, Amount=150, "Do you want to fabricate %A green tunics for green piece of cloth?", Topic=13 +Topic=13,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), Type=5910, Amount=1, Create(Type) +Topic=13,"yes" -> "You do not have one." +Topic=13,"yes",Amount>1 -> "You do not have that many." +Topic=13 -> "Perhaps another time then." + +"addon" -> "Currently we are offering accessories for the nobleman - and, of course, noblewoman - outfit. Would you like to hear more about our offer?", Topic=2 +Topic=2,"yes",male -> "Especially for you, my lord, we are offering a fashionable top hat and a fancy coat like the one Kalvin wears. Which one are you interested in?", Topic=3 +Topic=2,"yes",female -> "Especially for you, my lady, we are offering a pretty hat and a beautiful dress like the ones I wear. Which one are you interested in?", Topic=3 +Topic=2 -> "As you wish." +Topic=3,"hat",QuestValue(17551)=0 -> Amount=17552, "Great! Since our accessories are hand-tailored designer pieces, of course they are not made for citizens with an empty wallet. Should I inform you about our payment policy?", Topic=4 +Topic=3,"hat",QuestValue(17551)=1 -> Amount=17552, "Oh, I see you already have noble hat. Maybe take a look choosing another noble addon." +Topic=3,"dress",QuestValue(17553)=0,female -> Amount=17554, "Great! Since our accessories are hand-tailored designer pieces, of course they are not made for citizens with an empty wallet. Should I inform you about our payment policy?", Topic=4 +Topic=3,"dress",QuestValue(17553)=1,female -> Amount=17554, "Oh, I see you already have noble dress. Maybe take a look choosing another noble addon." +Topic=3,"coat",QuestValue(17553)=0,male -> Amount=17554, "Great! Since our accessories are hand-tailored designer pieces, of course they are not made for citizens with an empty wallet. Should I inform you about our payment policy?", Topic=4 +Topic=3,"coat",QuestValue(17553)=1,male -> Amount=17554, "Oh, I see you already have noble coat. Maybe take a look choosing another noble addon." +Topic=3 -> "Perhaps another time then." +Topic=4,"yes" -> "This accessory requires a small fee of 150000 gold pieces. Of course, we do not want to put you at any risk to be attacked while carrying this huge amount of money.", + "This is why we have established our brand-new installment sale. You can choose to either pay the price at once, or if you want to be safe, by installments of 10000 gold pieces.", + "I also have to inform you that once you started paying for one of the accessories, you have to finish the payment first before you can start paying for the other one, of course.", + "Are you interested in purchasing this accessory?", Topic=5 +Topic=4 -> "Perhaps another time then." +Topic=5,"yes" -> "I'm very pleased to hear that, %N! Which do you prefer - paying 150000 at once or 10000 for 15 times?", Topic=6 +Topic=5 -> "Perhaps another time then." +Topic=6,"once" -> "Good, I have noted down your order. Once you have the money, please come back to pick up your accessory.", SetQuestValue(Amount,1) +Topic=6,"150000" -> * +Topic=6,"15" -> "Good, I have noted down your order. Once you have the money, please come back to pick up your accessory.", SetQuestValue(Amount,2) +Topic=6,"10000" -> * +Topic=6 -> "Perhaps another time then." + +"addon",QuestValue(17552)=1, QuestValue(17551)=0 -> Price=150000, "Ah, are you here to pickup your accessory for 150000 gold pieces?", Topic=7 +"accessory",QuestValue(17552)=1, QuestValue(17551)=0 -> * +Topic=7,"yes",CountMoney>=Price -> "Congratulations! Here is your brand-new accessory, I hope you like it. Please visit us again!", DeleteMoney, SetQuestValue(17551,1), AddOutfitAddon(140,2), AddOutfitAddon(132,2), EffectOpp(13) +Topic=7,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=7 -> "Perhaps another time then." + +"addon",QuestValue(17552)>1, QuestValue(17551)=0 -> Price=10000, "Ah, are you here to pay your installment of 10000 gold pieces for your accessory?", Topic=8 +"accessory",QuestValue(17552)>1, QuestValue(17551)=0 -> * +Topic=8,"yes",CountMoney>=Price -> "Good, I have noted down your installment. Please come back once you have the money.", DeleteMoney, SetQuestValue(17552, QuestValue(17552)+1) +Topic=8,"yes",CountMoney>=Price,QuestValue(17552)>=16 -> "Congratulations! Here is your brand-new accessory, I hope you like it. Please visit us again!", DeleteMoney, SetQuestValue(17551,1), AddOutfitAddon(140,2), AddOutfitAddon(132,2), EffectOpp(13) +Topic=8,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=8 -> "Perhaps another time then." + +"addon",QuestValue(17554)=1, QuestValue(17553)=0 -> Price=150000, "Ah, are you here to pickup your accessory for 150000 gold pieces?", Topic=9 +"accessory",QuestValue(17554)=1, QuestValue(17553)=0 -> * +Topic=9,"yes",CountMoney>=Price -> "Congratulations! Here is your brand-new accessory, I hope you like it. Please visit us again!", DeleteMoney, SetQuestValue(17553,1), AddOutfitAddon(140,1), AddOutfitAddon(132,1), EffectOpp(13) +Topic=9,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=9 -> "Perhaps another time then." + +"addon",QuestValue(17554)>1, QuestValue(17553)=0 -> Price=10000, "Ah, are you here to pay your installment of 10000 gold pieces for your accessory?", Topic=10 +"accessory",QuestValue(17554)>1, QuestValue(17553)=0 -> * +Topic=10,"yes",CountMoney>=Price -> "Good, I have noted down your installment. Please come back once you have the money.", DeleteMoney, SetQuestValue(17554, QuestValue(17554)+1) +Topic=10,"yes",CountMoney>=Price,QuestValue(17554)>=16 -> "Congratulations! Here is your brand-new accessory, I hope you like it. Please visit us again!", DeleteMoney, SetQuestValue(17553,1), AddOutfitAddon(140,1), AddOutfitAddon(132,1), EffectOpp(13) +Topic=10,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=10 -> "Perhaps another time then." +} diff --git a/app/SabrehavenServer/data/npc/ironeye.npc b/app/SabrehavenServer/data/npc/ironeye.npc new file mode 100644 index 0000000..54d65a9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ironeye.npc @@ -0,0 +1,42 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ironeye.npc: Datenbank für den Waffen und Rüstungshandelsfürsten Abran Ironeye + +Name = "Abran Ironeye" +Outfit = (73,0-0-0-0-0) +Home = [32907,32116,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hail, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Stand still and wait!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! I did not dismiss you!" + +"bye" -> "You are dismissed.", Idle +"name" -> "I am Abran Ironeye." +"job" -> "I am a busy man. I run the Ironhouse." +"ironhouse" -> "What do you think? Here weapons and armor are forged, repaired, and sold." +"time" -> "You should know that on your own!" +"king" -> "I would like to see a true warrior-king in Thais... like in the old days. But who knows, perhaps one day the tides of fate will bring such a man to power. Who knows..." +"tibianus" -> * +"army" -> "Such a great tool wasted for garrison duties, a shame." +"ferumbras" -> "Quite a challenge, but his bets for power were made without the finesse of a true warrior." +"finesse" -> "I won't give away my tricks, learn your own." +"excalibug" -> "If someone would bring me that weapon I could reshape the realm... and reward this hero beyond his dreams." +"ironeye" -> "I don't care if you like it or not. Stop staring at it!" +"teddy" -> "I don't know anything about a teddy... and if you are smart you shouldn't either..." +"thais" -> "Thais has outlived its usefulness since years. Its star is sinking." +"tibia" -> "The world is ready for a significant change." +"warehouse" -> "My Ironhouse is more than a warehouse." +"carlin" -> "Their independence is a proof for the weakness of Thais." +"news" -> "In Venore nothing comes for free and you could not afford my 'news'." +"tax" -> "The taxing keeps the Thaian kingdom alive, but it also might break its neck one day." +"banor" -> "As a man that grows up needs no mommy, a warrior has to outgrow his need for gods." +"privilege" -> "Venore's privileges are hard earned." +"gambling" -> "I trust only in skill, not luck." +} diff --git a/app/SabrehavenServer/data/npc/irvin.npc b/app/SabrehavenServer/data/npc/irvin.npc new file mode 100644 index 0000000..49427df --- /dev/null +++ b/app/SabrehavenServer/data/npc/irvin.npc @@ -0,0 +1,23 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Irvin" +Outfit = (128,116-37-116-76-0) +Home = [32185,32736,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "A good day to you." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/ishebad.npc b/app/SabrehavenServer/data/npc/ishebad.npc new file mode 100644 index 0000000..14c1d6b --- /dev/null +++ b/app/SabrehavenServer/data/npc/ishebad.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ishebad.npc: Datenbank für den großwesir ankrahmuns + +Name = "Ishebad" +Outfit = (65,0-0-0-0-0) +Home = [33158,32848,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"hi",! -> * +ADDRESS,"salutations",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "This person will never achieve ascension that way!" + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"name" -> "I am Ishebad the chosen." +"time" -> "Time does not matter to the undead." + +"temple" -> "The temple will take care of your spiritual matters." +"pharaoh" -> "Our immortal ruler, may he be blessed, is the keeper of our enlightenment and our saviour." +"ashmunrah" -> "The fallen pharaoh did not see it was time to step back and let his son rule. So he met the fate that he deserved." +"scarab" -> "The scarabs are keepers of secrets. Some secrets are not ment for your mortals. Ever keep that in mind." + +"tibia" -> "This world just awaits the wisdom of our pharaoh. It needs that wisdom and will soon learn to appreciate it." +"carlin" -> "Other cities are of no importance. Ankrahmun will become the center of the known world anyways." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> * +"ab'dendriel" -> * +"darama" -> "The rule of our beloved pharaoh will soon spread this continent and one day the whole known world." +"darashia" -> "This village is so insignificant that our wise pharaoh has choosen to ignore it." +"daraman" -> "Some lunatic who was driven mad by the heat of the desert and dehydration." +"Ankrahmun" -> "Our city will become the capital of a worldwide empire." +"Arkhothep" -> "The pharaoh wants not to be disturbed. I am his grand vizier and responsible for the daily affairs of the city and promotions of heroes." +"pharaoh" -> * +"job" -> * +"mortality" -> "If you please our pharaoh, he will reward you and free you from your mortality." +"ascension" -> "Consult a priest to learn how you could achieve ascension." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * +"undead" -> "Undeath is only for the choosen." +"undeath" -> * +"mourn" -> "You mortals are all to be mourned for your miserable existance." + + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/app/SabrehavenServer/data/npc/ishina.npc b/app/SabrehavenServer/data/npc/ishina.npc new file mode 100644 index 0000000..a8d74aa --- /dev/null +++ b/app/SabrehavenServer/data/npc/ishina.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ishina.npc: Datenbank für die Juwelierin Ishina + +Name = "Ishina" +Outfit = (150,95-9-87-95-1) +Home = [33231,32423,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I will talk to you in a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings and good bye." + +"bye" -> "Daraman's blessings and good bye.", Idle +"job" -> "I am a jeweller. Maybe you want to have a look at my wonderful offers." +"name" -> "My name is Ishina." +"time" -> "Currently it is %T." +"caliph" -> "The caliph buys the most precious gems and jewellery for himself." +"kazzan" -> * +"daraman" -> "Oh, I am not an expert in mythology and philosophy. Better ask the enlightened Kasmir about this." +"kasmir" -> "You will find Kasmir in the Muhayin. He's a philosopher and teacher in the ways of Daraman." +"muhayin" -> "It's the sacred tower. A place of solitude and meditation." +"offer" -> "I can offer you various gems, pearls, or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"addon",male -> "My jewelled belt? Yes, that is a true masterpiece. However, this accessory is for lady only." +"outfit",male -> * + +"addon",QuestValue(17555)=0,female -> "My jewelled belt? Yes, that is a true masterpiece. Of course I could make one for you, but I have a small request. Would you fulfill a task for me?", Topic=3 +"outfit",QuestValue(17555)=0,female -> * +"mission",QuestValue(17555)=0,female -> * +"task",QuestValue(17555)=0,female -> * +Topic=3,"yes" -> "Listen, um... I was wanting a comb for a long time... not just any comb, but a mermaid's comb. ...", + "Do you think you could get one for me? I really would appreciate it.", Topic=4 +Topic=3 -> "Hmm, but next time." +Topic=4,"yes" -> "Brilliant! I will wait for you to return with a mermaid's comb then.", SetQuestValue(17555,1), SetQuestValue(17594,1) +Topic=4 -> "Hmm, but next time." + +"comb",QuestValue(17555)=1,female -> Type=5945, Amount=1, "Have you brought a mermaid's comb?", Topic=5 +"mission",QuestValue(17555)=1,female -> * +"task",QuestValue(17555)=1,female -> * +Topic=5,"yes",Count(Type)>=Amount -> "Yeah! That's it! I can't wait to comb my hair! Oh - but first, I'll fulfil my promise: Here is your jewelled belt! Thanks again!", Delete(Type), SetQuestValue(17555,2), AddOutfitAddon(146,1), AddOutfitAddon(150,1), EffectOpp(13) +Topic=5,"yes" -> "You don't have it." +Topic=5 -> "Hmm, but next time." + +"mission",QuestValue(17555)=2 -> "Sorry but I don't have any tasks for you." +"task",QuestValue(17555)=2 -> * +} diff --git a/app/SabrehavenServer/data/npc/isimov.npc b/app/SabrehavenServer/data/npc/isimov.npc new file mode 100644 index 0000000..cf55c11 --- /dev/null +++ b/app/SabrehavenServer/data/npc/isimov.npc @@ -0,0 +1,83 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# isimov.npc: Datenbank für den Zwergenmönch Isimov (Zwergenstadt) + +Name = "Isimov" +Outfit = (160,115-0-19-95-0) +Home = [32653,31925,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N and greetings my child!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please be patient, %N. I'll be with you in moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "No patience, no manners." + +"bye" -> "Take care, %N!", Idle +"farewell" -> * +"job" -> "I am the master of the hall of the ancients." +"name" -> "My name is Isimov Dustbearer, Son of Fire and Earth, from the Molten Rock." +"hall","ancients" -> "The burial chamber of our ancestors. All of the firstborn of our race are there .. all but Durin." +"tibia" -> "A world of dangers, a world of wonders." +"kazordoon" -> "The last haven for dwarfenkind. Our last hope for our survival." +"big","old" -> "This mountain is as old as the world. The first dwarfs were born here." +"elves" -> "Bah, squirrels, hedgehogs, rabbits, elves ... who cares." +"humans" -> "A young and greedy race, though more noble. They remind me of orcs sometimes." +"orcs" -> "The greenskins are after all of us. Beware! Beware!" +"minotaurs" -> "They leave us alone, we leave them alone, that's the way of our people." +"pyromancer" -> "They whorship the elemental forces of fire." +"geomancer" -> "They whorship the elemental forces of earth." +"technomancer" -> "Strange, are they still around? Well, give them one or two hundred years and they are gone again, jawoll." +"god" -> "The gods abandoned us, we abandoned the gods. That's it, no big deal I tell ya, jawoll." +"keeper" -> * +"shepherd" -> * +"fire" -> "It's warm, it's useful, we use it. We decide when or how, jawoll." +"earth" -> "Gives us food and shelter, quite useful isn't it?" +"durin" -> "Ah yes, the first born. He became a higher entity to protect us. His mortal remains were buried at a remote spot where only pilgrims disturb their peace now and then." +"life" -> "Life is easy to understand, you have birth, you have death. Simple stuff, even elves could grasp the concept." +"plant" -> "Don't know much about this stuff. Find them in a soup sometimes." +"citizen" -> "You can become citizen of Kazordoon by the power of our ancestors." +"kroox" -> "What a hasty fellow. Can't be healthy to live in such a hurry, jawoll." +"jimbin" -> "Isn't that the kid that took over the Jolly Axeman tavern? Far too young for such a job, but did anyone ask me? No!" +"maryza" -> "How could she marry this Jimbin? I mean, they are kids! Know nothing about life and stuff. Couldn't they wait at least hundred years or so?" +"bezil" -> "Bezil and Nezil are typical profiteers. Fine new breed of dwarfs we raised, pah." +"nezil" -> * +"uzgod" -> "Has hardly a beard and already forgotten the traditions of his ancestors. Modern techniques ... almost like one of these technomancers." +"etzel" -> "Etzel, good old Etzel. I was his tutor long ago. Now he's running a guild ... they grow so fast ... so fast, jawoll." +"gregor" -> "Never heared that word. What's a gregor?" +"duria" -> "Could become a great warrior one day. Still needs to learn so much." +"emperor" -> "The emperor resides far above us in the upper caves. Sometimes I wonder if it's good that the emperor is that much away from the temples." +"kruzak" -> * +"motos" -> "Must admit there were worse generals in the years before, jawoll." +"general" -> * +"army" -> " A bunch of kids playing war. May the elements help us." +"ferumbras" -> "I have seen many of his type coming and going. He will fall and anotherone will take his place." +"excalibug" -> "Ahhh! Whan I was a little dwarf I was on a quest to find it. I was almost literally digging up the ghostlands for it and now only one thing is sure: It must be elsewere, jawoll." +"news" -> "News? I heard there's a new human settlement in the south, called Thai...something." +"monster" -> "Only another nuisance." +"help" -> "Can't you kids do anything on your own?" +"quest" -> "You are too young for quests, jawoll." +"task" -> * +"what","do" -> * +"gold" -> "Greed for gold could blind your sight for the important." +"money" -> * +"equipment" -> "Go and buy some." +"fight" -> "The life ot a dwarf is an eternal struggle. It hardens us and makes us the fine race we are, jawoll." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(14) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "Let me see what I can do, kid.", HP=40, EffectOpp(13) +"heal$" -> "Stop the whining, kid, that are only some scratches. Dwarfenheart knows no pain." +"time" -> "I think it's the fourth age of the yellow flame, isn't it?" + +"stake",QuestValue(17576)=5,Count(5941)<=0 -> "I think you have forgotten to bring your stake, kid." +"stake",QuestValue(17576)=5 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Unclean spirits shall be repelled'. Now, bring your stake to Amanda in Edron for the next line of the prayer. I will inform her what to do.", SetQuestValue(17576,6) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, kid." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=6 -> "You should visit Amanda in Edron now, kid." +"stake",QuestValue(17576)>6 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That's a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." +} diff --git a/app/SabrehavenServer/data/npc/isolde.npc b/app/SabrehavenServer/data/npc/isolde.npc new file mode 100644 index 0000000..c4d5ddc --- /dev/null +++ b/app/SabrehavenServer/data/npc/isolde.npc @@ -0,0 +1,97 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Isolde" +Outfit = (139,3-14-52-128-1) +Home = [32412,32830,4] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, be greeted. Maybe you require my assistance as a paladin spell trainer." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"assistance" -> "I serve as officer and trainer for paladins in this fortress." +"pirates" -> "I can understand their desire for freedom. This, of course, does not justify robbery or murder in any way." +"time" -> "It is %T." +"quaras" -> "The quara pose an even greater danger than the pirates do. Whereas the pirates are out for plunder, the quara try to destroy us all." +"cult" -> "There is something evil brewing under the surface of Liberty Bay. The angry and the discontented are easily swayed by the promises of sinister seducers." +"voodoo" -> " I think that is dark magic and a mockery of the gods. Sadly the natives seem not to recognise what they are dealing with." +"thais" -> "Thais was so crowded I hardly found a space to breath. And the city was too busy to get my thoughts sorted. Here everything seems closer, more real. The sea, the people, enemies and friends." +"venore" -> "Venore always seemed to me like a lurking moloch. A greedy octopus whose arms are everywhere ... but of course that's only a foolish impression and I don't judge people based on such ponderings." +"liberty bay" -> "My duties hardly leave me time to visit the town. I don't like crowded streets and too many people anyway. I rather prefer a walk along the beach, to watch the sea from the cliffs or to listen to the music of wind and waves." +"djinn" -> "As a child I heard stories about these magical spirits. There are good djinns and evil djinns. It is rumoured that they live somewhere in the lands of the pyramids and deserts." +"king" -> "Although I admire our king, all the crowded festivities were not to my taste. I don't mind my noble heritage too much and so I gladly accepted my duties here." +"chondur" -> "His name was mentioned once or twice by whispering locals. I think he does not live in this town if he exists at all." +"ferumbras" -> "The evil he represents cannot be stopped easily. Perhaps you should talk to Malunga, the mage from Edron. As far as I know, the academy knows more about the connection of the dark ones to these isles." +"governor" -> "The governor is a very busy man. He leaves military issues in the competent hands of admiral Wyrmslicer." +"Wyrmslicer" -> "Admiral Wyrmslicer is a man of principles. He is rather a man of doings than of great words. That might put him in a disadvantage on battlegrounds that are parlours and not a field or the sea." +"eleonore" -> "The governor's daughter is rumoured to be a bit rebellious . Perhaps she is only missing the right man at her side." +"raymond striker" -> "That man leads some of the pirates. He is famous for his daring robberies and his escapes from justice. In the end though, his legacy will consist only of a few stories that are shared in a smoky tavern by some old sea dogs." +"sugar" -> "They say the sugar is the treasure of the isle." +"rum" -> "If people would be more disciplined in the use of rum, everything were only half as bad." +"charlotta" -> "This old woman gives me shivers. Another reason to avoid the town. She has some way to look at me ... as if she knew something about me that not even I am aware of ... but forgive me, that's just my imagination." +"Theodore Loveless" -> "I wish Tristan would be more careful about that man. Although only a tradesman, there is something dangerous about him. It does not seem wise to have him as an enemy." +"Tristan" -> "Tristan ... Tristan ... Oh how even his name lets my heart jump in joy. Forgive me, I really adore this young hero and our hearts will always be united in love." +"plantations" -> "The people work quite hard on the plantations in the West. I sometimes listen to the sad melodies the workers are singing." + +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Good bye.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/app/SabrehavenServer/data/npc/iwan.npc b/app/SabrehavenServer/data/npc/iwan.npc new file mode 100644 index 0000000..b66beb1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/iwan.npc @@ -0,0 +1,93 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# iwan.npc: Datenbank für den Edelsteinhändler Iwan + +Name = "Iwan" +Outfit = (128,0-112-88-113-0) +Home = [33217,31833,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am busy here, %N. Wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"name" -> "I'm Iwan." +"job" -> "I sell gems of all kind." +"time" -> "It should be %T right now." +"king" -> "I am sure even the king would appreciate my wares." +"tibianus" -> * +"army" -> "I know not much about the local army." +"ferumbras" -> "I hope the academy is safe from his assaults." +"excalibug" -> "I am sure you'd easily recognize it by the gems attached to it." +"thais" -> "We supply Thais with gems found on this isle." +"tibia" -> "I know only so little about our world. It's a pity." +"carlin" -> "I never visited that city." +"edron" -> "Our island is rich in precious stones." +"news" -> "I haven't been told anything of interest lately." +"rumors" -> * + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, small sapphires, small rubies, small emeralds, and small amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"tibianus","talon" -> "The Edron Academy is very interested in them, however the King doesn't want to invest that many money!" + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/iwar.npc b/app/SabrehavenServer/data/npc/iwar.npc new file mode 100644 index 0000000..b16235d --- /dev/null +++ b/app/SabrehavenServer/data/npc/iwar.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# iwar.npc: Möbelverkäufer Iwar in Kazordoon + +Name = "Iwar" +Outfit = (160,58-108-63-76-0) +Home = [32618,31917,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N! Welcome to Kazordoon Furniture Store." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, %N, me busy.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Guut bye." + +"bye" -> "Guut bye.", Idle +"farewell" -> * +"name" -> "Me is Iwar Woodpecker, son of Earth, from the Savage Axes. Me run this store." +"job" -> "You moving to new home? Me specialist for equipping it." +"time" -> "Time is %T. You needing clock for your house?" +"news" -> "You meaning my specials, eh?" + +"offer" -> "Me selling statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/jackfategoroma.npc b/app/SabrehavenServer/data/npc/jackfategoroma.npc new file mode 100644 index 0000000..122aad6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jackfategoroma.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Jack Fate" +Outfit = (129,19-69-107-50-0) +Home = [32161,32558,6] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(17501)<2,! -> "One moment please %N. I want to warn you about this trip.", Queue +BUSY,"bring","me","to","liberty","bay",Premium,QuestValue(17501)=2,! -> "Set the sails %N!", Queue, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(17501)<2,! -> "One moment please %N. I want to warn you about this trip.", Queue +ADDRESS,"bring","me","to","liberty","bay",Premium,QuestValue(17501)=2,! -> "Set the sails %N!", Queue, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Jack Fate from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." + +"trip",QuestValue(17501)=0 -> "I'd love to bring you back to Liberty Bay, but as you can see, my ship is ruined. I also hurt my leg and can barely move. Can you help me?", Topic=1 +"passage",QuestValue(17501)=0 -> * +"town",QuestValue(17501)=0 -> * +"destination",QuestValue(17501)=0 -> * +"sail",QuestValue(17501)=0 -> * +"go",QuestValue(17501)=0 -> * +"back",QuestValue(17501)=0 -> * +"liberty","bay",QuestValue(17501)=0 -> * + +"trip",QuestValue(17501)=1 -> Type=5901, Amount=30, "Have you brought 30 pieces of wood so that I can repair the ship?", Topic=3 +"passage",QuestValue(17501)=1 -> * +"town",QuestValue(17501)=1 -> * +"destination",QuestValue(17501)=1 -> * +"sail",QuestValue(17501)=1 -> * +"go",QuestValue(17501)=1 -> * +"back",QuestValue(17501)=1 -> * +"liberty","bay",QuestValue(17501)=1 -> * +"wood",QuestValue(17501)=1 -> * + +"trip",QuestValue(17501)>1 -> "Do you want to travel back to Liberty Bay?", Topic=4 +"passage",QuestValue(17501)>1 -> * +"town",QuestValue(17501)>1 -> * +"destination",QuestValue(17501)>1 -> * +"sail",QuestValue(17501)>1 -> * +"go",QuestValue(17501)>1 -> * +"back",QuestValue(17501)>1 -> * +"liberty","bay",QuestValue(17501)>1 -> * + +Topic=1,"yes" -> "Thank you. Luckily the damage my ship has taken looks more severe than it is, so I will only need a few wooden boards. ...", + "I saw some lousy trolls running away with some parts of the ship. It might be a good idea to follow them and check if they have some more wood. ...", + "We will need 30 pieces of wood, no more, no less. Did you understand everything?", Topic=2 +Topic=1 -> "Oh, well." +Topic=2,"yes" -> "Good! Please return once you have gathered 30 pieces of wood.", SetQuestValue(17501,1) +Topic=2 -> "Oh, well." + +Topic=3,"yes",Count(Type)>=Amount -> "Excellent! Now we can leave this godforsaken place. Thank you for your help. Should you ever want to return to this island, ask me for a passage to Goroma.", Delete(Type), SetQuestValue(17501,2), SetQuestValue(17593,2) +Topic=3,"yes" -> "You don't have that many." +Topic=3 -> "Oh, I'm still waiting then." + +Topic=4,"yes" -> "Set the sails!", Idle, EffectOpp(11), Teleport(32285,32892,6), EffectOpp(11) +Topic=4 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/jackfatelibertybay.npc b/app/SabrehavenServer/data/npc/jackfatelibertybay.npc new file mode 100644 index 0000000..7dc3607 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jackfatelibertybay.npc @@ -0,0 +1,103 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Jack Fate" +Outfit = (129,19-69-107-50-0) +Home = [32284,32891,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","thais",Premium,QuestValue(17501)<2,! -> "One moment please %N. I want to warn you about this trip.", Queue +BUSY,"bring","me","to","thais",Premium,QuestValue(17501)=2,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +BUSY,"bring","me","to","thais",Premium,QuestValue(17501)=2,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(17501)<2,! -> "One moment please %N. I want to warn you about this trip.", Queue +ADDRESS,"bring","me","to","thais",Premium,QuestValue(17501)=2,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +ADDRESS,"bring","me","to","thais",Premium,QuestValue(17501)=2,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) + +BUSY,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=190,! -> Price=190, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +BUSY,"bring","me","to","darashia",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=190,! -> Price=190, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,CountMoney>=200,! -> Price=200, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=160,! -> Price=160, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +BUSY,"bring","me","to","venore",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,QuestValue(250)>2,CountMoney>=170,! -> Price=170, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +ADDRESS,"bring","me","to","venore",Premium,CountMoney>=180,! -> Price=180, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) + +BUSY,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +BUSY,"bring","me","to","ankrahmun",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,QuestValue(250)>2,CountMoney>=80,! -> Price=80, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +ADDRESS,"bring","me","to","ankrahmun",Premium,CountMoney>=90,! -> Price=90, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) + +BUSY,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +BUSY,"bring","me","to","port","hope",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,QuestValue(250)>2,CountMoney>=40,! -> Price=40, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +ADDRESS,"bring","me","to","port","hope",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Jack Fate from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Darashia, Ankrahmun, Venore, Port Hope or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"thais" -> Price=180, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia" -> Price=200, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron" -> Price=170, "Do you seek a passage to Edron for %P gold?", Topic=3 +"liberty","bay" -> "This is Liberty Bay. Where do you want to go?" +"venore" -> Price=180, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun" -> Price=90, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope" -> Price=50, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + +"thais",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia",QuestValue(250)>2 -> Price=190, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Edron for %P gold?", Topic=3 +"venore",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope",QuestValue(250)>2 -> Price=40, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + +"goroma",QuestValue(17501)>1 -> Price=500, "Ugh. You really want to go back to Goroma? I'll surely have to repair my ship afterwards, so I will charge you for %P gold. Okay?", Topic=4 +"goroma",QuestValue(17501)>1,QuestValue(250)>2 -> Price=490, "Ugh. You really want to go back to Goroma? I'll surely have to repair my ship afterwards, so I will charge you for %P gold. Okay?", Topic=4 + +Topic=1,"yes",Premium,CountMoney>=Price,QuestValue(17501)<2 -> "I have to warn you - we might get into a tropical storm on that route. I'm not sure if my ship will withstand it. Do you really want to travel to Thais?", Topic=8 +Topic=8,"yes",Premium,CountMoney>=Price,Random(1,10)=1,Level>19 -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32161,32558,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32161,32558,6), EffectOpp(11) + +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/jakahr.npc b/app/SabrehavenServer/data/npc/jakahr.npc new file mode 100644 index 0000000..7d86b41 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jakahr.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jakahr.npc: Datenbank für den Postmann Jakahr + +Name = "Jakahr" +Outfit = (133,95-37-32-40-0) +Home = [33066,32876,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, mourned pilgrim. How may I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking to a customer, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was an honour to serve you, %N." + +"bye" -> "It was an honour to serve you.", Idle +"farewell" -> * + + +"kevin" -> "Even in our lands the name of the guildmaster is held in great respect." +"postner" -> * +"postmasters","guild" -> "The guild plays a vital role in the economy of the known world." +"join" -> "Please travel to our headquarters if you wish to join our guild." +"headquarters" -> "You can find them to the south of the dwarven city of Kazordoon." + +"job" -> "I am a member of the Postmasters Guild. If you have questions about the Royal Tibia Mail System or the depots, ask me." +"office" -> "I am always here in my office. You are welcome to visit me anytime." +"name" -> "My name is Jakahr." +"time" -> "The time is %T, pilgrim." +#"mail" -> "Our mail system is perfectly unique, and everybody is free to use it. Would you like to know more about it?", Topic=1 +"depot" -> "The depots are easy to use. Just open a locker to find your items there." + +"excalibug" -> "A weapon of legend. We rarely hear stories about it around here, however." +"news" -> "It does not befit a member of my position to spread rumours and stories, pilgrim." +"thais" -> "Thais is the capital of a kingdom on a far-off continent." +"carlin" -> "Carlin is a city far, far away from here. They say it is run by women and druids." + +@"gen-post.ndb" + +"darama" -> "On this continent, the only place of real importance is our city." +"darashia" -> "A minor settlement to the north." +"daraman" -> "As far as I can tell he was some philosopher." +"ankrahmun" -> "This city is a safe haven that protects its citizens from the dangers of the desert." +"city" -> * + +"pharaoh" -> "The pharaoh keeps this city safe. He is both our political and our spiritual leader." +"arkhothep" -> * + +"ascension" -> "Sorry, but you should discuss religous issues like these in the temple. I am not a priest, and there is little I can tell you about it." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * + +"arena" -> "Fights are frequently staged in the arena to entertain the people." +"palace" -> "You can't miss the palace. It is probably the biggest pyramid in the whole world." +"temple" -> "The temple is to the east of the city." + + +#"letter" -> Amount=1, Price=5, "Would you like to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Would you like to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System allows you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you do not have enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you do not have enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/james.npc b/app/SabrehavenServer/data/npc/james.npc new file mode 100644 index 0000000..4d4a74b --- /dev/null +++ b/app/SabrehavenServer/data/npc/james.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# james.npc: Datenbank für den Bauern James auf Edron + +Name = "James" +Outfit = (128,115-41-45-118-0) +Home = [33280,31771,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Talking right now, %N. Wait a second.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"how","are","you" -> "Fine, thanks." +"sell" -> "I sell some food." +"job" -> "I am a humble farmer." +"name" -> "James." +"time" -> "I am too poor to afford a watch." +"help" -> "Sorry, can't offer you any help." +"monster" -> "There are dangerous monsters and renegade knights in the northwest behind the river and the mountain." +"dungeon" -> "I stay away from dungeons as far as I can." +"god" -> "May Crunor bless our harvests." +"king" -> "I never saw him in person." +"daniel" -> "A brave warrior as far as a farmer like me can tell." +"avar$" -> "He scares me a little." +"academy" -> "The mages and druids have quite an appetite. They buy much from me and summon even more food." +"magic" -> "I am nothing but a humble farmer and know nothing about that." +"weapon" -> * +"spell" -> * +"tibia" -> "If I were you, I would stay here." +"thais" -> "I was born in Thais, but my family moved to Edron among the first settlers." +"edron" -> * + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 + + +"buy" -> "I can offer you bread, cheese, ham, meat, and apples." +"food" -> "Are you looking for food? I have bread, cheese, ham, meat, and apples." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/jason.npc b/app/SabrehavenServer/data/npc/jason.npc new file mode 100644 index 0000000..b544454 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jason.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Jason" +Outfit = (134,96-86-106-116-0) +Home = [32163,32941,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "Good bye.", Idle +"farewell" -> * + + +} diff --git a/app/SabrehavenServer/data/npc/jeanclaude.npc b/app/SabrehavenServer/data/npc/jeanclaude.npc new file mode 100644 index 0000000..e13842b --- /dev/null +++ b/app/SabrehavenServer/data/npc/jeanclaude.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jeanclaude.npc: Datenbank für eine Stadtwache in Venore + +Name = "Jean Claude" +Outfit = (131,113-113-113-115-0) +Home = [33006,32053,6] +Radius = 5 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/jefrey.npc b/app/SabrehavenServer/data/npc/jefrey.npc new file mode 100644 index 0000000..cc3ffb0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jefrey.npc @@ -0,0 +1,24 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Jefrey" +Outfit = (132,57-63-5-81-2) +Home = [32338,32834,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, what can I do for you? Welcome to the Tibian Bank." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "It's not your turn %N. Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "What else should I have expected other than that?" + +"bye" -> "Good bye, %N.", Idle +"farewell" -> * +"job" -> "I am running this Bank" +"time" -> "It is %T, precisely." +"king" -> "Hail to the king!" + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/jezzara.npc b/app/SabrehavenServer/data/npc/jezzara.npc new file mode 100644 index 0000000..34f144d --- /dev/null +++ b/app/SabrehavenServer/data/npc/jezzara.npc @@ -0,0 +1,108 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jezzara.npc: Datenbank für die pyramidenhändlerin Jezzara + +Name = "Jezzara" +Outfit = (138,3-43-91-97-0) +Home = [33126,32821,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell food of various kinds." +"name" -> "I am the mourned Jezzara." +"time" -> "You can buy watches here in the markethall." +"temple" -> "The temple can be found in the northeast of the city." +"pharaoh" -> "The pharaoh is our godking and the founder of our religion." +"oldpharaoh" -> "He was entombed in undead state. It is said that this will finally teach him to to strive for ascension." +"scarab" -> "I am not afraid of something that attacks only my physical form. But they stay away from the city anyway." +"chosen" -> "The chosen are those who are granted undeath after a life of service to the pharaoh." +"tibia" -> "The world can be a dangerous place for the whole of the Akh'rah Uthun." +"carlin" -> "The cities of the Tibian continent have little contact with us." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves are not really fond of the endless sands of the desert. I must say I can't blame them for it." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves don't like this land too much so we have little contact with them." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is very diverse. There are deserts and mountains as well as a large jungle region." +"darashia" -> "We are usually not allowed to travel that far without explicit permission, so we know this city mostly from the tales of travellers." +"daraman" -> "I know little of his teachings. The priests say his conclusions were inconsequent." +"ankrahmun" -> "This city is a safe haven that gives shelter from the dangers of the desert." + +"mortality" -> "The priests teach that mortality is a curse. I find it hard to understand but the priests will know best." +"false", "gods" -> "As far as I understand the gods worshipped by other nations are nothing but imposters." + +"ascension" -> "Ascension is difficult to achive. Too difficult to achieve as long as you are still alive." +"Akh'rah","Uthun" -> "Well its just the Akh, the Rah and the Unthun." +"Akh" -> "That is the body." + +"undead" -> "Those who follow the pharaoh might become undead one day." +"undeath" -> * +"Rah" -> "The Rah is the spiritual part of a being." +"uthun" -> "The Uthun is the sentient part of all living things." +"mourn" -> "The priests say we are to be mourned while we are still alive." + +"arena" -> "Sometimes spectacular battles are fought in the local arena." +"palace" -> "The palace is where the mighty pharaoh resides." + + +"buy" -> "I can offer you meat, ham, salmon, fish, fruits and vegetables." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, pumpkins and melons. What do you want?" +"vegetable" -> "I have carrots and tomatoes. What do you want?" + +"dragon","ham" -> Type=3583, Amount=1, Price=25, "Do you want to buy a dragon ham for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=10, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=15, "Do you want to buy a ham for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=7, "Do you want to buy a salmon for %P gold?", Topic=1 +"fish" -> Type=3578, Amount=1, Price=6, "Do you want to buy a fish for %P gold?", Topic=1 + + +%1,1<%1,"dragon","ham" -> Type=3583, Amount=%1, Price=25*%1, "Do you wanna buy %A dragon ham for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=10*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=15*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=7*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=6*%1, "Do you want to buy %A fish for %P gold?", Topic=1 +"fruit" -> "I have oranges, bananas, grapes, and melons. What do you want?" +"vegetable" -> "I have carrots and tomatoes. What do you want?" + +"orange" -> Type=3586, Amount=1, Price=9, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=5, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=8, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=13, "Do you want to buy a melon for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=8, "Do you want to buy a carrot for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=10, "Do you want to buy a tomato for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + + +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=9*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=5*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=8*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=13*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=8*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=10*%1, "Do you want to buy %A tomatoes for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/jimbin.npc b/app/SabrehavenServer/data/npc/jimbin.npc new file mode 100644 index 0000000..1693936 --- /dev/null +++ b/app/SabrehavenServer/data/npc/jimbin.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jimbin.npc: Datenbank für den Wirt Jimbin + +Name = "Jimbin" +Outfit = (160,97-69-58-76-0) +Home = [32636,31886,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","jimbin",! -> "Welcome to the Jolly Axeman, have a good time, %N!" +ADDRESS,"hi$","jimbin",! -> * +ADDRESS,"hello$",! -> "Talking to me, %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","jimbin",! -> "Gimme a minute, %N.", Queue +BUSY,"hi$","jimbin",! -> * +BUSY,"hello$",! -> "Talking to me, %N?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back if you enjoyed my tavern, if not ... well, get eaten by a dragon, jawoll." + +"bye" -> "Come back if you enjoyed my tavern, if not ... well, get eaten by a dragon, jawoll.", Idle +"farewell" -> * +"hello$","maryza",! -> * +"hi$","maryza",! -> * +"job" -> "I'm runing the Jolly Axeman together with my wife Maryza." +"tavern" -> * +"maryza" -> "She's a fine cook; likes it bloddy, though. Humans call her Bloody Mary, but don't mention that to her if you're smart." +"name" -> "I am Jimbin Luckythroat, son of Earth, from the Molten Rock." +"time" -> "It is about %T." +"king" -> "The king orders huge amounts of mushroombeer for festivities." +"army" -> "I supply the army with dwarfish beer to keep morals high." +"ferumbras" -> "Hah! He never dares to trespass our realm." +"general" -> "The general is a fine man. Can drink as much as he wants and still is sober." +"excalibug" -> "Actually I belive it's more than a taverntale." +"thais" -> "Bah! Humans, can't stand a drink, jawoll." +"tibia" -> "The Tibia our race was born into was even more fierce than the world you young ones know." +"carlin" -> "Silly town. Alcohol is forbidden there and elves visit this town quite often, what certainly suggests nothing good about a town, jawoll." +"news" -> "Oh well, many hidden places of ancient times appear seemingly out of nowhere in these times." +"rumour" -> * +"rumor" -> * +"book" -> "The cookbook? It belongs to maryza. I think she has a few copies for sale." +"cookbook" -> * + +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"buy" -> "I can offer you beer ... or water if you are sick." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Ask my wife Maryza for food." +} diff --git a/app/SabrehavenServer/data/npc/john.npc b/app/SabrehavenServer/data/npc/john.npc new file mode 100644 index 0000000..7154e96 --- /dev/null +++ b/app/SabrehavenServer/data/npc/john.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "John" +Outfit = (128,58-68-12-114-0) +Home = [32138,32904,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'll be with you in a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm so glad to have you here as my customer." +"sell" -> "Souvenirs! Shovels! Treasure maps! Noble watches! Tools! All here for great prices!" +"job" -> "I am a merchant. What can I do for you?" + +"wares" -> "Souvenirs! Shovels! Treasure maps! Noble watches! Tools! All here for great prices!" +"offer" -> * + +"backpack" -> Type=2872, Amount=1, Price=30, "Do you want to buy a camouflage backpack for %P gold?", Topic=1 +"bag" -> Type=2864, Amount=1, Price=10, "Do you want to buy a camouflage bag for %P gold?", Topic=1 +"fishing","rod" -> Type=3483, Amount=1, Price=300, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=100, "Do you want to buy a pick for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=100, "Do you want to buy a rope for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=100, "Do you want to buy a shovel for %P gold?", Topic=1 +"watch" -> Type=6091, Amount=1, Price=500, "Do you want to buy a very noble-looking watch. for %P gold?", Topic=1 +"treasure","map" -> Type=5090, Amount=1, Price=1000, "Do you want to buy a treasure map for %P gold?", Topic=1 +"worm" -> Type=3492, Amount=1, Price=2, "Do you want to buy a worm for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 + +%1,1<%1,"backpack" -> Type=2872, Amount=%1, Price=30*%1, "Do you want to buy %A camouflage backpacks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2864, Amount=%1, Price=10*%1, "Do you want to buy %A camouflage bags for %P gold?", Topic=1 +%1,1<%1,"fishing","rod" -> Type=3483, Amount=%1, Price=300*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=100*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=100*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=100*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=6091, Amount=%1, Price=500*%1, "Do you want to buy %A very noble-looking watches. for %P gold?", Topic=1 +%1,1<%1,"treasure","map" -> Type=5090, Amount=%1, Price=1000*%1, "Do you want to buy %A treasure maps for %P gold?", Topic=1 +%1,1<%1,"worm" -> Type=3492, Amount=%1, Price=2*%1, "Do you want to buy %A worms for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +} diff --git a/app/SabrehavenServer/data/npc/julian.npc b/app/SabrehavenServer/data/npc/julian.npc new file mode 100644 index 0000000..851d965 --- /dev/null +++ b/app/SabrehavenServer/data/npc/julian.npc @@ -0,0 +1,48 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# julian.npc: Datenbank für den Musikhändler Julian + +Name = "Julian" +Outfit = (128,55-30-23-115-0) +Home = [32883,32076,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N! May I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am busy right now, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I make instruments and sometimes I'm wandering through the lands of Tibia as a bard." +"name",male -> "My name is Julian, sire." +"name",female -> "My name is Julian, my lady." +"time" -> "Sorry, I don't know what time it is." +"music" -> "Music is the food of love." +"bard" -> "Bards from all over the world come here to buy their instruments." + +"offer" -> "Here you can buy lyres, lutes, drums, and simple fanfares. I also have a piano and a harp." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=1 +"drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=1 +"simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=1 + +%1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyres for %P gold?", Topic=1 +%1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lutes for %P gold?", Topic=1 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drums for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfares for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You need some more money." +Topic=1 -> "Oh well, next time perhaps." + +@"gen-t-furniture-instruments-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/kalvin.npc b/app/SabrehavenServer/data/npc/kalvin.npc new file mode 100644 index 0000000..7d87abf --- /dev/null +++ b/app/SabrehavenServer/data/npc/kalvin.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Kalvin" +Outfit = (132,115-117-120-125-3) +Home = [32943,32108,5] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N! If you need information about outfits, I'm your man." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I'm an apprentice of Hugo. My current task is to inform everyone about our summer collection." +"name" -> "My name is Kalvin." +"hugo" -> "I hope that one day I will have grand visions, too." +"outfit" -> "Ah, I can tell you all about the newest range of patterns! There are, all in all, four free outfits, seven premium outfits, and four quest outfits." +"free","outfit" -> "Well, we have the traditional knight outfit, the hunter outfit, the mage outfit and the citizen outfit." +"premium","outfit" -> "Premium outfits are only available to characters on a premium account. They include the nobleman outfit, the summoner outfit, the warrior outfit, the barbarian outfit, the wizard outfit, the oriental outfit and the druid outfit." +"quest","outfit" -> "Quest outfits require certain tasks to be fulfilled before the outfit becomes accessible. Those quests are only available for premium customers. ...", + "We currently have the pirate outfit, the assassin outfit, the beggar outfit and the shaman outfit." +"addons" -> "Addons are usually accessories. Each outfit has two addons which can be worn only together with this outfit. All addons have to be earned through quests." +"quest" -> "Many addon quests require that a number of ingredients are collected." +"Irmana" -> " Such a beautiful and charming lady." +"knight" -> "If you spend your time fighting monsters and need that little extra protection, this well-armored outfit is great for you. Addons are usually awarded by knight guilds." +"hunter" -> "A light and comfortable ranger outfit which every paladin simply loves to wear. If you are looking to earn addons for this outfit, a good place to start would be the paladin guild in Thais." +"mage" -> "Great for wise old male mages as well as for pretty enchantresses. If you are looking to earn addons, ladies should take a stroll in Port Hope, whereas elders should explore the Dark Cathedral." +"citizen" -> "Your basic starter outfit - simple, but practical. If you are looking to earn addons for this outfit, you should walk around Thais and look for citizens wearing a hat or a backpack." +"nobleman" -> "One of our finest pieces. If you have the money and wish to show how wealthy and noble you are, this is the outfit for you. We also create addons especially for you, right here." +"summoner" -> "Great for young mages and ladies who love witchcraft. If you are looking to earn addons, gentlemen should take a stroll in Port Hope, whereas ladies should explore the Dark Cathedral." +"warrior" -> "The lighter version of the knight outfit is great if you want to save stamina and maintain agility. Addons are usually awarded by knight guilds." +"barbarian" -> "This outfit stands for raw power, battle cries and lack of manners. I heard that there are a few barbarians living near Northport. They might just be able to provide fitting addons." +"wizard" -> "Are you feeling evil today? This outfit is fitting for mages pursuing the dark side of sorcery. Shunned by the magic academy of Edron, you might find wizards hiding near Thais." +"oriental" -> "This comfortable, yet fashionable outfit will create the romantic mood of Thousand and One Nights. A good place to look around for addons are naturally Darama and Ankrahmun." +"druid" -> "Nature-loving and peaceful druids might like this outfit, especially the beastlike addons. I heard there is a druid living near Ab'Dendriel, who might be able to provide fitting addons." +"pirate" -> "Yo ho ho and a bottle of rum! If you love the pirate way of life and sneer at landlubbers, this might be the right outfit for you. Pirates are said to roam the Shattered Isles..." +"assassin" -> "Mysterious and lethal, assassins are hard to find and almost impossible to befriend. I know of one assassin hiding away on the Daramian Plague Spire. If you are lucky, he won't kill you." +"beggar" -> "Ah, Hugo is working on designing this look. He might need some inspiration, so if you have some time to spare, don't hesitate to contribute some ideas to him." +"shaman" -> "If you have seen everything and worn every look, this outfit will put the icing on your cake. Maybe you can earn it by becoming the apprentice of a shaman... or voodoo priest." + +} diff --git a/app/SabrehavenServer/data/npc/karl.npc b/app/SabrehavenServer/data/npc/karl.npc new file mode 100644 index 0000000..da26958 --- /dev/null +++ b/app/SabrehavenServer/data/npc/karl.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# karl.npc: Datenbank fuer den Wirt der vebotenen Kneipe in Carlin + +Name = "Karl" +Outfit = (128,58-68-109-131-0) +Home = [32318,31797,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Pshhhht! Not that loud ... but welcome." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back, but don't tell others." + +"bye" -> "Please come back, but don't tell others.", Idle +"farewell" -> * +"job" -> "I am the responsible for our ... uhm ... resistance." +"saloon" -> * +"resistance" -> "We fight the opression of the males and male needs by the women. This is our secret headquarters." +"headquarters" -> "Well its more a hidden tavern, so to say." +"tavern" -> "Our offers are limited but here a man can buy what a man needs." +"name" -> "I won't tell you my name." +"karl" -> "Who told you that???" +"queen" -> "Well, shes not that bad ... but some of her laws are." +"eloise" -> * +"laws" -> "Those crazy women forbid us alcohol in the city! Imagine that!" +"needs" -> * +"opression" -> * +"alcohol" -> * +"army" -> "They are the tools of opression. Hunting down every alcohol smuggler they can get." +"smuggler" -> "We collected money and hired one of the best smuggler in the whole land. His name is Todd." +"Todd" -> "A true fighter for malehood. He will bring us all the hard stuff from Thais and even contact the king there to support us." +"king" -> "I'm sure if the king learns about our tragedy, he will support us with alcohol." +"hard", "stuff" -> "Todd took all the money we could gather to buy us the best stuff on the whole continent." +"hugo" -> "I think Todd mentioned a Hugo once." +"news" -> "Some travelers from Edron told about a great treasure guarded by cruel demons in the dungeons there." +"rumors" -> * +"whisper","beer",QuestValue(17521)=3 -> Type=6106, Amount=1, Price=80, "Do you want to buy a bottle of our finest whisper beer for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=20, "Do you want to buy a mug of beer for %P gold?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> "Here. Don't take it into the city though.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, come back with more gold." +Topic=1 -> "Maybe later." + +"buy" -> "I can offer you beer. For wine and realy hard stuff we have to wait for Todd." +"offers" -> * +"do","you","sell" -> * +"do","you","have" -> * + +} diff --git a/app/SabrehavenServer/data/npc/kasmir.npc b/app/SabrehavenServer/data/npc/kasmir.npc new file mode 100644 index 0000000..9425b80 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kasmir.npc @@ -0,0 +1,147 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Kasmir.npc: Datenbank für den Priester Kasmir + +Name = "Kasmir" +Outfit = (130,76-0-38-95-0) +Home = [33212,32452,1] +Radius = 3 + +Behaviour = { +ADDRESS,male,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,female,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,"hello$",! -> "May Daraman enlighten you %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "May Daraman fulfill you with patience, %N. Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Gone like a Djinn..." + +"bye" -> "Good bye, %N. May Daraman's all-seeing eye watch your travels!", Idle +"job" -> "I am a chosen of Daraman." +"news" -> "Don't look for news when you don't know the past." +"name" -> "Kasmir Ibn Darasir." +"tibia" -> "The world is only a portal the gods created to allow our ascension to heaven." +"ascension" -> "Daraman had a vision that all mortals are able to ascend to heaven, becoming celestial beings." +"heaven" -> * +"celestial" -> "By enhancing one's soul a mortal can ascend to heaven. If you are not prepared to ascend, you are bound to this world by reincarnation." +"reincarnation" -> "If your soul is not strong and purified, you will not ascend but return to life on death, even losing strength in the process." +"necromancer" -> "Undeath is even worse than reincarnation. Those souls are nothing but a rotting mockery of a soul on the path of ascension." +"daraman" -> "Daraman travelled the world and learned the secrets of the ancients. At last he learned the secret of ascension and founded his philosophy." +"soul" -> "The soul was made by the gods and therefore is divine. So by enhancing its divinity it can become more like the image of its creators." +"philosophy" -> "Daraman led his followers to this promised land to follow his teachings. It was named Darama after him later." +"darama" -> "This land is harsh and challenging. It's far away from temptations and delusions. Here Daraman's people can concentrate on themselves." +"how","are","you"-> "Thank you, I'm fine and my soul is strong." +"sell" -> "Go to the bazaar if you are interested in worldly wealth." +"sin$" -> "Do you whish to confess your sins?", Topic=3 +"sins$" -> * +"god$" -> "The gods are in heaven and far away. You are here. So concentrate on your soul and take care for it on your own." +"gods$" -> * +"life" -> "Life is divine though not without flaws." +"citizen" -> "The people are too concerned about the illusions of the moment and care less and less about Daraman's philosophy." +"caliph" -> "The caliph is heavily involved in the affairs in the world, but one has to make this sacrifice for the welfare of all." +"monster" -> "They misuse their god given souls for evil and must be destroyed." +"quest" -> "Your quest should be to prepare your soul for ascension." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "Fight is unavoidable sometimes. Make sure only your body is hurt but not your soul." +"slay" -> * + +"heal$",HP<40 -> "You are hurt, pilgrim. I will heal your wounds.", HP=40, EffectOpp(13) +"heal$",Poison>0 -> "You are poisoned, pilgrim. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",Burning>0 -> "You are burning, pilgrim. I will help you.", Burning(0,0), EffectOpp(15) +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"help$",HP<40 -> "You are hurt, pilgrim. I will heal your wounds.", HP=40, EffectOpp(13) +"help$",Poison>0 -> "You are poisoned, pilgrim. I will help you.", Poison(0,0), EffectOpp(14) +"help$",Burning>0 -> "You are burning, pilgrim. I will help you.", Burning(0,0), EffectOpp(15) +"help$" -> "You aren't looking so bad. Sorry, I need my powers for cases more severe than yours." + +"ferumbras" -> "His soul is corrupted beyond any hope for ascension." +"time" -> "Now, it is %T." +"excalibug" -> "Your greed for such items can easily corrupt your soul." +"fardos" -> "Fardos is the creator. It was his work that we possess divine souls." +"uman" -> "Uman is the positive aspect of magic. His powers flow through each Tibian, making us his children." +"suon" -> "Suon is the sun. He watches our ascension." +"crunor" -> "Crunor is the force of life and part of our all being." +"nornur" -> "Nornur is the mysterious god of fate. Daraman taught us that he is the judge who allows ascension." +"bastesh" -> "Bastesh is the goddess of the seas, and deep as the see is our soul, indeed." +"kirok" -> "Kirok is called the mad one. He gifted us with the creativity to achive our ascension." +"toth" -> "Toth is the final judge. The unworthy are condemened to reincarnation." +"banor" -> "Banor is the very proof that ascension is possible." +"tibiasula" -> "Though this entity is dead, Tibiasula's energy is present in all of us." +"tibia" -> "Tibia is the raw force of earth." +"sula" -> "Sula is the raw force of water." +"air" -> "Air is without true mind and meaning." +"fire" -> "Fire is without true mind and meaning." +"zathroth" -> "Zathroth is the corruptor of souls who does not want mortals to ascend and become more like him." +"fafnar" -> "Fafnar is a test for our endurance and dilligence." +"brog" -> "Brog's hot blood is in our veins, tempting us and distracting us from improvement of our souls." +"urgith" -> "The bonemaster is strong in the ruins of Drefia. There you can test the braveness of your soul ... or lose it to his minions." +"archdemons" -> "They are soulless and therefore without true power in the end." +"ruthless","seven" -> * + +Topic=1,"yes",CountMoney>=Price -> "May Daraman guide your quest for ascension.", DeleteMoney, EffectOpp(15) +Topic=1,"yes" -> "Don't be ashamed, but you lack the gold." +Topic=1,"no" -> "As you wish." + +Topic=3,"yes" -> "So what does trouble your soul, pilgrim?", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and try harder to improve soul." + +"marriage" -> "You want me to initiate a marriage ceremony?", Topic=5 +"ceremony" -> * +Topic=5,"yes" -> "In the Name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time. Marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence please! I hereby invoke the attention of the eternal powers looking over our souls and lives. May the gods bless us!", EffectMe(13), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,male,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further live as a married couple. Go now and celebrate your marriage!", EffectOpp(14), EffectMe(13), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", Idle + +"stake",QuestValue(17576)=7,Count(5941)<=0 -> "I think you have forgotten to bring your stake, pilgrim." +"stake",QuestValue(17576)=7 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Let there be honour and humility'. Now, bring your stake to Rahkem in Ankrahmun for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,8) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, pilgrim." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=8 -> "You should visit Rahkem in Ankrahmun now, pilgrim." +"stake",QuestValue(17576)>8 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That is a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." +} diff --git a/app/SabrehavenServer/data/npc/kawill.npc b/app/SabrehavenServer/data/npc/kawill.npc new file mode 100644 index 0000000..88b6dc7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kawill.npc @@ -0,0 +1,123 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kawill.npc: Datenbank für den Geomancer Kawill (Zwergenstadt) + +Name = "Kawill" +Outfit = (66,0-0-0-0-0) +Home = [32644,31969,12] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! May earth protect you!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. Let me help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. Let me help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are hurt, my child, let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Earth under your feet, pilgrim." + +"bye" -> "Earth under your feet, %N!", Idle +"farewell" -> * + +"fire",PvPEnforced,! -> "The lifeforce of this world is wanig. There are no more blessings avaliable on this world." +"suns",PvPEnforced,! -> * +"fire","suns" -> "You can get the blessing of the two suns in the suntower near Ab'Dendriel." + +"job" -> "I am the great geomancer of dwarvenkind." +"name" -> "I am Kawill Marbleeye, Son of Earth, from the Molten Rock." +"tibia" -> "Nice world in general. It's a shame there is so much water ruining the general impression." +"kazordoon" -> "By using the powers of fire and earth we forced the river that once wound its way through the big old one in other directions, and created our home." +"big","old" -> "The mountain we live in is called the big old one. It's the mountain of mountains, and it isand like a friend and protector to our race." +"elves" -> "Who cares for that silly people." +"humans" -> "We are allied with this young race, though they seldom have the wisdom to listen to us." +"orcs" -> "Stupid beasts. Their savagery is only rivalled by their smell." +"minotaurs" -> "They lost their way long ago. Now they are lost and doomed. It should be a warning to all of us." +"geomancer" -> "We investigate the will of the earth. It is our duty to make sure things to work in their natural way." +"god" -> "The gods are treacherous and vain. They want to use us like they did in the past. Only the elements can be trusted, because all they want is for nature to run its set course." +"earth" -> "The lifegiving earth protects us, feeds us and takes us home after death." +"fire" -> "Where earth is giving, fire is taking. That is the way of the elements." +"life" -> "Life is born by earth and fed by earth." +"plant" -> "Plants are minor messengers of earth. If you understand the soil you understand the plants." +"citizen" -> "Many people are living in the embracement of earth in Kazordoon." +"kroox" -> "He is a fine smith and his armour may save your neck one day." +"jimbin" -> "He is a jolly fellow and one of the oldest dwarves alive." +"maryza" -> "She is a fine cook, jawoll." +"bezil" -> "Bezil and Nezil have pawn and equpiment shop with an amazing stock." +"nezil" -> * +"uzgod" -> "Uzgod is a blacksmith and understands the ways of his element well." +"etzel" -> "I fear the sorcerers focus on the destructive forces of fire. They forget about the protection earth could provide." +"motos" -> "The scars in this dwarf's face tell the tale of many a great battle." +"durin" -> "The celestial paladin, the protector of our race. The only divine being we care for and the only one who still cares for dwarfs." +"duria" -> "The first knight of dwarvenkind is a fine woman." +"emperor" -> "The emperor has rarely visited the temple district in the last years. He should care more about spirituality then about politics. Jawoll." +"kruzak" -> * +"pyromancer" -> "They are the followers of the great flame." +"technomancer" -> "FOOLS! FOOLS! ALL OF THEM! MAY EARTH SWALLOW THEM ALL!" +"motos" -> "He finally made his peace with his own heart." +"general" -> * +"army" -> "Our fortresses are strong and easy to defend. Any aggressor will be smashed by our armies. Most intruders will not get manage to pass the colossus." +"colossus" -> "The big fortress that guards our realm is shaped like a dwarf." +"ferumbras" -> "The day will come when he finally bites the dust." +"excalibug" -> "Ah, a weapon to be feared by man, beast and god alike, jawoll. He who wields it will be both blessed and cursed at the same time." +"news" -> "There will be nothing new, but every pain and every pleasure will return to you, and all in the same order. The eternal hour glass of existence will be turned again and again, and you will be turned with it, little speck of dust." +"Nietzsche" -> "In his mind he might have been a giant, but in his heart he was a dwarf." +"monster" -> "May the earth swallow them all!" +"stone","golem" -> "These beings are filled with the power of earth. Therefore they, too, are sacred in a way." +"help" -> "I am a mere diviner of earth's will and not allowed to help you." +"quest" -> "There's nothing I need, better ask others." +"task" -> * +"what","do" -> * +"gold" -> "Gold is one of earth's treasures. To have gold is to be blessed by earth. To be rich is to be favoured by earth." +"money" -> * +"equipment" -> "You can buy equiment in Bezil's and Nezil's little shop." +"fight" -> "Never forget your defence when fighting." + +"heal$",Burning>0 -> "You are burning. Let me help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. Let me help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are hurt, my child. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you recive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "You can receive the spiritual shielding which in the whiteflower temple south of thais." +"shielding" -> * +"spark" -> "The spark of the phoenix is given by me and by the great pyromancer in the nearby firetemple. Do you wish to receive my part of the blessing of the phoenix?", topic=1 +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." +# "fire","suns" -> "You can get the blessing of the two suns in the suntower near Ab'Dendriel." +# "suns" -> * +# nach oben gestellt wg antwort auf fire +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=1,"yes", QuestValue(199) > 0,! -> "You already possess my blessing." +Topic=1,"yes", QuestValue(102) > 0,! -> "You already possess my blessing." +Topic=1,"yes" -> "So receive the blessing of the live-giving earth, pilgrim.", EffectOpp(13),SetQuestValue(199,1) +Topic=1 -> "Ok. If you don't want it ... ." + + +"time" -> "Time is not of importance." +} diff --git a/app/SabrehavenServer/data/npc/kazzan.npc b/app/SabrehavenServer/data/npc/kazzan.npc new file mode 100644 index 0000000..4db23f4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kazzan.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kazzan.npc: Datenbank für Kalif Kazzan + +Name = "Kazzan" +Outfit = (130,95-13-15-76-0) +Home = [33231,32389,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome in the lands of the children of the enlightened Daraman, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,"salutations$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will test your patience %N, since I am already talking!", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your soul flourish" + +"bye" -> "May your soul flourish.", Idle +"news" -> "I don't give much attention to rumours." +"king" -> "Tibianus is the shepherd of the lost souls of the so called Thaian empire." +"tibianus" -> * +"thais" -> * +"name" -> "I am Kazzan Ibn Gadral, caliph of Darama." +"job" -> "I am the caliph of the children of Daraman." +"caliph" -> "A caliph is a leader of his people, just like a king." +"how","are","you"-> "I am fine, thank you." +"army" -> "Our people are well prepared to fight for their land and their souls." +"guard" -> * +"enemies" -> "The necromancers of Drefia fell under the wrath of the djinns once. If they challenge us again they might lose more than a city." +"enemy" -> * +"drefia" -> "When the djinns destroyed the better part of the unholy town in their wrath, the brotherhood hid like worms in the sand." +"brotherhood" -> "The Brotherhood of Bones came here fleeing some war on the continent. They corrupted the settlers of the Thaian colony with ease." +"minotaur" -> "The minotaurs are another test we have to endure. They inhabit the pyramid which is taboo for our people, as Daraman taught us." +"daraman" -> "Daraman led our ancestors to the continent Darashia to live a life of simplicity and meditation." +"taboo" -> "Daraman knew our souls might get corupted by the things hidden there." +"quest" -> "I will not entrust foreigners with any quest. Live amongst us for some years and listen to Daraman's teachings and we will see." +"mission" -> * +"god" -> "The gods are powerful but it's ultimately up to us to work on our souls' ascension." +"excalibug" -> "Once a djinn claimed to have seen it in a dream. I guess it's just that, some dream of a supernatural creature." +"kazordoon" -> "We have lost contact with the dwarf people of Kazordoon." +"dwarf" -> * +"carlin" -> "We keep in touch with the queen but did not take sides in the conflict of Carlin and Thais ... yet." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/app/SabrehavenServer/data/npc/kevin.npc b/app/SabrehavenServer/data/npc/kevin.npc new file mode 100644 index 0000000..63cc723 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kevin.npc @@ -0,0 +1,165 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kevin.npc: Datenbank für Kevin Postner den Anführer der Postlergilde + +Name = "Kevin" +Outfit = (128,76-43-38-76-0) +Home = [32569,32018,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, what brings you here?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +# TOPICS BIS EINSCHL. 25 BENUTZT + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"name" -> "My name is Kevin. Kevin Postner, that is." +"job" -> "I am the head of the Tibian Postmaster's Guild." +"guild" -> "We are a powerful organisation that is vital part of Tibian society. It's a honour and a privilege to be a member." +"postmaster" -> * +"academy" -> "Ah, yes! One day I will found a postofficers academy. Perhaps with the help of our most able members. But rhats a task for another day." +"wally" -> "Wally is my right hand, so to say. He is the head to our guilds office branch." +"branch" -> "Well, thers the depot branch, the office brance, the delivery branch and some officers for special operations." +"special","operations",QuestValue(250)<4 -> "Sorry but I won't talk about this matter with someone of your rank." +"special","operations",QuestValue(250)>3 -> "We have a secret branch, called 'the stamps of the gods'. They secretly supervise our members reliability and have an eye on certain groups of interest that want to gain influence over our guild." + +"member",QuestValue(227)<1 -> "We have high standards for our members. To rise in our guild is a difficult but rewarding task. Why do you ask? Are you interested in joining?",Topic=1 +"member",QuestValue(227)>0 -> "You are already a member, %N." +"mission",QuestValue(227)<1 -> "You are not a member of our guild yet! We have high standards for our members. To rise in our guild is a difficult but rewarding task. Are you interested in joining?",Topic=1 + +"advancement",QuestValue(250) "You are worthy indeed. Do you want to advance in our guild?",Topic=23 +"advancement",QuestValue(250)>=QuestValue(249) -> "Sorry, but you are not yet ready for advancement." +"yes",Topic=23,QuestValue(249)=2 -> "I grant you the title of postman. You are now a full member of our guild. Here have your own officers hat and wear it with pride.",SetQuestValue(250,2), Amount=1, Create(3576) +"yes",Topic=23,QuestValue(249)=3 -> "From now on it shall be known that you are a grand postman. You are now a privileged member until the end of days. Most captains around the world have an agreement with our guild to transport our privileged members, like you, for less gold.",SetQuestValue(250,3) +"yes",Topic=23,QuestValue(249)=4 -> "From now on you are a grand postman for special operations. You are an honoured member of our guild and earned the privilege of your own post horn. Here, take it.",SetQuestValue(250,4), Amount=1, Create(2957) +"yes",Topic=23,QuestValue(249)=5 -> "I grant you the title of archpostman. You are a legend in our guild. As privilege of your newly aquired status you are allowed to make use of certain mailboxes in dangerous areas. Just look out for them and you'll see.",SetQuestValue(250,5) + +"mission",QuestValue(248)=1,QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." + +"mission",QuestValue(248)=1 -> "You have mastered all our current missions. There's nothing left to be done ... for now." + +"mission",QuestValue(245)=1 -> "You are not done with your current mission. Deliver that letter to king Markwin. Please report back when you are ready." +"mission",QuestValue(245)=2 -> "You have delivered that letter? You are a true postofficer. All over the land bards shall praise your name. There are no missions for you left right now.",SetQuestValue(248,1),SetQuestValue(249,5),SetQuestValue(245,3) + +"mission",QuestValue(244)=1 -> "You are not done with your current mission. Deliver those letters to Santa. Please report back when you are ready." +"mission",QuestValue(244)=2 -> "You did it? I hope you did not catch a flu in the cold! However theres another mission for you. Are you interested?",topic=21 +Topic=21,"yes" -> "Excellent. Here is a letter for you to deliver. Well, to be honest, no one else volunteered. It's a letter from the mother of Markwin, the king of Mintwallin. Deliver that letter to him, but note that you will not be welcome there.",SetQuestValue(245,1),SetQuestValue(244,3), Amount=1, Create(3220) +Topic=21 -> "Too bad, perhaps another time then." + +"mission",QuestValue(246)=1,QuestValue(244)=0 -> "So are you ready for another Mission?", Topic=20 + +"mission",QuestValue(243)=0,QuestValue(242)=1 -> "You are not done with your current mission. Search for the whereabout of Postofficer Waldo. Please report back when you are ready." +"mission",QuestValue(243)=1 -> "So Waldo is dead? This is grave news indeed. Did you recover his posthorn?",topic=19,Type=3219, Amount=1 +Topic=19,"yes",Count(Type)>=Amount -> "Thank you. We will honour this. Your next mission will be a very special one. Good thing you are a special person as well. Are you ready?",Delete(Type),SetQuestValue(246,1),SetQuestValue(249,4),SetQuestValue(242,2),Topic=20 +Topic=19,"yes" -> "Hm, no, you don't have it. Too bad, go and look for it." +Topic=19 -> "Too bad, go and look for it." + +Topic=20,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." +Topic=20,"yes" -> "So listen well. Behind the lower left door you will find a bag. The letters in the bag are for none other than Santa Claus! Deliver them to his house on the isle of Vega, USE the bag on his mailbox and report back here.",SetQuestValue(244,1) +Topic=20 -> "Too bad, perhaps another time then." + +"mission",QuestValue(234)>0,QuestValue(234)<7 -> "You are not done with your current mission. We still need the measurements of several postofficers. Please report back when you are ready." +"mission",QuestValue(234)=7 -> "Once more you have impressed me! Are you willing to do another job?",topic=17 +Topic=17,"yes" -> "Ok but your next assignment might be dangerous. Our Courier Waldo has been missing for a while. I must assume he is dead. Can you follow me so far?", topic=18 +Topic=17 -> "Too bad, perhaps another time then." + +Topic=18,"yes" -> "Find out about his whereabouts and retrieve him or at least his posthorn. He was looking for a new underground passage that is rumoured to be found underneath the troll-infested Mountain east of Thais.",SetQuestValue(242,1),SetQuestValue(234,8) +Topic=18 -> "Too bad, perhaps you will try some other time then." + +"mission",QuestValue(233)>0,QuestValue(233)<10 -> "You are not done with your current mission. Make sure Hugo chief is tailoring our new uniforms. Please report back when you are ready." +"mission",QuestValue(233)=10 -> "Excellent! Another job well done! Would you accept another mission?",SetQuestValue(249,3),topic=16 + +Topic=16,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first let's talk about advancement." +Topic=16,"yes" -> "Good, so listen. Hugo Chief informed me that he needs the measurements of our postofficers. Go and bring me the measurements of Ben, Lokur, Dove, Liane, Chrystal and Olrik.",SetQuestValue(234,1),SetQuestValue(233,11) +Topic=16 -> "Too bad, perhaps another time then." + +"dress","pattern",QuestValue(233)=8 -> "Fine, fine. I think that should do it. Tell Hugo that we order those uniforms. The completed dress pattern will soon arrive in Venore. Report to me when you have talked to him.",SetQuestValue(233,9) + +"dress","pattern",QuestValue(233)=6 -> "The queen has sent me the samples we needed. The next part is tricky. We need the uniforms to emanate some odor that dogs hate.The dog with the best 'taste' in that field is Noodles, the dog of King Tibianus. Do you understand so far?",Topic=15,Amount=Random(1,3) +"uniform",QuestValue(232)=6 -> * +Topic=15,"yes" -> "Good. Go there and find out what taste he dislikes most: moldy cheese, a piece of fur or a bananaskin. Tell him to SNIFF, then the object. Show him the object and ask 'Do you like that?'. DONT let the guards know what you are doing.",SetQuestValue(233,7),SetQuestValue(251,Amount) +Topic=15 -> "Too bad, perhaps you can try doing it some other time then." + +"dress","pattern",QuestValue(233)=2 -> "Oh yes, where did we get that from ...? Let's see, first ask the great technomancer in Kazordoon for the technical details. Return here afterwards.",SetQuestvalue(233,3) + +"dress","pattern",QuestValue(233)=4 -> "The mail with Talphion's instructions just arrived. I remember we asked Queen Eloise of Carlin for the perfect colours. Go there, ask her about the UNIFORMS and report back here.",SetQuestvalue(233,5) + +"mission",QuestValue(231)=1 -> "You are not done with your current mission. Deliver that present to Fibula. Please report back when you are ready." +"mission",QuestValue(231)=2 -> "Splendid, I knew we could trust you. I would like to ask for your help in another matter. Are you interested?",topic=14 +Topic=14,"yes" -> "Ok. We need a new set of uniforms, and only the best will do for us. Please travel to Venore and negotiate with Hugo Chief a contract for new uniforms.",SetQuestValue(233,1),SetQuestValue(231,3) +Topic=14 -> "Too bad, perhaps another time then." + +# BEFÖRDERUNG 2 +Topic=13,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." +Topic=13,"yes" -> "Since I am convinced I can trust you, this time you must deliver a valuable present to Dermot on Fibula. Do NOT open it!!! You will find the present behind the door here on the lower right side of this room.",SetQuestValue(231,1) + +Topic=13 -> "Too bad, perhaps another time then." + +"mission",QuestValue(230)=1 -> Type=3115, Amount=1,"Do you bring ONE bone for our officers' safety fund or ALL bones at once?",topic=24 +"mission",QuestValue(230)>1,QuestValue(230)<20 -> Type=3115, Amount=1,"Do you bring a bone for our officers' safety fund?",topic=12 +"mission",QuestValue(230)>1,QuestValue(230)=20 -> Type=3115, Amount=1,"Do you bring a bone for our officers' safety fund?",topic=22 +"mission",QuestValue(230)>20 -> "You have made it! We have enough bones for the fund! You remind me of myself when I was young! Interested in another mission?",Topic=13 + +Topic=12,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "Excellent! You have collected %A bones. Just report about your mission again if you find more." +Topic=12 -> "You have no suitable bone with you. Too bad, but you surely will find some more." +Topic=22,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "You have collected all the %A bones needed. Excellent! Now let's talk about further missions if you are interested.",SetQuestValue(249,2) +Topic=22 -> "You have no suitable bone with you. Too bad, but you surely will find some more." + +Topic=24,"one",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "Excellent! You have collected %A bones. Just report about your mission again if you find more." +Topic=24,"all" -> Type=3115, Amount=20,"Are you sure you have collected all the 20 bones needed?",topic=25 + +Topic=25,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,21),"You have collected all the 20 bones needed. Excellent! Now let's talk about further missions if you are interested.",SetQuestValue(249,2) +Topic=25 -> "You have not enough bones with you. Too bad, but you surely will find some more." + +"mission",QuestValue(229)>0,QuestValue(229)<3 -> "You are not done with your current mission. Find David Brassacres and hand him that bill. Report when you are ready." +"mission",QuestValue(229)=3 -> "You truly got him? Quite impressive. You are a very prommising candidate! I think I have another mission for you. Are you interested?",Topic=11 + +"mission",QuestValue(228)=1 -> "You are not done with your current mission. The mailbox is still not fixed. Report if you are ready." +"mission",QuestValue(228)=2 -> "Excellent, you got it fixed! This will teach this mailbox a lesson indeed! Are you interested in another assignment?",Topic=9 +"mission",QuestValue(228)=3 -> "Are you interested in another assignment?",Topic=10 + +"mission",QuestValue(227)<5 -> "You are not done with your current mission. Make sure all the passages are secure. Report if you are ready." +"mission",QuestValue(227)=5 -> "So you have finally made it! I did not think that you would have it in you ... However: are you ready for another assignment?",Topic=8 + +Topic=11,"yes" -> "Ok, listen: we have some serious trouble with agressive dogs lately. We have accumulated some bones as a sort of pacifier but we need more. Collect 20 Bones like the one in my room to the left and report here.",SetQuestValue(230,1),SetQuestValue(229,4) +Topic=11 -> "Too bad, perhaps another time then." + +Topic=9,"yes" -> "For your noble deeds I grant you the title Assistant Postofficer. All Postofficers will charge you less money from now on. After every second mission ask me for an ADVANCEMENT. Your next task will be a bit more challenging. Do you feel ready for it?",SetQuestValue(250,1),SetQuestValue(249,1),SetQuestValue(228,3),Topic=10 +Topic=9 -> "Too bad, perhaps another time then." + +Topic=10,"yes" -> "I need you to deliver a bill to the stage magician David Brassacres. He's hiding from his creditors somewhere in Venore. It's likely you will have to trick him somehow to reveal his identity. Report back when you delivered this bill.",Amount=1,Create(3216),SetQuestValue(229,1) +Topic=10 -> "Too bad, perhaps another time then." + +# Topic=8,"yes" -> "I am glad to hear that. One of our mailboxes was reported to be jammed. It is located on the so called 'mountain' on the isle Folda. Get a crowbar and fix the mailbox. Report about your mission when you have done so.",Topic=9,SetQuestValue(228,1) +Topic=8,"yes" -> "I am glad to hear that. One of our mailboxes was reported to be jammed. It is located on the so called 'mountain' on the isle Folda. Get a crowbar and fix the mailbox. Report about your mission when you have done so.",SetQuestValue(228,1), SetQuestValue(227,6) +Topic=8 -> "I thought so. The mail service is not for just anyone." + +Topic=1,"yes" -> "Hm, I might consider your proposal, but first you will have to prove your worth by doing some tasks for us. Are you willing to do that?",Topic=2 +Topic=1 -> "I thought so. The mail service is not for just anyone." + +Topic=2,"yes" -> "Excellent! Your first task will be quite simple. But you should better write my instructions down anyways. You can read and write?",Topic=3 +Topic=2 -> "I thought so. The mail service is not for just anyone." + +Topic=3,"yes" -> "So listen, you will check certain tours our members have to take to see if there is some trouble. First travel with Captain Bluebear's ship from Thais to Carlin, understood?",Topic=4 +Topic=3 -> "I am sorry, but being illiterate disqualifies you from joining." + +Topic=4,"yes" -> "Excellent! Once you have done that you will travel with Uzon to Edron. You will find him in the Femor Hills. Understood?",Topic=5 +Topic=4 -> "I thought so. The mail service is not for just anyone." + +Topic=5,"yes" -> "Fine, fine! Next, travel with Captain Seahorse to the city of Venore. Understood?",Topic=6 +Topic=5 -> "I thought so. The mail service is not for just anyone." + +Topic=6,"yes" -> "Good! Finally, find the technomancer Brodrosch and travel with him to the Isle of Cormaya. After this passage report back to me here. Understood?",Topic=7 +Topic=6 -> "I thought so. The mail service is not for just anyyone." + +Topic=7,"yes" -> "Ok, remember: the Tibian mail service puts trust in you! Don't fail and report back soon. Just tell me about your MISSION.",SetQuestValue(227,1) +Topic=7 -> "I thought so. The mail service is not for just anyone." +} diff --git a/app/SabrehavenServer/data/npc/killing-tasks.ndb b/app/SabrehavenServer/data/npc/killing-tasks.ndb new file mode 100644 index 0000000..c6342a6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/killing-tasks.ndb @@ -0,0 +1,182 @@ +ADDRESS,"hello$",QuestValue(17615)>99,! -> "Welcome back elite hunter %N of the 'Paw and Fur - Hunting Elite'. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17615)>99,! -> * +ADDRESS,"hello$",QuestValue(17615)>69,! -> "Welcome back trophy hunter %N of the 'Paw and Fur - Hunting Elite'. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17615)>69,! -> * +ADDRESS,"hello$",QuestValue(17615)>39,! -> "Welcome back big game hunter %N of the 'Paw and Fur - Hunting Elite'. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17615)>39,! -> * +ADDRESS,"hello$",QuestValue(17615)>19,! -> "Welcome back ranger %N of the 'Paw and Fur - Hunting Elite'. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17615)>19,! -> * +ADDRESS,"hello$",QuestValue(17615)>9,! -> "Welcome back huntsman %N of the 'Paw and Fur - Hunting Elite'. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17615)>9,! -> * +ADDRESS,"hello$",QuestValue(17607)=1,! -> "Welcome to the 'Paw and Fur - Hunting Elite' %N. Feel free to do tasks for us." +ADDRESS,"hi$",QuestValue(17607)=1,! -> * +ADDRESS,"hello$",! -> "Welcome %N. Would you like to join the 'Paw and Fur - Hunting Elite'?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a second, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Happy hunting, old chap!" + +"bye" -> "Happy hunting, old chap!", Idle +"farewell" -> * + +"yes",QuestValue(17607)=0 -> "Great! A warm welcome to our newest member: %N! Ask me for a task if you want to go on a hunt.", SetQuestValue(17607,1) +"join",QuestValue(17607)=0 -> * + +"task",QuestValue(17607)=0 -> "Firstly, you have to join us in our society %N. Would you like to join the 'Paw and Fur - Hunting Elite'?" +"mission",QuestValue(17607)=0 -> * + +# Trades +"do","you","sell" -> "I am looking for Tibianus talons however I will need them later." +"do","you","have" -> * +"offer" -> * +"trade" -> * +"tibianus","talon" -> "We as a hunting society know very interesting purposes of those magical talons. But only the loyal hunters of the 'Paw and Fur - Hunting Elite' are allowed to know." + +"low","task",QuestValue(17607)=1,QuestValue(17608)=0 -> "You can hunt: Frost trolls, swamp trolls, lions, rats, hyaenas, bears, bugs, wolves, wasps, larvas, dwarfs, low undeads, crocodiles, tarantulas, carniphilas, apes, thornback tortoises, gargoyles, elves, outlaws or slimes.", Topic=5 +"medium","task",QuestValue(17607)=1,QuestValue(17608)=0 -> "You can hunt: Medium class orcs, high class minotaurs, lizards, high class dwarfs, medium undeads, quara scouts, ancient scarabs, wyverns, bonebeasts, beholders, djinns, pirates or dragons.", Topic=5 +"high","task",QuestValue(17607)=1,QuestValue(17608)=0 -> "You can hunt: High class orcs, hero, necromancers, underwater quara, giant spiders, banshees, lichs or cults.", Topic=5 +"very","high","task",QuestValue(17607)=1,QuestValue(17608)=0 -> "You can hunt: Nightmare, hydras, serpent spawns, behemoths, dragon lords, warlocks, hand of cursed fates, juggernauts, demons.", Topic=5 +"task",QuestValue(17607)=1,QuestValue(17608)=0 -> "Alright, what would you like to hunt? I have low level tasks, medium level tasks, high level tasks and very high level tasks.", Topic=5 + +# Recommended tasks for low level +Topic=5,"crocodile" -> Amount=17609, "They are a nuisance! You'll find them here in the jungle near the river. Hunt 100 crocodiles and you'll get a nice reward. Interested?", Topic=2 +Topic=5,"tarantula" -> Amount=17610, "There is a veritable plague of tarantulas living in the caves north of the river to the east. Can you squish 100 tarantulas for the Hunting Elite. What do you say?", Topic=2 +Topic=5,"carniphila" -> Amount=17611, "Damn walking weed-thingies! You'll find them deeper in the jungle. Weed out 50 carniphilas for our society. Alright?", Topic=2 +Topic=5,"ape" -> Amount=17612, "You'll find the apes deeper in the jungle. Hunt 100 merlkins, kongras or sibangs to complete this task. Alright?", Topic=2 +Topic=5,"tortoise" -> Amount=17613, "You'll find them on the Laguna Islands. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"gargoyle" -> Amount=17614, "They can be found all over Tibia. Hunt 65 of them. Interested?", Topic=2 +Topic=5,"frost","troll" -> Amount=17697, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"swamp","troll" -> Amount=17698, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"rat" -> Amount=17699, "They can be found all over Tibia. Hunt 25 of them. Interested?", Topic=2 +Topic=5,"wolves" -> Amount=17700, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"wasp" -> Amount=17701, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"larva" -> Amount=17702, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"dwarf" -> Amount=17703, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"undead" -> Amount=17704, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5,"elves" -> Amount=17729, "They can be found all over Tibia. Hunt 200 of them. Interested?", Topic=2 +Topic=5,"bug" -> Amount=17730, "They can be found all over Tibia. Hunt 40 of them. Interested?", Topic=2 +Topic=5,"outlaw" -> Amount=17731, "They can be found all over Tibia. Hunt 250 of them. Interested?", Topic=2 +Topic=5,"hyaena" -> Amount=17732, "They can be found all over Tibia. Hunt 30 of them. Interested?", Topic=2 +Topic=5,"lion" -> Amount=17733, "They can be found all over Tibia. Hunt 20 of them. Interested?", Topic=2 +Topic=5,"bear" -> Amount=17734, "They can be found all over Tibia. Hunt 35 of them. Interested?", Topic=2 +Topic=5,"slime" -> Amount=17735, "They can be found all over Tibia. Hunt 100 of them. Interested?", Topic=2 +Topic=5 -> "Maybe next time." + +"task",QuestValue(17609)=100 -> Data=1, Amount=40*QuestValue(17609)*ExperienceStage(29)*40/100, Price=2000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17610)=100 -> Data=2, Amount=120*QuestValue(17610)*ExperienceStage(29)*40/100, Price=6000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17611)=50 -> Data=2, Amount=150*QuestValue(17611)*ExperienceStage(29)*40/100, Price=5000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17612)=100 -> Data=1, Amount=115*QuestValue(17612)*ExperienceStage(29)*40/100, Price=6000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17613)=100 -> Data=2, Amount=150*QuestValue(17613)*ExperienceStage(29)*40/100, Price=6000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17614)=65 -> Data=3, Amount=150*QuestValue(17614)*ExperienceStage(29)*40/100, Price=5000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17697)=100 -> Data=2, Amount=23*QuestValue(17697)*ExperienceStage(29)*40/100, Price=1000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17698)=100 -> Data=2, Amount=25*QuestValue(17698)*ExperienceStage(29)*40/100, Price=1100, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17699)=25 -> Data=1, Amount=7*QuestValue(17699)*ExperienceStage(29)*40/100, Price=200, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17700)=100 -> Data=2, Amount=19*QuestValue(17700)*ExperienceStage(29)*40/100, Price=900, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17701)=100 -> Data=2, Amount=24*QuestValue(17701)*ExperienceStage(29)*40/100, Price=1000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17702)=100 -> Data=2, Amount=44*QuestValue(17702)*ExperienceStage(29)*40/100, Price=1200, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17703)=100 -> Data=2, Amount=45*QuestValue(17703)*ExperienceStage(29)*40/100, Price=2000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17704)=100 -> Data=2, Amount=60*QuestValue(17704)*ExperienceStage(29)*40/100, Price=2500, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17729)=200 -> Data=3, Amount=97*QuestValue(17729)*ExperienceStage(29)*40/100, Price=3800, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17730)=40 -> Data=1, Amount=18*QuestValue(17730)*ExperienceStage(29)*40/100, Price=400, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17731)=250 -> Data=1, Amount=57*QuestValue(17731)*ExperienceStage(29)*40/100, Price=4000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17732)=30 -> Data=1, Amount=20*QuestValue(17732)*ExperienceStage(29)*40/100, Price=350, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17733)=20 -> Data=1, Amount=30*QuestValue(17733)*ExperienceStage(29)*40/100, Price=600, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17734)=35 -> Data=1, Amount=23*QuestValue(17734)*ExperienceStage(29)*40/100, Price=400, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17735)=100 -> Data=1, Amount=160*QuestValue(17735)*ExperienceStage(29)*40/100, Price=7000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney + +# Recommended tasks for medium level +Topic=5,"quara","scout" -> Amount=17616, "The scouts can be found on Malada, one of the Shattered Isles. Your task is to kill 200 quara scouts. I accept quara constrictor scouts, quara hydromancer scouts, quara mantassin scouts, quara pincher scouts and quara predator scouts, are you in?", Topic=2 +Topic=5,"scarab" -> Amount=17617, "They can be found in tombs beneath the desert around Ankrahmun. Hunt 125 of them. Interested?", Topic=2 +Topic=5,"wyvern" -> Amount=17618, "They can be found all over Tibia. Hunt 100 wyverns. Interested?", Topic=2 +Topic=5,"bonebeast" -> Amount=17619, "They can be found all over Tibia. Hunt 100 bonebeasts. Interested?", Topic=2 +Topic=5,"dragon" -> Amount=17620, "They can be found all over Tibia. Hunt 200 dragons. Interested?", Topic=2 +Topic=5,"medium","orc" -> Amount=17712, "They can be found all over Tibia. Hunt 300 of them. Interested?", Topic=2 +Topic=5,"minotaur" -> Amount=17713, "They can be found all over Tibia. Hunt 300 of them. Interested?", Topic=2 +Topic=5,"lizard" -> Amount=17714, "They can be found all over Tibia. Hunt 300 of them. Interested?", Topic=2 +Topic=5,"high","dwarf" -> Amount=17715, "They can be found all over Tibia. Hunt 300 of them. Interested?", Topic=2 +Topic=5,"medium","undead" -> Amount=17716, "They can be found all over Tibia. Hunt 200 of them. Interested?", Topic=2 +Topic=5,"beholder" -> Amount=17736, "They can be found all over Tibia. Hunt 250 of them. Interested?", Topic=2 +Topic=5,"djinn" -> Amount=17737, "They can be found all over Tibia. Hunt 500 of them. Interested?", Topic=2 +Topic=5,"pirate" -> Amount=17738, "They can be found all over Tibia. Hunt 600 of them. Interested?", Topic=2 + +"task",QuestValue(17616)=200 -> Data=2, Amount=430*QuestValue(17616)*ExperienceStage(49)*35/100, Price=13000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17617)=125 -> Data=2, Amount=720*QuestValue(17617)*ExperienceStage(49)*35/100, Price=13000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17618)=100 -> Data=3, Amount=515*QuestValue(17618)*ExperienceStage(49)*35/100, Price=11000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17619)=100 -> Data=3, Amount=580*QuestValue(17619)*ExperienceStage(49)*35/100, Price=12000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17620)=200 -> Data=2, Amount=700*QuestValue(17620)*ExperienceStage(49)*35/100, Price=15000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17712)=300 -> Data=2, Amount=100*QuestValue(17712)*ExperienceStage(49)*35/100, Price=10000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17713)=300 -> Data=2, Amount=125*QuestValue(17713)*ExperienceStage(49)*35/100, Price=11000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17714)=300 -> Data=2, Amount=158*QuestValue(17714)*ExperienceStage(49)*35/100, Price=11500, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17715)=300 -> Data=2, Amount=166*QuestValue(17715)*ExperienceStage(49)*35/100, Price=11500, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17716)=200 -> Data=2, Amount=221*QuestValue(17716)*ExperienceStage(49)*35/100, Price=11000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17736)=250 -> Data=3, Amount=450*QuestValue(17736)*ExperienceStage(49)*35/100, Price=15000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17737)=500 -> Data=4, Amount=245*QuestValue(17737)*ExperienceStage(49)*35/100, Price=30000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17738)=600 -> Data=4, Amount=205*QuestValue(17738)*ExperienceStage(49)*35/100, Price=30000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney + +# Recommended tasks for high level +Topic=5,"quara" -> Amount=17621, "As you wish. Seek out a quara settlement and hunt 600 quara, it doesn't matter which type you hunt except scouts. Alright?", Topic=2 +Topic=5,"spider" -> Amount=17622, "Never liked spiders. Simply too many legs. And I always find them in my bath! Those nasty creepy-crawlies are a threat to the hygiene of every living being in Tibia. Hunt 500 of them. Okay?", Topic=2 +Topic=5,"banshee" -> Amount=17623, "The Banshee is an undead creature of sheer terror. There is several places where you can hunt them. Hunt 300 of them. Okay?", Topic=2 +Topic=5,"lich" -> Amount=17624, "Lich hell. Ugh, to become a Lich is the ultimate goal of all those who follow the dark path of necromancy. Hunt 500 of them. Alright?", Topic=2 +Topic=5,"cult" -> Amount=17625, "I hate those strange cult fanatics. Hunt 500 of any cult follower. Alright?", Topic=2 +Topic=5,"high","orc" -> Amount=17717, "They can be found all over Tibia. Hunt 125 of them. Interested?", Topic=2 +Topic=5,"hero" -> Amount=17718, "They can be found all over Tibia. Hunt 150 of them. Interested?", Topic=2 +Topic=5,"necromancer" -> Amount=17719, "They can be found all over Tibia. Hunt 300 of them. Interested?", Topic=2 + +"task",QuestValue(17621)=600 -> Data=4, Amount=850*QuestValue(17621)*ExperienceStage(69)*30/100, Price=50000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17622)=500 -> Data=3, Amount=900*QuestValue(17622)*ExperienceStage(69)*30/100, Price=60000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17623)=300 -> Data=5, Amount=900*QuestValue(17623)*ExperienceStage(69)*30/100, Price=45000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17624)=500 -> Data=4, Amount=900*QuestValue(17624)*ExperienceStage(69)*30/100, Price=65000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17625)=500 -> Data=2, Amount=325*QuestValue(17625)*ExperienceStage(69)*30/100, Price=50000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17717)=125 -> Data=2, Amount=475*QuestValue(17717)*ExperienceStage(69)*30/100, Price=30000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17718)=150 -> Data=2, Amount=1200*QuestValue(17718)*ExperienceStage(69)*30/100, Price=35000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17719)=300 -> Data=2, Amount=500*QuestValue(17719)*ExperienceStage(69)*30/100, Price=35000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney + +# Recommended tasks for very high level +Topic=5,"hydra" -> Amount=17626, "The hydras are located in the eastern jungle of Tiquanda and there are several mountain caves that are inhabited by them. Your task is to hunt a mere 650 hydras. Are you willing to do that?", Topic=2 +Topic=5,"serpent" -> Amount=17627, "Very dangerous, nasty and slimy creatures. They live deep in the old ruins of Banuta. I think a mere 800 serpent spawns should do the trick. What do you say?", Topic=2 +Topic=5,"behemoth" -> Amount=17628, "Behemoths must be kept away from the settlements at all costs. You'll find them east of here in the taboo-area or under Cyclopolis in Edron. Go there and hunt a few of them - shall we say... 700? Are you up for that?", Topic=2 +Topic=5,"dragon","lord" -> Amount=17629, "They can be found all over Tibia. Hunt 600 dragon lords. Interested?", Topic=2 +Topic=5,"hand" -> Amount=17630, "Those creatures are annoying and obviously lacking a head. Hunt 200 of them. Interested?", Topic=2 +Topic=5,"juggernaut" -> Amount=17631, "Juggernauts are known to be able to slash the head of a dragon with one hit. Hunt 200 of them. Interested?", Topic=2 +Topic=5,"nightmare" -> Amount=17720, "They can be found all over Tibia. Hunt 150 nightmares. Interested?", Topic=2 +Topic=5,"warlock" -> Amount=17721, "They can be found all over Tibia. Hunt 300 warlocks. Interested?", Topic=2 +Topic=5,"demon" -> Amount=17722, "Oh, this one is crazy task. Hunt 6666 demons. Interested?", Topic=2 + +"task",QuestValue(17626)=650 -> Data=4, Amount=2100*QuestValue(17626)*ExperienceStage(79)*25/100, Price=100000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17627)=800 -> Data=5, Amount=2000*QuestValue(17627)*ExperienceStage(79)*25/100, Price=120000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17628)=700 -> Data=5, Amount=2500*QuestValue(17628)*ExperienceStage(79)*25/100, Price=110000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17629)=600 -> Data=5, Amount=2100*QuestValue(17629)*ExperienceStage(79)*25/100, Price=100000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17630)=200 -> Data=5, Amount=5000*QuestValue(17630)*ExperienceStage(79)*25/100, Price=100000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17631)=200 -> Data=6, Amount=6500*QuestValue(17631)*ExperienceStage(79)*25/100, Price=100000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17720)=150 -> Data=6, Amount=2150*QuestValue(17720)*ExperienceStage(79)*25/100, Price=45000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17721)=300 -> Data=6, Amount=4000*QuestValue(17721)*ExperienceStage(79)*25/100, Price=60000, "Spiffing work, old chap. Here is your reward %A EXP and %P gold!", SetQuestValue(QuestValue(17608),99999), SetQuestValue(17608,0), SetQuestValue(17615,Data), Experience(Amount), CreateMoney +"task",QuestValue(17722)=6666 -> "You really did that? It is not implemented yet bro." + +# Speaks +"task",QuestValue(17608)>0 -> "Your current task is in progress. Follow the status of your task in the quest log. If you wish to cancel your in-progress task then don't be afraid and feel free to cancel." + +Topic=2,"yes",QuestValue(Amount)=99999 -> "Oh, I am sorry but you have already finished that task." +Topic=2,"yes" -> "Happy hunting, old chap! Speak to me again when you are done hunting.", SetQuestValue(17608,Amount), SetQuestValue(Amount,0) +Topic=2 -> "Maybe later." + +"cancel",QuestValue(17608)>0 -> "Are you sure you want to cancel your current task?", Topic=3 +Topic=3,"yes" -> "Alright! Feel free to do other tasks for us.", SetQuestValue(QuestValue(17608),99998), SetQuestValue(17608,0) +Topic=3 -> "Good! Speak to me again when you are done hunting." + +"rank",QuestValue(17615)>99 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Elite Hunter." +"point",QuestValue(17615)>99 -> * +"rank",QuestValue(17615)>69 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Trophy Hunter." +"point",QuestValue(17615)>69 -> * +"rank",QuestValue(17615)>39 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Big Game Hunter." +"point",QuestValue(17615)>39 -> * +"rank",QuestValue(17615)>19 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Ranger." +"point",QuestValue(17615)>19 -> * +"rank",QuestValue(17615)>9 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Huntsman." +"point",QuestValue(17615)>9 -> * +"rank",QuestValue(17607)=1 -> Amount=QuestValue(17615), "At this time, you have %A points and your rank is Member." +"point",QuestValue(17607)=1 -> * + +"mission" -> "We prefer to call that 'task' in the society of the 'Paw and Fur - Hunting Elite'." \ No newline at end of file diff --git a/app/SabrehavenServer/data/npc/king.npc b/app/SabrehavenServer/data/npc/king.npc new file mode 100644 index 0000000..9980c0a --- /dev/null +++ b/app/SabrehavenServer/data/npc/king.npc @@ -0,0 +1,97 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# king.npc: Datenbank für den König von Tibia + +Name = "King Tibianus" +Outfit = (130,21-87-107-95-0) +Home = [32311,32171,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hail","tibianus",! -> "Long live the Tibianus!" +ADDRESS,"hello","king",! -> "I greet thee, my loyal subject." +ADDRESS,"hail","king",! -> * +ADDRESS,"salutations","king",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am your sovereign, King Tibianus III, and it's my duty to provide justice and guidance for my subjects." +"justice" -> "I try my best to be just and fair to our citizens. The army and the TBI are a great help for fulfilling this duty." +"name" -> "It's hard to believe that you don't know your own king!" +"news" -> "The latest news are usually brought to our magnificent town by brave adventurers. They spread tales of their journeys at Frodo's tavern." +"tibia" -> "Soon the whole land will be ruled by me once again!" +"land" -> * +"how","are","you"-> "Thank you, I'm fine." +"castle" -> "Rain Castle is my home." +"sell" -> "Sell? Sell what? My kingdom isn't for sale!" +"god" -> "Honor the gods and pay your taxes." +"zathroth" -> "Please ask a priest about the gods." +"citizen" -> "The citizens of Tibia are my subjects. Ask the old monk Quentin to learn more about them." +"sam" -> "He is a skilled blacksmith and a loyal subject." +"frodo" -> "He is the owner of Frodo's Hut and a faithful tax-payer." +"gorn" -> "He was once one of Tibia's greatest fighters. Now he is selling equipment." +"benjamin" -> "He was once my greatest general. Now he is very old and senile but we entrusted him with work for the Royal Tibia Mail." +"harkath" -> "Harkath Bloodblade is the general of our glorious army." +"bloodblade" -> * +"general" -> * +"noodles" -> "The royal poodle Noodles is my greatest treasure!" +"ferumbras" -> "He is a follower of the evil god Zathroth and responsible for many attacks on us. Kill him on sight!" +"bozo" -> "He is my royal jester and cheers me up now and then." +"treasure" -> "The royal poodle Noodles is my greatest treasure!" +"monster" -> "Go and hunt them! For king and country!" +"help" -> "Visit Quentin, the monk, for help." +"quest" -> "I will call for heroes as soon as the need arises again and then reward them appropriately." +"mission" -> * +"gold" -> "To pay your taxes, visit the royal tax collector." +"money" -> * +"tax" -> * +"sewer" -> "What a disgusting topic!" +"dungeon" -> "Dungeons are no places for kings." +"equipment" -> "Feel free to buy it in our town's fine shops." +"food" -> "Ask the royal cook for some food." +"time" -> "It's a time for heroes, that's for sure!" +"heroes" -> * +"hero$" -> * +"adventurer" -> * +"tax","collector"-> "He has been lazy lately. I bet you have not payed any taxes at all." +"king" -> "I am the king, so mind your words!" +"army" -> "Ask the soldiers about that topic." +"enemy" -> "Our enemies are numerous. The evil minotaurs, Ferumbras, and the renegade city of Carlin to the north are just some of them." +"enemies" -> * +"city","north" -> "They dare to reject my reign over the whole continent!" +"carlin" -> * +"thais" -> "Our beloved city has some fine shops, guildhouses, and a modern system of sewers." +"city" -> * +"shop" -> "Visit the shops of our merchants and craftsmen." +"merchant" -> "Ask around about them." +"craftsmen" -> * +"guild" -> "The four major guilds are the knights, the paladins, the druids, and the sorcerers." +"minotaur" -> "Vile monsters, but I must admit they are strong and sometimes even cunning ... in their own bestial way." +"paladin" -> "The paladins are great protectors for Thais." +"elane" -> * +"knight" -> "The brave knights are necessary for human survival in Thais." +"gregor" -> * +"sorcerer" -> "The magic of the sorcerers is a powerful tool to smite our enemies." +"muriel" -> * +"druid" -> "We need the druidic healing powers to fight evil." +"marvik" -> * +"good" -> "The forces of good are hard pressed in these dark times." +"evil" -> "We need all strength we can muster to smite evil!" +"order" -> "We need order to survive!" +"chaos" -> "Chaos arises from selfishness, and that's its weakness." +"excalibug" -> "It's the sword of the kings. If you could return this weapon to me I would reward you beyond your dreams." +"reward" -> "Well, if you want a reward, go on a quest to bring me Excalibug!" +"chester" -> "A very competent person. A little nervous but very competent." +"tbi$" -> "This organisation is important in holding our enemies in check. Its headquarter is located in the bastion in the northwall." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/app/SabrehavenServer/data/npc/klaus.npc b/app/SabrehavenServer/data/npc/klaus.npc new file mode 100644 index 0000000..6fd6450 --- /dev/null +++ b/app/SabrehavenServer/data/npc/klaus.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Klaus" +Outfit = (96,0-0-0-0-0) +Home = [31981,32826,2] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Ho matey!", Topic=1 +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a moment, %N. I'll be here soon.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Whenever your throat is dry, you know where to find my tavern." + +"bye" -> "Whenever your throat is dry, you know where to find my tavern.", Idle +"farewell" -> * +"name" -> "I'm Klaus." +"rum" -> "There are people who would kill you for rum. Then again there are people who would kill you anyway." +"raymond striker" -> "Sooner or later he will run out of luck and then we will have his head." +"liberty bay" -> "We have a kind of trade agreement. They make the rum and we plunder it. Har Har Har." +"thais" -> "The Thaian people see themselves as the rulers of the world. If they only knew ... Har Har Har." +"carlin" -> "A city that is run by women. And those wimps in Thais have still not been able to regain control. Har Har Har." +"venore" -> "The Venoreans are the power of the future, trust me." +"king" -> "If I were the king, I'd fill my court with the most beautiful women of the realm. Har Har Har." +"treasure map" -> "I've already given you the complete map. Now you're on your own, matey." +"pirate" -> "Ah, the romantic life of a pirate. Fish for breakfast, storms for lunch and an early death for supper. Har Har Har." +"excalibug" -> "I guess it would come in handy to open some casks. Har Har Har." +"ferumbras" -> "Dead magicians are only good for frightening little children. Har Har Har." + +"gambl" -> "So you care for a civilized game of dice?", Topic=1 +"game" -> * +"dice" -> * + +Topic=1,"yes" -> "Hmmm, would you like to play for money or for a chance to win your own dice?", Topic=3 +Topic=1,"no" -> "Oh come on, don't be a child." + +Topic=3,"money" -> "I thought so. Okay, I will roll a dice. If it shows 6, you will get five times your bet. How much do you want to bet?", Amount=Random(1,6), Topic=2 +Topic=3,"dice" -> "Hehe, good choice. Okay, the price for this game is 100 gold pieces. I will roll a dice. If I roll a 6, you can have my dice. Agreed?", Amount=Random(1,6), Topic=4 + +Topic=6,"yes" -> "Okay, I will roll a dice. If it shows 6, you will get five times your bet. How much do you want to bet?", Amount=Random(1,6), Topic=2 +Topic=6,"no" -> "Oh come on, don't be a child." +Topic=2,%1,0<%1,100>%1,CountMoney>=%1,Amount=6 -> Price=%1*5, "Ok, here we go ... 6! You have won %P, congratulations. One more game?", CreateMoney, EffectMe(27), Topic=6 +Topic=2,%1,0<%1,100>%1,CountMoney>=%1 -> Price=%1, "Ok, here we go ... %A! You have lost. Har Har Har. One more game?", DeleteMoney, EffectMe(27), Topic=6 +Topic=2,%1,0<%1,100>%1 -> "You don't have so much money. How much do you want to bet?", Topic=2 +Topic=2,%1 -> "I accept only bets between 1 and 99 gold. I don't want to ruin you after all. How much do you want to bet?", Topic=2 + +Topic=5,"yes" -> "Okay, the price for this game is 100 gold pieces. I will roll a dice. If I roll a 6, you can have my dice. Agreed?", Amount=Random(1,6), Topic=4 +Topic=5,"no" -> "Oh come on, don't be a child." +Topic=4,"yes",CountMoney>=100,Amount=6 -> Price=100, Type=5792, Amount=1, "Ok, here we go ... 6! You have won a dice, congratulations. One more game?", DeleteMoney, Create(Type), EffectMe(27), Topic=5 +Topic=4,"yes",CountMoney>=100 -> Price=100, "Ok, here we go ... %A! You have lost. Har Har Har. One more game?", DeleteMoney, EffectMe(27), Topic=5 +Topic=4,"yes" -> "You don't have so much money." + +"mission",QuestValue(17520)=9,QuestValue(17531)=0 -> "Hmm, you look like a seasoned seadog. Kill Captain Ray Striker, bring me his lucky pillow as a proof and you are our hero!", SetQuestValue(17531,1) + +"mission",QuestValue(17531)=1 -> Type=6105, Amount=1, "Do you have Striker's pillow?", Topic=7 +"pillow",QuestValue(17531)=1 -> * +Topic=7,"yes",Count(Type)>=Amount -> "You DID it!!! Incredible! Boys, lets have a PAAAAAARTY!!!!", Delete(Type), SetQuestValue(17531,2) +Topic=7,"yes" -> "No you not!" +Topic=7 -> "Maybe another time." + +"mission" -> "Sorry, I don't have any missions for you." +} diff --git a/app/SabrehavenServer/data/npc/kroox.npc b/app/SabrehavenServer/data/npc/kroox.npc new file mode 100644 index 0000000..bbf0835 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kroox.npc @@ -0,0 +1,129 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kroox.npc: Datenbank für den Rüstungshändler Kroox + +Name = "Kroox" +Outfit = (160,0-100-105-76-0) +Home = [32650,31887,9] +Radius = 6 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Kroox Quality Armor, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You next %N, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * + +"measurements",QuestValue(235)>0,QuestValue(237)<1 -> "Hm, well I guess its ok to tell you ... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(237,1) + +"measurements" -> "UH? No clue what you are talking about, jawoll." + +"job" -> "I sell best armor in land. My armor save you life. Better buy much." +"shop" -> * +"name" -> "My name is Kroox Shieldbearer, son of Earth, from the Molten Rock." +"time" -> "It's %T now." +"help" -> "I sell and buy all kinds of armor. Dwarfish are the best, jawoll!" +"dwarf$" -> "We are proud fellows." +"monster" -> "You not be afraid, here you be save." +"dungeon" -> "Much fun you can have in dungeons. Much battle and much gold, jawoll!" +"mines" -> "Foreigners not welcome in mines. An evil basilisk rob our deeper mines." +"thanks" -> "I you thank, too." +"thank","you" -> * + +"buy" -> "What you need? I sell armor, helmets, shields, and legs." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "I offer armor, helmets, legs, and shields." +"weapon" -> "Ask in the shop next tunnel about that." +"helmet" -> "I sell chain helmets, brass helmets, iron helmets, and steel helmets. What you want?" +"armor" -> "I sell chain armor, brass armor, and plate armor. What you need?" +"shield" -> "I sell steel shields, dwarven shields, brass shields, and plate shields. What you want?" +"trousers" -> "I am selling chain legs, and brass legs. What you need?" +"legs" -> * +"you","buy" -> "You want sell any armor?" + +"chain","armor" -> Type=3358, Amount=1, Price=200, "You want buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "You want buy a brass armor for %P gold?", Topic=1 +"plate","armor" -> Type=3357, Amount=1, Price=1200,"You want buy a plate armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "You want buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "You want buy a brass helmet for %P gold?", Topic=1 +"iron","helmet" -> Type=3353, Amount=1, Price=390, "You want buy an iron helmet for %P gold?", Topic=1 +"steel","helmet" -> Type=3351, Amount=1, Price=580, "You want buy a steel helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "You want buy a steel shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "You want buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "You want buy a plate shield for %P gold?", Topic=1 +"dwarven","shield" -> Type=3425, Amount=1, Price=500, "You want buy a dwarven shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "You want buy chain legs for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "You want buy brass legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "You want buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "You want buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=1200*%1,"You want buy %A plate armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "You want buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "You want buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=390*%1, "You want buy %A iron helmets for %P gold?", Topic=1 +%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=580*%1, "You want buy %A steel helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "You want buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "You want buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "You want buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=500*%1, "You want buy %A dwarven shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "You want buy %A chain legs for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "You want buy %A brass legs for %P gold?", Topic=1 + +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "You want sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "You want sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "You want sell a plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "You want sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "You want sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "You want sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "You want sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "You want sell a iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "You want sell a devil's helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "You want sell a warrior helmet for %P gold?", Topic=2 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=100, "You want sell a dwarven shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "You want sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "You want sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "You want sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "You want sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "You want sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "You want sell chain legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "You want sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "You want sell knight legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "You want sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "You want sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "You want sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "You want sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "You want sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "You want sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "You want sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "You want sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "You want sell %A devil's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "You want sell %A warrior helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=100*%1, "You want sell %A dwarven shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "You want sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "You want sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "You want sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "You want sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "You want sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "You want sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "You want sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "You want sell %A knight legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here, you take it.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No, no, you do not hold enough gold." +Topic=1 -> "I think you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe you sell it next time." +"sam","sen",QuestValue(289)=1 -> "Oh, so its you, he wrote me about? Sadly I have no dwarven armor in stock. But I give you the permission to retrive one from the mines. ...", + "The problem is, some giant spiders made the tunnels where the storage is their new home. Good luck.",SetQuestValue(289,2) +} diff --git a/app/SabrehavenServer/data/npc/kruzak.npc b/app/SabrehavenServer/data/npc/kruzak.npc new file mode 100644 index 0000000..9d6c676 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kruzak.npc @@ -0,0 +1,93 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kruzak.npc: Datenbank für den Zwergenkönig + +Name = "Emperor Kruzak" +Outfit = (66,0-0-0-0-0) +Home = [32627,31923,3] +Radius = 1 + +Behaviour = { +ADDRESS,"hello","emperor",! -> "Hiho, may Fire and Earth bless you, my child." +ADDRESS,"hail","emperor",! -> * +ADDRESS,"salutations","emperor",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "Gone, like swallowed from the earth!" + +"bye" -> "Farewell, %N, my child!", Idle +"farewell" -> * +"job" -> "Well, I am the emperor of the dwarfs. It's my duty to protect my folk and give them justice." +"justice" -> "Well, justice is a difficult thing. Can one be just to all at once, and if not, to whom should he be just?" +"name" -> "Well, I am Emperor Kruzak Dustbeard, son of Fire and Earth, second only to the gods, jawoll." +"news" -> "Well, I am too old to care about gossip anymore." +"tibia" -> "Well, the gods handed the lands over to the younger races, but my people will leave this word in dignity." +"land" -> * + +"how","are","you"-> "Well, I'm fine, the last centuries have been good to me, jawoll." +"castle" -> "Well, our people need no castle, our home IS a fortress." +"sell" -> "Well, what do you expect kings to sell?" +"god" -> "Well, we honor Father Earth and Mother Fire. Some of us even follow the teachings of additional gods." +"citizen" -> "Well, all dwarfs could be considered citizens. We are brothers and sisters in Fire and Earth." +"noodles" -> "WAAAH! Don't mention that beast! It was after the bones of our ancestors, last time King Tibianus visted us!" +"ferumbras" -> "Well, we are prepared even for him." +"treasure" -> "Well, we are not poor people, but we have ways to defend our wealth." +"monster" -> "Well, it's up to the younger ones to slay the beasts that roam the lands and the tunnels." +"help" -> "Well, I am too busy to help you, but feel free to ask around." +"quest" -> "Well, if you wander the world with open eyes, you will see the quests without asking." +"mission" -> * +"gold" -> "Well, our people love gold; that's common knowledge." +"money" -> * +"tax" -> * +"mines" -> "Well, our mines have been invaded by a basilisk. We trapped him with a cave in. The hero that could bring me the body of the beast will get a great reward." +"dungeon" -> "Well, there are a lot of dungeons in the lands, waiting to be explored by the daring ones." +"equipment" -> "Well, go and buy it in the city." +"food" -> * +"time" -> "Well, after some centuries I stopped to worry about time anymore." +"hero" -> "Well, we dwarfs produced some of the greatest heroes of all times." +"adventurer" -> * +"tax" -> "Well, our taxes are moderate." +"emperor" -> "Well, I am the emperor of the dwarfs and the oldest living dwarf, jawoll!" +"age" -> "Well, I don't want to talk about my age." +"old" -> * +"youth" -> * +"army" -> "Well, you better ask the general." +"enemy" -> "Well, only a dead enemy is a good enemy." +"enemies" -> * +"thais" -> "Well we are at peace with Carlin and Thais." +"carlin" -> * +"city" -> "Well, go and see the wonders of our cities yourself." +"shop" -> "Well, my subjects maintain many fine shops. Go and have a look at their wares." +"merchant" -> "Well, there are some in our city. Go and visit them." +"craftsmen" -> * +"guild" -> "Well, we have two guilds in our town, the Knights and the Sorcerers." +"minotaur" -> "Well, we don't fear them. They seem more occupied with the humans anyway." +"elves" -> "Well, they are not as bad as one might think." +"paladin" -> "Well, we have some crossbowmen in our army but rely more on our knights." +"legola" -> "Well, isn't that the leader of the Carlin Paladins?" +"elane" -> "Well, the High Paladin is a woman that deserves respect. I admired most Elanes I met in my life." +"knight" -> "Well, dwarfish knights are feared by our enemies in the whole world." +"trisha" -> "Well, that's the Carlin High Knight, isn't she? Not to bad as fighter for a big one." +"sorceror" -> "Well, the sorcerers are followers of the elemental powers." + +"durin" -> "Well, Durin is one of the celestial paladins, messenger of the gods. He is the protector of the dwarfish race." +"druid" -> "Well, we have almost no druids in our town. If need arises we can call for them from Carlin." +"padreia" -> "Well, thats an trustworthy ally of dwarfs." +"good" -> "Well, good and evil will fight each other for all eternity." +"evil" -> * +"order" -> "Well, order is of great importance. Life has to follow rules, so do we." +"chaos" -> "Well, chaos is the ancient enemy. Dwarfs bring order to the world and give things shape. We are enemies of chaos." +"excalibug" -> "Well, well, well, the godblade. A myth? Perhaps. Even in my youth, it was only a legend." +"reward" -> "Well, isn't it reward enough to talk to the emperor of all dwarfs?" +"tbi" -> "Well, I don't think our southern allies have agents in our town." +"t.b.i." -> * +"edron" -> "Well, it is a colony of this human king." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/app/SabrehavenServer/data/npc/kulag.npc b/app/SabrehavenServer/data/npc/kulag.npc new file mode 100644 index 0000000..0de8240 --- /dev/null +++ b/app/SabrehavenServer/data/npc/kulag.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Kulag.npc: Datenbank für die Stadtwache am Westtor + +Name = "Kulag, the guard" +Outfit = (131,19-19-19-19-0) +Home = [32287,32264,07] +Radius = 2 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/app/SabrehavenServer/data/npc/lea.npc b/app/SabrehavenServer/data/npc/lea.npc new file mode 100644 index 0000000..d3ac49a --- /dev/null +++ b/app/SabrehavenServer/data/npc/lea.npc @@ -0,0 +1,120 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lea.npc: Datenbank für die Magierin Lea + +Name = "Lea" +Outfit = (138,59-95-94-113-0) +Home = [32348,31828,6] +Radius = 3 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> "Welcome back, %N!" +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Be patient %N, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care on your journeys." + +"bye" -> "Take care on your journeys.", Idle +"farewell" -> * +"job" -> "I am the archsorcerer of Carlin. I keep the secrets of our order." +"name" -> "My name is Lea." +"time" -> "Time is a force we sorcerers will master one day." +"wisdom" -> "You need great wisdom to cast spells of power." +"male" -> "Some tricks of sorcery are easy enough to be mastered even by males, but they'd better stick to cardtricks." +"sorcerer" -> "Any sorcerer dedicates his whole life to the study of the arcane arts." +"power" -> "We sorcerers wield arcane powers beyond comprehension of men." +"arcane" -> * +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Sorcerers, paladins, knights, and druids." +"spellbook" -> "A spellbook lists all your spells. There you can find the pronunciation of each spell. You can buy one at the magicians' shop." +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Take care on your journeys.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." +} diff --git a/app/SabrehavenServer/data/npc/lector.npc b/app/SabrehavenServer/data/npc/lector.npc new file mode 100644 index 0000000..22f526b --- /dev/null +++ b/app/SabrehavenServer/data/npc/lector.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lector.npc: Datenbank für den Metzger Lector + +Name = "Lector" +Outfit = (128,79-38-0-124-0) +Home = [32351,31795,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my humble shop, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come and buy again." + +"bye" -> "Please come and buy again.", Idle +"farewell" -> * +"job" -> "I am the butcher. I am selling delicious meat." +"butcher" -> * +"name" -> "My father named me Lector." +"father" -> "My father, Hannibal, was the royal cook. He died some years ago in an attack of the evil Ferumbras." +"time" -> "It is exactly %T." +"graveyard" -> "I heared, the mausoleum is haunted!" +"mausoleum" -> * +"dragon","steak" -> "Dragon steak was the favourite meal of Tark Trueblade." +"tark","trueblade" -> "Tark Trueblade was the greatest dragonslayer of all. I heared, he died in a battle with an ancient dragon lord." +"ghostlands" -> "A bloody place with a bloody history. I wonder if it realy drove people mad or if it just attracted those already disbalanced in their minds." + +"buy" -> "I can offer you ham or meat. Dragon steaks are out. " +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I can offer you ham or meat." + +"meat" -> Type=3577, Amount=1, Price=3, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=6, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=3*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=6*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/leedelle.npc b/app/SabrehavenServer/data/npc/leedelle.npc new file mode 100644 index 0000000..6ea6fb2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/leedelle.npc @@ -0,0 +1,176 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# leedelle.npc: Datenbank für die Händlerin Le'Delle (Newbie) + +Name = "Lee'Delle" +Outfit = (136,78-76-72-96-0) +Home = [32024,32196,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "I'm sorry %N, but I only serve premium account customers.", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Not now, not now, sorry %N. Please wait a moment.", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "I'm sorry %N, but I only serve premium account customers." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm delighted to welcome you as customer." +"sell" -> "I sell much. Have a look at the blackboards for my wares or just ask." +"job" -> "I am a merchant, so what can I do for you?" +"name" -> "My name is Lee'Delle. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I am already helping you by selling stuff." +"monster" -> "There are plenty of them. Buy here the equipment to kill them and sell their loot afterwards!" +"dungeon" -> "be carefull down there. Make sure you bought enough torches and a rope or you might get lost." +"sewer" -> "The sewers are full of rats. They are quite a challenge for inexperienced adventurers." +"king" -> "The king supports our little village very much!" +"dallheim" -> "He is a great warrior and our protector." +"bug" -> "There are several bugs in the wildernes." +"stuff" -> "I sell equipment of all kinds. Just ask me about the type of wares you are interested in." +"tibia" -> "The continent is even more exciting than this isle!" +"thais" -> "Thais is the capital of the thaian empire." + +"mission" -> "I really love flowers. Sadly my favourites, honey flowers are very rare on this isle. If you can find me one, I'll give you a little reward." +"quest" -> * +"reward" -> * + +"honey", "flower", Count(2984)>=1 -> "Oh, thank you so much! Please take this piece of armor as reward.",Amount=1, Delete(2984), Create(3362) +"honey", "flower" -> "Honey flowers are my favourites ." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "Sorry I fear the only picks left on this isle are in the posession of Al Dee." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","studded","armor" -> Type=3378, Amount=1, Price=25, "Do you want to sell a studded armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=25*%1, "Do you want to sell %A studded armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/leeland.npc b/app/SabrehavenServer/data/npc/leeland.npc new file mode 100644 index 0000000..6d33f22 --- /dev/null +++ b/app/SabrehavenServer/data/npc/leeland.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# leeland.npc: Datenbank für den Besitzer des allgemeinen Markts Leeland Slim + +Name = "Leeland" +Outfit = (130,19-36-96-76-0) +Home = [32883,32082,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute, %N. I am talking already, but will be avaliable for you very soon.", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N. Make sure to visit my shop." + +"bye" -> "Good bye, %N. Make sure to visit my shop.", Idle +"name" -> "My name is Slim, Leeland Slim." +"job" -> "I am the owner of the Useful Things Warehouse." +"warehouse" -> "I offer many things. Actually I am sure you will find something you have desired for a long time." +"time" -> "So it is a watch you need? Make sure to buy one downstairs." +"king" -> "Do I hear envy in your voice? Is it that you realy want? To be ... king? Well, one never knows ... perhaps you find something royal in my warehouse." +"tibianus" -> * +"army" -> "Your question suggests you are interested in military? I am sure you will find something interesting in my warehouse." +"ferumbras" -> "Ah, Ferumbras. I remember selling him torches for his first adventures as if it was yesterday." +"excalibug" -> "That's one of the few things even I can't aquire for you." +"thais" -> "Perhaps one day I will settle in thais again. I love the city's potential." +"tibia" -> "I have seen most of it. And I like it. " +"carlin" -> "I was there some time ago. I exchanged ideas with some important people there and even could sell them something that furthered their cause." +"news" -> "I would love to have the time to chat and exchange gossip, but sadly business always comes first, you know?" +"tax" -> "Taxes are a necessary evil, people say. I like that." +"privilege" -> "Privileges have to be paid for. The one way ... or the other." +"gambling" -> "I just love bets and gambling. It inspires people doing such interesting things." +} diff --git a/app/SabrehavenServer/data/npc/legola.npc b/app/SabrehavenServer/data/npc/legola.npc new file mode 100644 index 0000000..378bb9b --- /dev/null +++ b/app/SabrehavenServer/data/npc/legola.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# legola.npc: Datenbank für die Paladinin Legola + +Name = "Legola" +Outfit = (137,72-105-105-95-0) +Home = [32298,31784,7] +Radius = 2 + +Behaviour = { +ADDRESS,Paladin,"hello$",! -> "Hello, %N! Nice to see you." +ADDRESS,Paladin,"hi$",! -> * +ADDRESS,"hello$",! -> "Welcome to the Paladins, %N! What is your business here?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the local leader of the paladins' guild. I am trainer and teacher to our members." +"name" -> "My name is Legola. I am the head of the local paladins' guild." +"time" -> "It is %T." +"member" -> "Paladins profit from their chosen vocation. It has many advantages to be a paladin." +"profit" -> "The guild will help paladins to improve their skills. Besides we offer spells for our members." +"advantage" -> * +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Paladins, knights, sorcerers, and druids." +"paladin" -> "Paladins are great warriors and able magicians. Besides we are deadly missile fighters. Many people in Tibia want to join us." +"skill" -> * +"warrior" -> "Of course, we aren't as strong as knights, but no druid or sorcerer will ever defeat a paladin with a sword." +"magician" -> "Paladins learn to use most runes and can cast some usefull spells." +"missile" -> "Paladins are missile fighters, unequaled in Tibia!" +"woman" -> "All guild leaders in Carlin are chosen for their wisdom, and so all are women." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit the magicians' shop in the south of Carlin." +"ghostlands" -> "Many tried to break that curse, but the evil there is so deep and overwhelmig there seems to be no hope." + +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Good bye.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/app/SabrehavenServer/data/npc/liane.npc b/app/SabrehavenServer/data/npc/liane.npc new file mode 100644 index 0000000..213964c --- /dev/null +++ b/app/SabrehavenServer/data/npc/liane.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# liane.npc: Datenbank für die Postfrau Liane + +Name = "Liane" +Outfit = (136,77-60-79-114-0) +Home = [32332,31784,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. May I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N. I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was a pleasure to help you." + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + +"kevin" -> "Kevin Postner was already leader of the guild as I joined. I can't imagine anyone better for that position." +"postner" -> * +"postmasters","guild" -> "Our guild relys heavily on the honor and trustworthyness of its members." +"join" -> "You might apply for a membership in our haedquarter." +"headquarter" -> "Its just south oh Kazordoon. Follow the road and you will run right into it." + + +"measurements",QuestValue(234)>0,QuestValue(239)<1 -> "I have more urgent problem to attend then that. Those hawks are hunting my carrier pigeons. Bring me 12 arrows and I'll see if I have the time for this nonsense. Do you have 12 arrows with you?",Type=3447, Amount=12,Topic=5 +"arrows",QuestValue(234)>0,QuestValue(239)<1 -> "Do you have 12 arrows with you?",Type=3447, Amount=12,Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Great! Now I'll teach them a lesson ... For those measurements ... ", Delete(Type),SetQuestValue(234,QuestValue(234)+1),SetQuestValue(239,1) +Topic=5,"yes" -> "Fool, you have no 12 arrows." +Topic=5 -> "Don't waste my time." + + +"job" -> "I am working here at the post office. If you have questions about the Royal Carlin Mail System or the depots ask me." +"office" -> "I rarely leave my office. You are welcome at any time." +"name" -> "My name is Liane." +"time" -> "Now it's %T." +#"mail" -> "Our mail system is unique! And so simple even males can use it. Do you want to know more about it?", Topic=1 +#"depot" -> "The depots are very easily to use. Just step in front of them and you will find your items in them. They are free for all citizens. Hail our Queen!" +"queen" -> "Our Queen's rule makes Carlin prosper." +"carlin" -> "Our wonderful town is protected by the wise Queen Eloise." +"thais" -> "A town ruled by men, a dangerous place. Anyway, we bring also letters and parcels there." +"benjamin" -> "He is the postman in Thais and somewhat stupid. But he never sents wrong letters or parcels." +"ghostlands" -> "We don't deliver letters or parcels there, sorry." +"wally" -> "Wally and I became pen-pals in the course of years." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else, I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/lightfoot.npc b/app/SabrehavenServer/data/npc/lightfoot.npc new file mode 100644 index 0000000..f23227e --- /dev/null +++ b/app/SabrehavenServer/data/npc/lightfoot.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lightfoot.npc: Datenbank fuer den Rennhund Lightfoot + +Name = "Lightfoot" +Outfit = (32,0-0-0-0-0) +Home = [32914,32080,6] +Radius = 14 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/app/SabrehavenServer/data/npc/lily.npc b/app/SabrehavenServer/data/npc/lily.npc new file mode 100644 index 0000000..72fd2d8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lily.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lily.npc: Datenbank fuer die Druidin Lily + +Name = "Lily" +Outfit = (138,78-101-86-115-0) +Home = [32068,32225,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, %N. I'll be with you in no time.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care." + +"bye" -> "Take care.", Idle +"farewell" -> * +"how","are","you" -> "Very well. Thank you." +"offer" -> "I only sell my antidote runes and I'll be happy to buy some blueberries from you." +"job" -> "I am a druid, bound to the spirit of nature. I'm selling antidote runes that help against poison. Oh, and I buy blueberries, of course." +"name" -> "My name is Lily." + +"hyacinth" -> "Hyacinth lives in the forest. He's never in town so I don't know him very well." +"time" -> "It is about %T." +"help" -> "I can sell you an antidote rune. It's against the poison of so many dangerous creatures." + +"monster" -> "Many monsters are poisonous. Don't let them bite you or you will need one of my antidote runes." +"creature" -> * +"poison" -> * +"life","fluid" -> "I'm sorry, but Hyacinth is the only one on Rookgaard who knows how to brew life fluids." + +"antidote" -> Type=3153, Data=1, Amount=1, Price=40, "Do you want to buy an antidote rune for %P gold?", Topic=1 +"rune" -> * +%1,1<%1,"antidote" -> Type=3153, Data=1, Amount=%1, Price=40*%1, "Do you want to buy %A antidote runes for %P gold?", Topic=1 +%1,1<%1,"rune" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you don't have enough gold." +Topic=1 -> "As you wish." + +"sell","blueberry" -> Type=3588, Amount=5, Price=1, "Do you want to sell 5 blueberries for %P gold?", Topic=2 +"sell","berry" -> * +"sell","blueberries" -> * +"sell","berries" -> * + +Topic=2,"yes",Count(Type)>=Amount -> "Fine! Here's your gold.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Oh, I'm sorry. I'm not buying less than 5 blueberries." +Topic=2 -> "As you wish." +} diff --git a/app/SabrehavenServer/data/npc/livielle.npc b/app/SabrehavenServer/data/npc/livielle.npc new file mode 100644 index 0000000..754d4f2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/livielle.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Livielle.npc: Datenbank für die Nahrungsmittelhändlerin Livielle + +Name = "Livielle" +Outfit = (138,114-94-132-132-0) +Home = [32982,32036,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ah, 'ello, %N! I can see you're longing for my delicious fruits, chéri." +ADDRESS,"hi$",male,! -> * +ADDRESS,"salut$",male,! -> * +ADDRESS,"hello$",female,! -> "Bienvenue, %N! My fruits will complete the icing on your cake." +ADDRESS,"hi$", female,! -> * +ADDRESS,"salut$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Don't push me, don't push me. One after another, %N." +BUSY,"hi$",! -> * +BUSY,"salut$",! -> * +BUSY,! -> NOP +VANISH,! -> "Aww, I don't even deserve a farewell?" + +"bye" -> "Bon appétit, and come back soon for your daily dose of vitamins!", Idle +"au","revoir" -> "Bon appétit, and come back soon for your daily dose of vitamins!", Idle +"job" -> "Alors, guess what my job might be, standing 'ere in the middle of all these juicy exotic fruits?" +"shop" -> * +"name",male -> "Moi? Livielle for you, chéri. " +"name",female -> "I'm Livielle Delacroix, madame." +"time" -> "Time is %T now." +"help" -> "Oh, for sure will my fruits 'elp you driving off all these nasty diseases and strengthen your immune system!" +"thanks" -> "You're welcome, enjoy." +"thank","you" -> * + +"buy" -> "What's your favorite flavour today? I offer all sorts of exotic fruits." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"fruits" -> "I offer you bananas, melons, pumpkins, white mushrooms, oranges, strawberries, and blueberries." + +"banana" -> Type=3587, Amount=1, Price=5, "Do you want to buy a banana for %P gold?", Topic=1 +"white","mushroom" -> Type=3723, Amount=1, Price=10, "Do you want to buy one of the white mushrooms for %P gold?", Topic=1 +"orange" -> Type=3586, Amount=1, Price=10, "Do you want to buy an orange for %P gold?", Topic=1 +"strawberr" -> Type=3591, Amount=1, Price=2, "Do you want to buy a strawberry for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=10, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"blueberr" -> Type=3588, Amount=1, Price=1, "Do you want to buy a blueberry for %P gold?", Topic=1 +"mango" -> Type=5096, Amount=1, Price=10, "Do you want to buy a mango for %P gold?", Topic=1 +"juice squeezer" -> Type=5865, Amount=1, Price=100, "Do you want to buy a juice squeezer for %P gold?", Topic=1 + +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=5*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=10*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=1 +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=10*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"strawberr" -> Type=3591, Amount=%1, Price=2*%1, "Do you want to buy %A strawberries for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=10*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"blueberr" -> Type=3588, Amount=%1, Price=1*%1, "Do you want to buy %A blueberries for %P gold?", Topic=1 +%1,1<%1,"mango" -> Type=5096, Amount=%1, Price=10*%1, "Do you want to buy %A mangos for %P gold?", Topic=1 +%1,1<%1,"juice squeezer" -> Type=5865, Amount=%1, Price=100*%1, "Do you want to buy %A juice squeezers for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Merci, 'ere you go.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, but that's not enough money, please count again." +Topic=1 -> "You should really prefer my fruits over all this fat meat offered elsewhere. They keep you lithe and lissom." +} diff --git a/app/SabrehavenServer/data/npc/lokur.npc b/app/SabrehavenServer/data/npc/lokur.npc new file mode 100644 index 0000000..ab42f29 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lokur.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lokur.npc: Datenbank für den Postzwerg Lokur + +Name = "Lokur" +Outfit = (160,57-79-98-95-0) +Home = [32647,31904,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N. May I help you?" +ADDRESS,"hi$",! -> "Hiho %N. May I help you?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. One moment please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back if you need my services." + +"bye" -> "Come back if you need my services.", Idle +"farewell" -> * +"job" -> "I am the royal postdwarf and damned proud of it." +"office" -> "It's not big but I like the company." +"name" -> "My name is Lokur Stampsmasher, son of Earth of the Dragoneaters." +"time" -> "Too bad, I forgot my watch at home." +"mail" -> "The mail system was invented by dwarfs! Do you want me to tell you about it?", Topic=1 +#"depot" -> "Just walk towards them and you will find the items you stored in them during your last visit." +"king" -> "Our king has a treasure room and does not need a depot." +"carlin" -> "Imagine, they have a postoffice their too, jawoll." +"thais" -> * + +"kevin" -> "Ah, this human is persistant as a dwarf. A worthy leader indeed, jawoll." +"postner" -> * +"postmasters","guild" -> "The guild keeps things running. Organized and reliable. I appreciate that, jawoll." +"join" -> "Our members are handpicked by Kevin postner in our headquarter." +"headquarter" -> "Its south of kazordoon. Just follow that road, can't miss it." + + +"measurements",QuestValue(235)>0 -> "Ask Kroox about that stuff." +"measurements",QuestValue(234)>0 -> "Come on, I have no clue what they are. Better ask my armorer Kroox for such nonsense. Go and ask him for good ol' Lokurs measurements, he'll know.",SetQuestValue(235,1) + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "So you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "So you want to buy a parcel for %P gold?", Topic=3 + +Topic=1,"yes" -> "The mail system enables you to send and receive letters and parcels. You can buy them here if you want." +Topic=1 -> "Is there anything else, I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/lorbas.npc b/app/SabrehavenServer/data/npc/lorbas.npc new file mode 100644 index 0000000..9cff002 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lorbas.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lorbas.npc: Datenbank für den falschen mönch lorbas + +Name = "Lorbas" +Outfit = (57,0-0-0-0-0) +Home = [32695,32310,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Accept my sincere good wishes." + +"bye" -> "Accept my sincere good wishes.", Idle +"farewell" -> * +"job" -> "I am just a humble monk and responsible to maintain this little outpost that is leftover from our grand order." +"monk" -> "We monks of the humble path feel that we are not worthy to spread the word of the gods. We live in humility and poverty to serve the gods. Most of us have vowed an oath of silence and I humbly took the burden to become the spokesperson." +"order" -> "Our order was once the greatest and richest in the whole known world. Kings, traders and knights of various orders were our supporters and the gods smiled upon us ... or at least that's what we thought until the day of doom." +"time" -> "I own no watch and only a small number of other worldly possessions." +"day","doom" -> "On the day of doom, our dream of building the greatest and most opulent cathedral was shattered." +"shattered" -> "The cathedral was already high and impressive, the order had started to move in although there was still much left to be done. Then the great earthquake came." +"earthquake" -> "Some say it was just the unstable ground or volcanic activity, some even claim it was the work of demons, but we know it was the will of the gods to punish our vanity." +"vanity" -> "In our vanity we thought that we could impress the gods with our money and show piety by building them a monument. ...", + "We were wrong and the gods punished us by sending the worst earthquake that mankind has seen. ...", + "Its ground motions could still be felt in Thais and as the dust settled, little had remained of that what we had built. ...", + "Most members of our order were dead, others turned mad or lost faith. We are all that is left from our glorious order." +"cathedral" ->"What was once planned as the most impressive cathedral of all times, lies now in ruins. ...", + "All what the earthquake has left over is a heap of rubble. The ruins are cursed and everybody who dares to go there will draw the ire of heaven on himself. ...", + "All those that travel there are infested with bad luck. But only few have returned from this treacherous ground. Noxious fumes are killing intruders almost unnoticed. ...", + "Crumbling structures might kill you instantly. ...", + "Those who survive the dangers of nature will face the soul-eating ghosts of those who have died in the catastrophe. ...", + "It's not worth to go there, there are no richnesses or treasures left in the ruins, the gold of our order melted away in funding the cathedral's construction. ...", + "I urge you to stay away from the cursed ground and the ruins. For the safety of your body and your soul keep away from there." +"king" -> "The king is a worldly ruler, and we don't burden ourselves with worldly concerns anymore." +"venore" -> "The gracious tradesmen from Venore send us provisions from time to time." +"thais" -> "Thais is far and we have little contact with the kingdom's capital." +"carlin" -> "We have no relations with that town." +"edron" -> "I hope the knightly order there fares better than our own." +"gods" -> "I am not worthy to speak about the gods." + +"tibia" -> "The world is in the hand of the gods." + +"kazordoon" -> "The dwarves are far from being humble. At least this ancient folk knows that there is nothing to gain in the cathedral's ruins and their treasure hunters stay away from there." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "They will have to learn on their own." +"elves" -> * +"elfs" -> * +"darama" -> "Another continent that has to be seen as a present of the gods to us." +"darashia" -> "I will not judge those people." +"ankrahmun" -> "I am not the right person to discuss this subject." +"ferumbras" -> "He will discover where his path will lead him to. But no matter how ruthless he is, even he stays away from the ruins of the cathedral." +"excalibug" -> "It is rumoured to be hidden somewhere beneath Edron." +"assassin" -> "I know nothing about that topic. If you would excuse me, I have things to attend.",idle +"dark","monk" -> * +} diff --git a/app/SabrehavenServer/data/npc/lorek.npc b/app/SabrehavenServer/data/npc/lorek.npc new file mode 100644 index 0000000..da0c357 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lorek.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Lorek.npc: Datenbank für den Fährman Lorek + +Name = "Lorek" +Outfit = (132,19-10-38-95-0) +Home = [32679,32775,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Just wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am a ferryman. If you want me to transport you to the other end of the city, feel free to ask me for a passage." +"name" -> "I am Lorek." +"time" -> "I have no idea." +"king" -> "I wonder if he will inspect our colony some day." +"venore" -> "It seems the traders are incredibly rich." +"thais" -> "I left Thais for the opportunities that might be found here." +"carlin" -> "I am not sure if we are at war with them. I think they defy the rule of our king." +"edron" -> "Edron has to be very pretty and all people there are rich and such." +"jungle" -> "I can only hope that the guards protect us all from those dangerous beasts out there." + +"tibia" -> "The world is so big that it often scares me." + +"kazordoon" -> "I overheard the dwarves talking about it. I have no idea what it is though." +"dwarves" -> "There are some dwarves living here." +"dwarfs" -> * +"ab'dendriel" -> "What?" +"elves" -> "I only heard of them, but I never saw one. It's said that they have funny ears." +"elfs" -> * +"darama" -> "If more people move to Darama, I might get a better job and earn a fortune." +"darashia" -> "Another human settlement on this continent. It's somewhere in the desert though." +"ankrahmun" -> "They say it's a city full of undead and half-dead people. What a horrible thought!" +"ferumbras" -> "I heard he is some scary magician or so." +"excalibug" -> "What's that?" +"apes" -> "If only the guards could stop their constant attacks." +"lizard" -> "I have only heard about them. I hope they won't come here." +"dworcs" -> "Those little greenskins are more dangerous than a cobra." + + +"trip" -> "I can bring you either to the centre of Port Hope or to the west end of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"cent" -> Price=7, "Do you seek a passage to the centre of Port Hope for %P gold?", Topic=1 +"west" -> Price=7, "Do you seek a passage to the west end of Port Hope for %P gold?", Topic=2 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + +#"trip" -> Price=7, "Would you like to travel to the other end of Port Hope or to the centre of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * + +#Topic=1,"end",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"centre",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"end",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +#Topic=1,"end",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"centre",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +#Topic=1,"centre",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "HELLO? Anyone in there? I was asking you WHERE you want to travel!" + +} diff --git a/app/SabrehavenServer/data/npc/loria.npc b/app/SabrehavenServer/data/npc/loria.npc new file mode 100644 index 0000000..4a43336 --- /dev/null +++ b/app/SabrehavenServer/data/npc/loria.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# loria.npc: Datenbank für die Zauberkundige Loria + +Name = "Loria" +Outfit = (138,96-118-82-95-0) +Home = [32385,32131,7] +Radius = 10 + +Behaviour = { +ADDRESS,"hello","loria",! -> "Welcome %N, my friend." +ADDRESS,"hi","loria",! -> * +ADDRESS,"hello",! -> "Welcome %N." +ADDRESS,"hi",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hope to see you again." + +"bye" -> "May the magic be with you, %N.", Idle +"farewell" -> * +"job" -> "I am studying the power of magic all the time." +"lake" -> "I hope you like it. It is named like my master, Alatar, the Sage" +"name" -> "I am Loria, a former apprentice of Alatar, the Sage." +"alatar" -> "Well, he was my great master. He taught me all these fantastic things about magic. I really miss him." +"time" -> "Time means nothing to me." +"buy" -> "I don't care for money, I care for magic." +"power" -> "Although your attack spells get stronger with your usage of magic, real power is gained by finding strategies to properly use your magic abilities." +"mana" -> "Mana is the source of all magic. If you use spells, it will drain mana from your energy pool. This mana regenerates slowly, if you eat, or if you drink those mana fluids you can buy at Xodet's." +"gorn" -> "He runs an equipment shop close to the north gate of the city." +"xodet" -> "He runs a magic shop in the main road." +"praise","alatar" -> "I praise my master Alatar." + +"kill" -> "Killing and destruction are just foolish steaps to entrophy." + +"quest" -> "I heard from a mystic bone of the lich lord below the House of Necromant. Bring it to me, and you will receive a reward." +"crystal" -> "The mystic crystal should be able to resurrect fresh corpses." +"necromant" -> "He lived in a lonely house in the south eastern part of Tibia beyond the mountains." +"reward" -> "I'll teach you a very seldom spell.", Topic=4 +Topic=4,"spell" -> "I'll teach you 'exevo gran mas vis', but bring me this bone first!" + +"magic",Knight -> "I could tell you much about all sorcerer spells, but you won't understand it. Anyway, feel free to ask me." +"magic" -> "Oh, I can tell you a lot about all sorcerer spells. Feel free to ask me." +"spell" -> "Oh, I can tell you a lot about all sorcerer spells. Feel free to ask me." +"rune" -> "All spells starting with the syllable 'ad' must be burned into a rune. For this buy a rune from Xodet and put it in one of your hands. Now cast the formula of the spell." +"Muriel" -> "He runs his magic shop in the southwest of the city. He sells runes and spells and helps you, if you want to become a sorcerer." +"find","person" -> "If you search someone, this spell will give you an idea of the direction you must head. You will be able to see, whether he is below or above you." +"light" -> "A ray of light will emerge from your flat hand to illuminate your environment." +"light","healing" -> "The paths to the next temple are long. Even in Tibia. So learn this spell, and be able to heal yourself during your travels. This spell will only cure small wounds, but it is pretty helpful." +"light","magic","missile" -> "You can activate this spell by pointing your index finger in the direction of your enemy, conjure the power of your rune and shoot the magic missiles in your enemy's body." +"antidote" -> "This spell sucks the venom out of your veins, that some enemy might have injected." +"intense","healing" -> "This spell will cure more wounds or greater ones at once. This is of course more 'mana intensive', but everybody will sooner or later get in a situation where mana is nothing - compared to life." +"poison","field" -> "This spell will create a single field of poisonous gas. Cast it on a creature you were not able to arrange a peace treaty with. If it has no antidote, watch what could happen if you forget yours." +"great","light" -> "This spell will illuminate your whole screen and last longer than 'light'. Use it in deep dungeons, 'cause behind every corner there could be your last enemy ... and you might just walk into him." +"fire","field" -> "This spell acts similar to the 'poison field' spell, except that you create fire instead of poisonous gas. Don't enter it yourself or you will realize why it is said that 'fire eats everything'." +"heavy","magic","missile" -> "Remember the spell where you only got to wave your hand? Well, wave it twice and shoot a heavy magic missile at your enemy. This spell will create a rune with five charges." +"magic","shield" -> "Well, mages are more bookworms than sportsmen, and as such often neglect their physical fitness. In ancient tomes lies the power to use mana as an equivalent to life. So use this spell to survive." +"fireball" -> "A perfect symbiosis of fire and wind. More is not to be said about this spell. Use this fireball as a warning or as your defence, but don't burn your fingers." +"energy","field" -> "This one will create a field of energy. Everyone stepping in will be struck from lightning. This field will not last as long as poison or fire fields, but it is more deadly." +"destroy","field" -> "Trapped again between fire, poison and energy fields? This spell will give you the ability to destruct the fields, so you can pass safely." +"fire","wave" -> "Turn to you opponent and release the forces of nature with a whisper of your voice. A triangle of fire will burn all persons in your view, so take care, in which direction you look!" +"ultimate","healing" -> "This spell is able to cure almost every injury at a higher cost than the other healing spells." +"great","fireball" -> "Imagine scaling the normal fireball by two and raising the fire temperature." +"firebomb" -> "With a snip of your finger you can cover the floor with a burning carpet that keeps on burning for a while." +"fire","bomb" -> "With a snip of your finger you can cover the floor with a burning carpet that keeps on burning for a while." +"energybeam" -> "A ray of energy will strike everyone in your current direction" +"creature","illusion" -> "A good one to scare childs. You can change your appearance to any monster. You can be as handsome as a ghoul!" +"poison","wall" -> "With this one you can create a huge wall of poisonous gas. Many monsters will be too scared to pass the wall and if they do, they will choke from nausea." +"explosion" -> "A strong blast of fire wounds the opponent you point at, and the adjacent squares." +"fire","wall" -> "As the poison wall, this spell creates an even larger wall of fire, burning everyone who passes." +"great","energy","beam" -> "A lightning bolt strikes the point you look at." +"invisible" -> "This spell drains the colors out of you body, making yourself invisible for an hour or two." +"summon","creature" -> "This one gives you the ability to summon monsters that aid you in your battles." +"energy","wall" -> "Attracts lightning bolts from the sky, to form a giant wall, seriously damaging everyone who passes." +"energy","wave" -> "Shoots a triangular bundle of lightning bolts in the direction you look." +"sudden","death" -> "The best spell for deciding a battle within seconds. The spell tries to interrupt the opponents heart beat, leading to his instant death in most cases." + +"formula" -> "Which is the spell, you need the formula to?", Topic=1 +Topic=1,"find","person" -> "Say the words: exiva 'name'" +Topic=1,"light" -> "Say the words: utevo lux" +Topic=1,"light","healing" -> "Say the word: exura" +Topic=1,"light","magic","missile" -> "Say the word: adori" +Topic=1,"antidote" -> "Say the words: exana pox" +Topic=1,"intense","healing" -> "Say the words: exura gran" +Topic=1,"poison","field" -> "Say the words: adevo grav pox" +Topic=1,"great","light" -> "Say the words: utevo gran lux" +Topic=1,"fire","field" -> "Say the words: adevo grav flam" +Topic=1,"heavy","magic","missile" -> "Say the words: adori gran" +Topic=1,"magic","shield" -> "Say the words: utamo vita" +Topic=1,"fireball" -> "Say the words: adori flam" +Topic=1,"energy","field" -> "Say the words: adevo grav vis" +Topic=1,"destroy","field" -> "Say the words: adito grav" +Topic=1,"fire","wave" -> "Say the words: exevo flam hur" +Topic=1,"ultimate","healing" -> "Say the words: exura vita" +Topic=1,"great","fireball" -> "Say the words: adori gran flam" +Topic=1,"fire","bomb" -> "Say the words: adevo mas flam" +Topic=1,"firebomb" -> "Say the words: adevo mas flam" +Topic=1,"energy","beam" -> "Say the words: exevo vis lux" +Topic=1,"creature","illusion" -> "Say the words: utevo res ina 'creature'" +Topic=1,"poison","wall" -> "Say the words: adevo mas grav pox" +Topic=1,"explosion" -> "Say the words: adevo mas hur" +Topic=1,"fire","wall" -> "Say the words: adevo mas grav flam" +Topic=1,"great","energy","beam" -> "Say the words: exevo gran vis lux" +Topic=1,"invisible" -> "Say the words: utana vid" +Topic=1,"summon","creature" -> "Say the words: utevo res 'creature'" +Topic=1,"energy","wall" -> "Say the words: adevo mas grav vis" +Topic=1,"energy","wave" -> "Say the words: exevo mort hur" +Topic=1,"sudden","death" -> "Say the words: adori vita vis" +} diff --git a/app/SabrehavenServer/data/npc/loui.npc b/app/SabrehavenServer/data/npc/loui.npc new file mode 100644 index 0000000..e31b979 --- /dev/null +++ b/app/SabrehavenServer/data/npc/loui.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Loui.npc: Datenbank für Loui den ängstlichen Mönch + +Name = "Loui" +Outfit = (57,0-0-0-0-0) +Home = [32014,32190,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "BEWARE! Beware of that hole!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, you may listen to the frightening story I am telling!", Queue +BUSY,"hi$",! -> * + +BUSY,! -> NOP +VANISH,! -> "STAY AWAY FROM THAT HOLE!" + +"bye" -> "May the gods protect you! And stay away from that hole!", Idle +"farewell" -> * +"job" -> "I am a monk, collecting healing herbs." +"name" -> "My name is Loui." +"monk" -> "I am a humble servant of the gods." +"tibia" -> "Everything around us, that is Tibia." +"rookgaard" -> "This is the place where everything starts." +"god" -> "They created Tibia and all lifeforms. Talk to other monks and priests to learn more about them." +"life" -> "The gods blessed Tibia with abundant forms of life." +"herb" -> "I was looking for some herbs as I foolishly entered this unholy hole." +"obi" -> "He owns a shop in the town." +"al","dee" -> "He owns a shop in the town." +"seymour" -> "Seymour is the headmaster of the local academy." +"academy" -> "Most adventurers take their first steps there." +"willie" -> "The gods may protect me from his foul language." +"monster" -> "There must be an army of them, just down this hole." +"rabbit" -> "So it must have been some magic wielding beasts using creature illusion. Good thing you escaped." + +"quest" -> "I have no quests but to stay away from that hole and I'd recomend you to do the same." +"task" -> * + +"gold" -> "I am pennyless and poor as it is fit for a humble monk like me." +"money" -> * +"rat" -> "The good thing is, those horrible rats stay in the town mostly. The bad thing is, they do so because outside the bigger Monsters devour them." +"hole" -> "While looking for herbs I found that hole. I went down though I had no torch. And then I heard THEM! There must be dozens!" +"story" -> * +"them" -> "They were so many, EVERYWHERE! I could barely escape alive. I have no clue what THEY were but one more second down there and I'd be dead!" + +"heal" -> "Sorry I am out of mana and ingredients, please visit Cipfried in the town." +"time" -> "Now, it is %T, my child." + + +} diff --git a/app/SabrehavenServer/data/npc/lubo.npc b/app/SabrehavenServer/data/npc/lubo.npc new file mode 100644 index 0000000..a33026a --- /dev/null +++ b/app/SabrehavenServer/data/npc/lubo.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lubo.npc: Datenbank fuer Lubo, den Haendler im Abenteurer-Laden + +Name = "Lubo" +Outfit = (128,115-39-96-118-3) +Home = [32488,32119,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my adventurer shop, %N! What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment for adventurers. Do you need anything?" +"name" -> "I am Lubo, the owner of this shop." +"time" -> "It is exactly %T." +"mountain" -> "It is said that once there lived a great magician on the top of this mountain." +"magician" -> "I don't remember his name, but it's said that his banner was the black eye." +"food" -> "I sell the best apples in Tibia." +"map" -> "Oh! I'm sorry, I sold the last one just five minutes ago." +"magic" -> "There's a lot of magic flowing in the mountain to the north." +"weapon" -> "If you want to buy weapons, you'll have to go to a town or city." +"dog" -> "This is Ruffy my dog, please don't do him any harm." +"pet" -> "There are some strange stories about a magicians pet names. Ask Hoggle about it." +"finger" -> "Oh, you sure mean this old story about the mage Dago, who lost two fingers when he conjured a dragon." + +"inn" -> "Frodo runs a nice inn in the near town Thais." +"crunor","cottage" -> "Ah yes, I remember my grandfather talking about that name. This house used to be an inn a long time ago. My family bought it from some of these flower guys." +"flower","guy" -> "Oh, I mean druids of course. They sold the cottage to my family after some of them died in an accident or something like that." +"accident" -> "As far as I can remember the story, a pet escaped its stable behind the inn. It got somehow involved with powerfull magic at a ritual and was transformed in some way." +"stable",QuestValue(211)=3 -> "My grandpa told me, in the old days there were some behind this cottage. Nothing big though, just small ones, for chicken or rabbits.",SetQuestValue(211,4) +"stable",QuestValue(211)<3 -> "Sorry speak louder I can't hear you." + +"equipment" -> "I sell torches, fishing rods, sixpacks of worms, ropes, water hoses, backpacks, apples, and maps." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=3, "Do you want to buy a torch for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=60, "Do you want to buy a rope for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you want to buy a water hose for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=25, "Do you want to buy a backpack for %P gold?", Topic=1 +"fishing","rod" -> Type=3483, Amount=1, Price=175, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold? I know its rather expensive, but I must protect people from thieves.", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 +%1,"torch" -> Type=2920, Amount=%1, Price=3*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +"addon",QuestValue(18502)=0,premium -> "Sorry, the backpack I wear is not for sale. It's handmade from rare minotaur leather.", Topic=4 +"backpack",QuestValue(18502)=0,premium -> * +"addon",QuestValue(18502)=0 -> "Sorry, the backpack I wear is not for sale." +"backpack",QuestValue(18502)=0 -> * +Topic=4,"minotaur","leather" -> "Well, if you really like this backpack, I could make one for you, but minotaur leather is hard to come by these days. Are you willing to put some work into this?", Topic=5 +Topic=5,"yes" -> "Alright then, if you bring me 100 pieces of fine minotaur leather I will see what I can do for you. You probably have to kill really many minotaurs though...", + "so good luck!", SetQuestValue(18502,1), SetQuestValue(17594,1) + +"addon",QuestValue(18502)=1 -> Type=5878, Amount=100, "Ah, right, almost forgot about the backpack! Have you brought me 100 pieces of minotaur leather as requested?", Topic=6 +"backpack",QuestValue(18502)=1 -> * +Topic=6,"yes",Count(Type)>=Amount -> "Great! Alright, I need a while to finish this backpack for you. Come ask me later, okay?", Delete(Type), SetExpiringQuestValue(18503, 7200000), SetQuestValue(18502,2) +Topic=6,"yes" -> "You don't have that many!" +Topic=6 -> "Too bad." + +"addon",ExpiringQuestValue(18503)>0 -> "Uh... I didn't expect you to return that early. Sorry, but I'm not finished yet with your backpack. I'm doing the best I can, promised." +"backpack",ExpiringQuestValue(18503)>0 -> * + +"addon",ExpiringQuestValue(18503)<0,QuestValue(18502)=2 -> "Just in time! Your backpack is finished. Here you go, I hope you like it.", SetQuestValue(18502,3), AddOutfitAddon(136,1), AddOutfitAddon(128,1), EffectOpp(13) +"backpack",ExpiringQuestValue(18503)<0,QuestValue(18502)=2 -> * + +"addon",QuestValue(18502)=3 -> "Oh, you also have a nice backpack just like me!" +"backpack",QuestValue(18502)=3 -> * +} diff --git a/app/SabrehavenServer/data/npc/lugri.npc b/app/SabrehavenServer/data/npc/lugri.npc new file mode 100644 index 0000000..81b7846 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lugri.npc @@ -0,0 +1,141 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lugri.npc: Datenbank fuer den Zathrothpriester Lugri + +Name = "Lugri" +Outfit = (9,0-0-0-0-0) +Home = [32389,32118,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "What do you want, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May darkness be with you!" + +"bye" -> "Leave now, %N. The gods of darkness will watch your soul!", Idle +"farewell" -> * +"job" -> "I am a priest of Zathroth, the bringer of dark secrets." +"name" -> "My name is Lugri." +"news" -> "You will soon see the 'news' with your own eyes. " +"tibia" -> "The world of Tibia is to be taken by the strongest." +"how","are","you"-> "I feel the power of evil rising and enjoy that." +"sell" -> "I am in the death business. You wouldn't like what I have to offer." +"god$" -> "The gods of darkness give us the chance to reach our whole potentials, the gods of good want to capture us in eternal stasis!", Topic=2 +"gods$" -> * +"life" -> "Life is war. It's about survival of the fittest." +"citizen" -> "The people of Tibia are sheep, so be smart and strong enough to become their wolf." +"people" -> * +"king" -> "This puny king is no threat for our master's plans." +"monster" -> "They are a challenge to sift the chaff from the wheat." +"quest" -> "Aren't we all on a quest for survival and supremacy?" +"mission" -> * +"survival" -> * +"supermacy" -> * +"gold" -> Price=30, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "Life is an eternal fight!" +"slay" -> "The weak have to be slain by the strong!" +"heal" -> "Your wounds are your problem, not mine." +"help" -> "If you cant help yourself you are not worth of my assistance." +"ferumbras" -> "He is one of Zathroth's strongest followers and wields special powers, given to him by the dark one." +"time" -> "Who cares?" +"excalibug" -> "It's existence is just a lie to inspire hope and bravery in the hearts of the followers of good." + +Topic=2,"good" -> "The so called gods of good are Fardos, Uman, the elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +Topic=2,"light" -> "The so called gods of good are Fardos, Uman, the elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator. He is a helpless watcher whose 'creation' is far more then he bargained for." +"uman" -> "Uman is a jealous keeper of magic. He gives only little knowledge to the mortals." +"suon" -> "Suon is one of the suns of our world. He gives his light mindlessly to the weak and the strong alike." +"crunor" -> "Crunor is a plantgod ... and plants exist to be stomped over." +"nornur" -> "Nornur fancies himself as god of fate without even understanding the ways of fate at all." +"bastesh" -> "Bastesh is so afraid that she hides in the depth of the seas." +"kirok" -> "Kirok, the mad one, is the patron of scientists and jesters, more a nuisance than a god." +"toth" -> "Toth is just the undertaker for the other 'gods of good'." +"banor" -> "Banor isn't a god at all, but one of their tools. It is stupidity to worship a tool, isn't it?" +"tibiasula" -> "Zathroth took her life, recoginzig it was necessary for the process of creation." +Topic=2,"tibia" -> "Tibia is just the mindless elemental power of earth." +"sula" -> "Sula is just the mindless elemental power of water." +"air" -> "Air is is just a mindless elemental force." +"fire" -> "Fire is is just a mindless elemental force." + +Topic=2,"evil" -> "The glorious gods of darkness are Zathroth, Fafnar, Brog, Urgith, and the Archdemons." +Topic=2,"darkness"-> "The glorious gods of darkness are Zathroth, Fafnar, Brog, Urgith, and the Archdemons." +"zathroth" -> "Zathroth represents the true and unbound power of magic. He is the keeper of great secrets." +"fafnar" -> "Fafnar is the power of the sun. She burns the weak to ashes." +"brog" -> "Brog, the raging one, the great destroyer, the berserk of darkness ... call him how you like, but fear his awesome power." +"urgith" -> "Urgith is the master of the undead. The bonemaster also takes care of the damned souls." +"archdemons" -> "The demons are powerful followers of Zathroth. Their leaders are known as the ruthless seven." +"ruthless","seven"-> "Infernatil, Pumin, Verminor, Tafariel, Apocalypse, Bazir and Ashfalor." +"tafariel" -> "She is the mistress of the damned! Rewarding or torturing, it is the same for her victims!" +"apocalypse" -> "It is said even speaking its TRUE name will bring total destruction to you!" +"pumin" -> "He is the lord of despair." +"infernatil" -> "The incendiary of hell." +"bazir" -> "He is the great deciver, the lord of lies." +"Verminor" -> "Ah, the plaguelord." +"ashfalor" -> "The right hand of Urgith. The general of the undead hordes." +"pits","inferno" -> "After the ruthless seven conquered it, it's again a holy place for the followers of the dark path." +"nightmare","pits"-> "That name is a disgrace. The pityful nightmare knights couldn't defend them and even lost the treasure of their order there." +"goshnar" -> "The necromant king was only defeated by the nightmare knights due to a bad twist of fate." +"necromant","nectar"-> "That's none of your business!" + +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes" -> "Don't be ashamed but you lack the gold." +Topic=1 -> "As you wish." + +"death","to","noodles" -> Type=3061, Amount=1, "So, I guess you bring me a magic crystal?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Fine. Now you get what you deserve, you fool! DIE IN AGONY!", Burning(25,25), EffectOpp(6), EffectMe(14), Delete(Type), Idle + +"addon",QuestValue(17561)>4,male -> "This skull shows that you are a true follower of Zathroth and the glorious gods of darkness." +"outfit",QuestValue(17561)>4,male -> * +"addon",QuestValue(17561)>4,female -> "This tiara shows that you are a true follower of Zathroth and the glorious gods of darkness." +"outfit",QuestValue(17561)>4,female -> * + +"mission",QuestValue(17561)=0,male -> "This skull shows that you are a true follower of Zathroth and the glorious gods of darkness. Are you willing to prove your loyalty?", Topic=5 +"task",QuestValue(17561)=0,male -> * +"outfit",QuestValue(17561)=0,male -> * +"addon",QuestValue(17561)=0,male -> * +"mission",QuestValue(17561)=0,female -> "This tiara shows that you are a true follower of Zathroth and the glorious gods of darkness. Are you willing to prove your loyalty?", Topic=5 +"task",QuestValue(17561)=0,female -> * +"outfit",QuestValue(17561)=0,female -> * +"addon",QuestValue(17561)=0,female -> * + +Topic=5,"yes" -> "It will be a hard task which requires many sacrifices. Do you still want to proceed?", Topic=6 +Topic=5 -> "As you wish." +Topic=6,"yes" -> "Good decision, %N. Your first sacrifice will be a medusa shield. Bring it to me and do give it happily.", SetQuestValue(17561,1), SetQuestValue(17594,1) +Topic=6 -> "As you wish." + +"medusa","shield",QuestValue(17561)=1 -> Type=3436, Amount=1, "Is it your true wish to sacrifice a medusa shield to Zathroth?", Topic=7 +"mission",QuestValue(17561)=1 -> * +"task",QuestValue(17561)=1 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Good. I accept your sacrifice. The second sacrifice I require from you is a dragon scale mail. Bring it to me and do give it happily.", Delete(Type), SetQuestValue(17561,2) +Topic=7,"yes" -> "Don't be ashamed but you don't have it." +Topic=7 -> "As you wish." + +"dragon","scale","mail",QuestValue(17561)=2 -> Type=3386, Amount=1, "Is it your true wish to sacrifice a dragon scale mail to Zathroth?", Topic=8 +"mission",QuestValue(17561)=2 -> * +"task",QuestValue(17561)=2 -> * +Topic=8,"yes",Count(Type)>=Amount -> "Good. I accept your sacrifice. The third sacrifice I require from you are crown legs. Bring them to me and do give them happily.", Delete(Type), SetQuestValue(17561,3) +Topic=8,"yes" -> "Don't be ashamed but you don't have it." +Topic=8 -> "As you wish." + +"crown","legs",QuestValue(17561)=3 -> Type=3382, Amount=1, "Is it your true wish to sacrifice crown legs to Zathroth?", Topic=9 +"mission",QuestValue(17561)=3 -> * +"task",QuestValue(17561)=3 -> * +Topic=9,"yes",Count(Type)>=Amount -> "Good. I accept your sacrifice. The last sacrifice I require from you is a ring of the sky. Bring it to me and do give it happily.", Delete(Type), SetQuestValue(17561,4) +Topic=9,"yes" -> "Don't be ashamed but you don't have it." +Topic=9 -> "As you wish." + +"ring","of","the","sky",QuestValue(17561)=4 -> Type=3006, Amount=1, "Is it your true wish to sacrifice a ring of the sky to Zathroth?", Topic=10 +"mission",QuestValue(17561)=4 -> * +"task",QuestValue(17561)=4 -> * +Topic=10,"yes",Count(Type)>=Amount -> "Good. I accept your sacrifice. You have proven that you are a true follower of Zathroth and do not hesitate to sacrifice worldly goods. Thus, I will reward you with this headgear.", Delete(Type), SetQuestValue(17561,5), AddOutfitAddon(149,2), AddOutfitAddon(145,2), EffectOpp(13) +Topic=10,"yes" -> "Don't be ashamed but you don't have it." +Topic=10 -> "As you wish." + +"mission",QuestValue(17561)>4 -> "%N follower of Zathroth I have no more tasks for you." +"task",QuestValue(17561)>4 -> * +} diff --git a/app/SabrehavenServer/data/npc/luna.npc b/app/SabrehavenServer/data/npc/luna.npc new file mode 100644 index 0000000..0d410c6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/luna.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# luna.npc: Datenbank für die Kräuterhändlerin Luna + +Name = "Luna" +Outfit = (137,0-118-100-115-0) +Home = [33254,31840,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I'm too busy now." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye, traveller." + +"bye" -> "Goodbye, traveller.", Idle +"name" -> "I'm called Luna." +"job" -> "I sell various herbs, mushrooms, and flowers." +"time" -> "Sorry, I don't know." +"king" -> "I don't know much about the king, sorry." +"tibianus" -> * +"army" -> "I sometimes heal soldiers with my herbal mixtures." +"heal" -> * +"ferumbras" -> "Mentioning his name makes me shiver." +"excalibug" -> "I am not an expert for weapons." +"thais" -> "I prefer the wilderness to cities." +"tibia" -> * +"carlin" -> * +"edron" -> * +"news" -> "I fear I know nothing new that is of any importance." +"rumors" -> * + +"offer" -> "I'm selling various herbs, mushrooms, and flowers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"herbs" -> "I have stone herbs, star herbs, and ferns. What do you want?" +"mushroom" -> "I have white, red, and brown mushrooms. Which one do you want?" +"flowers" -> "I have red roses and tulips. What do you want?" + +"white","mushroom" -> Type=3723, Amount=1, Price=6, "Do you want to buy one of the white mushrooms for %P gold?", Topic=1 +"red","mushroom" -> Type=3724, Amount=1, Price=12, "Do you want to buy one of the red mushrooms for %P gold?", Topic=1 +"brown","mushroom" -> Type=3725, Amount=1, Price=10, "Do you want to buy one of the brown mushrooms for %P gold?", Topic=1 + +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=6*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=1 +%1,1<%1,"red","mushroom" -> Type=3724, Amount=%1, Price=12*%1, "Do you want to buy %A of the red mushrooms for %P gold?", Topic=1 +%1,1<%1,"brown","mushroom" -> Type=3725, Amount=%1, Price=10*%1, "Do you want to buy %A of the brown mushrooms for %P gold?", Topic=1 + + +"rose" -> Type=3658, Amount=1, Price=11, "Do you want to buy a red rose for %P gold?", Topic=1 +"tulip" -> Type=3668, Amount=1, Price=9, "Do you want to buy a tulip for %P gold?", Topic=1 +"stone","herb" -> Type=3735, Amount=1, Price=28, "Do you want to buy a stone herb for %P gold?", Topic=1 +"star","herb" -> Type=3736, Amount=1, Price=21, "Do you want to buy a star herb for %P gold?", Topic=1 +"fern" -> Type=3737, Amount=1, Price=24, "Do you want to buy a fern for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/lungelen.npc b/app/SabrehavenServer/data/npc/lungelen.npc new file mode 100644 index 0000000..0969c73 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lungelen.npc @@ -0,0 +1,17 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lungelen.npc: Datenbank für die Erzmagierin Lungelen + +Name = "Lungelen" +Outfit = (138,77-19-95-115-0) +Home = [32303,32267,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Please don't disturb me, I am very busy in my recent researches. Have a nice day!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> NOP +BUSY,! -> NOP +VANISH,! -> NOP +} diff --git a/app/SabrehavenServer/data/npc/lynda.npc b/app/SabrehavenServer/data/npc/lynda.npc new file mode 100644 index 0000000..3d734c6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lynda.npc @@ -0,0 +1,209 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lynda.npc: Datenbank für die Priesterin Lynda + +Name = "Lynda" +Outfit = (138,79-83-86-114-0) +Home = [32333,32200,7] +Radius = 4 + +Behaviour = { +ADDRESS,male,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours life could bring upon you?", Topic=9 +ADDRESS,female,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours life could bring upon you?", Topic=9 +ADDRESS,"hello$","lynda",! -> "Welcome in the name of the gods, pilgrim %N!" +ADDRESS,"hi$","lynda",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N! Please be patient, my child.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods be with you!" + +"bye" -> "Good bye, %N. May the gods guard you, my child!", Idle +"farewell" -> * +"job" -> "I am a priest of the great pantheon." +"news" -> "Sorry, I had no enlightening visions lately." +"name" -> "My name is Lynda. And the spirits tell me that you are %N." +"tibia" -> "The world of Tibia is the creation of the gods." +"how","are","you"-> "Thank you, I'm fine, the gods are with me." +"sell" -> "The grace of the gods must be earned, not bought!" +"sin$" -> "Do you whish to confess your sins?", Topic=3 +"sins$" -> "Do you whish to confess your sins?", Topic=3 +"god$" -> "The gods of good guard us and guide us, the gods of evil want to destroy us and steal our souls!", Topic=2 +"gods$" -> * +"life" -> "Life is a gift of the gods, honor life and don't destroy it." +"citizen" -> "The things I know about our citizens are confidential." +"lugri" -> "He is a follower of evil. May the gods punish him." +"king" -> "King Tibianus is our benevolent sovereign." +"monster" -> "They are creatures of the gods of evil!" +"quest" -> "It is my mission to spread knowledge about the gods." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "It is MY mission to teach, it is YOUR mission to fight!" +"slay" -> * + +"help",HP<40,! -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0,! -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0,! -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) + +"heal$",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +"ferumbras" -> "He is a favourite of the gods of evil and one of the champions of evil." +"time" -> "Now, it is %T." +"excalibug" -> "This fabled weapon was lost in ancient times. If someone found it, this person would be nearly invincible." + +Topic=2,"good" -> "The gods we call the good ones are Fardos, Uman, the Elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator, the great obsever. He is our caretaker." +"uman" -> "Uman is the positive aspect of magic. He brings us the secrets of the arcane arts." +"suon" -> "Suon is the lifebringing sun. He observes the creation with love." +"crunor" -> "Crunor, the great tree, is the father of all plantlife. He is a prominent god for many druids." +"nornur" -> "Nornur is the mysterious god of fate. Who knows if he is its creator or just a chronist?" +"bastesh" -> "Bastesh, the deep one, is the goddess of the sea and its creatures." +"kirok" -> "Kirok, the mad one, is the god of scientists and jesters." +"toth" -> "Toth, lord of death, is the keeper of the souls, the guardian of the afterlife." +"banor" -> "Banor, the heavenly warrior, is the patron of all fighters against evil. He is the gift of the gods to inspire humanity." +"tibiasula" -> "Tibiasula lost her life, but out of her essence the world was created." + +Topic=2,"tibia" -> "Tibia is the essence of the elemental power of earth." +"sula" -> "Sula is the essence of the elemental power of water." +"air" -> "Air is one of the primal elemental forces, sometimes worshipped by tribal shamans." +"fire" -> "Fire is one of the primal elemental forces, sometimes worshipped by tribal shamans." + +Topic=2, "evil" -> "The gods we call the evil ones are Zathroth, Fafnar, Brog, Urgith, and the Archdemons!" +"zathroth" -> "Zathroth is the destructive aspect of magic. He is the deceiver and the thief of souls." +"fafnar" -> "Fafnar is the scorching sun. She observes the creation with hate and jealousy." +"brog" -> "Brog, the raging one, is the great destroyer. The berserk of darkness." +"urgith" -> "The bonemaster Urgith is the lord of the undead and keeper of the damned souls." +"archdemons" -> "The demons are followers of Zathroth. The cruelest are known as the ruthless seven." +"ruthless", "seven" -> "I dont want to talk about that subject!" + +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes",CountMoney "Dont be ashamed, but you lack the gold." + +Topic=3,"yes" -> "So tell me what shadows your soul, my child.", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and pray for your soul." + +"marriage" -> "You want me to initiate a marriage ceremony?", Topic=5 +"ceremony" -> * +Topic=5,"yes" -> "In the Name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time. Marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence please! I hereby invoke the attention of the eternal powers looking over our souls and lives. May the gods bless us!", EffectMe(13), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours life could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours life could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,male,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further life as a married couple. Go now and celebrate your marriage!", EffectOpp(14), EffectMe(13), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", Idle + +"angelina",QuestValue(17549)=1 -> "Angelina had been imprisoned? My, these are horrible news, but I am so glad to hear that she is safe now. ...", + "I will happily carry out her wish and reward you, but I fear I need some important ingredients for my blessing spell first....", + "Will you gather them for me?", Topic=10 +Topic=10,"yes" -> "Thank you, I promise that your efforts won't be in vain! Listen closely now: First, I need a sample of five druid rods and five sorcerer wands. ...", + "I need a snakebite rod, a moonlight rod, a volcanic rod, a quagmire rod and a hailstorm rod. Then, I need a wand of vortex, a wand of dragonbreath ...", + "a wand of plague, a wand of cosmic energy and a wand of inferno. Please bring them all at once so that their energy will be balanced. ...", + "Secondly, I need 10 ounces of magic sulphur. It can absorb the elemental energy of all the wands and rods and bind it to something else. ...", + "Next, I will need a soul stone. These can be used as a vessel for energy, evil as well as good. They are rarely used nowadays though. ...", + "Lastly, I need a lot of holy energy. I can extract it from ankhs, but only a small amount each time. I will need about 20 ankhs. ...", + "Did you understand everything I told you and will help me with my blessing?", Topic=11 +Topic=10 -> "As you wish." +Topic=11,"yes" -> "Alright then. Come back to with a sample of those five wands and five rods I mentioned, please.", SetQuestValue(17549,2) +Topic=11 -> "As you wish." + +"sample",QuestValue(17549)=2 -> "Did you bring a sample of the wands and rods with you?", Topic=12 +"wand",QuestValue(17549)=2 -> * +"rod",QuestValue(17549)=2 -> * +Topic=12,"yes",Count(3074)>=1,Count(3075)>=1,Count(3072)>=1,Count(3073)>=1,Count(3071)>=1,Count(3066)>=1,Count(3070)>=1,Count(3069)>=1,Count(3065)>=1,Count(3067)>=1 -> "Thank you, that must have been a lot to carry. Now, please bring me 10 ounces of magic sulphur.", DeleteAmount(3074, 1), DeleteAmount(3075, 1), DeleteAmount(3072, 1), DeleteAmount(3073, 1), DeleteAmount(3071, 1), DeleteAmount(3066, 1), DeleteAmount(3070, 1), DeleteAmount(3065, 1), DeleteAmount(3067, 1), DeleteAmount(3069, 1), SetQuestValue(17549,3) +Topic=12,"yes" -> "You don't have that many." +Topic=12 -> "Maybe another time." + +"magic","sulphur",QuestValue(17549)=3 -> Type=5904, Amount=10, "Did you obtain 10 ounces of magic sulphur?", Topic=13 +"mission",QuestValue(17549)=3 -> * +"task",QuestValue(17549)=3 -> * +Topic=13,"yes",Count(Type)>=Amount -> "Very good. I will immediately start to prepare the ritual and extract the elemental energy from the wands and rods. Please bring me the Necromancer's soul stone now.", Delete(Type), SetQuestValue(17549,4) +Topic=13,"yes" -> "You don't have that many." +Topic=13 -> "Maybe another time." + +"soul","stone",QuestValue(17549)=4 -> Type=5809, Amount=1, "Were you actually able to retrieve the Necromancer's soul stone?", Topic=14 +"mission",QuestValue(17549)=4 -> * +"task",QuestValue(17549)=4 -> * +Topic=14,"yes",Count(Type)>=Amount -> "You have found a rarity there, %N. This will become the tip of your blessed wand. Please bring me 20 ankhs now to complete the ritual.", Delete(Type), SetQuestValue(17549,5) +Topic=14,"yes" -> "You don't have it." +Topic=14 -> "Maybe another time." + +"ankh",QuestValue(17549)=5 -> Type=3077, Amount=20, "Am I sensing enough holy energy from ankhs here?", Topic=15 +"mission",QuestValue(17549)=5 -> * +"task",QuestValue(17549)=5 -> * +Topic=15,"yes",Count(Type)>=Amount -> "The ingredients for the ritual are complete! I will start to prepare your blessed wand, but I have to medidate first. Please come back later to hear how the ritual went.", Delete(Type), SetQuestValue(17549,6),SetExpiringQuestValue(17550, 10800000) +Topic=15,"yes" -> "You don't have 20 of them." +Topic=15 -> "Maybe another time." + +"addon",ExpiringQuestValue(17550)>0 -> "Please let me focus for a while, %N." +"ritual",ExpiringQuestValue(17550)>0 -> * + +"addon",ExpiringQuestValue(17550)<0,QuestValue(17549)=6 -> "I'm glad to tell you that I have finished the ritual, %N. Here is your new wand. I hope you carry it proudly for everyone to see.", SetQuestValue(17549,7), AddOutfitAddon(141,1), AddOutfitAddon(130,1), EffectOpp(13) +"ritual",ExpiringQuestValue(17550)<0,QuestValue(17549)=6 -> * + +"addon",QuestValue(17549)=7 -> "Wear your wand proudly for everyone to see!" +"ritual",QuestValue(17549)=7 -> * +} diff --git a/app/SabrehavenServer/data/npc/lyonel.npc b/app/SabrehavenServer/data/npc/lyonel.npc new file mode 100644 index 0000000..beb9d23 --- /dev/null +++ b/app/SabrehavenServer/data/npc/lyonel.npc @@ -0,0 +1,40 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Lyonel" +Outfit = (151,38-58-22-95-0) +Home = [32298,32832,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, my friend. Make yourself comfortable and have some food and rum." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You're served soon, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Someone must have been in a hurry." + +"bye" -> "Good bye and come again.", Idle +"farewell" -> * + +"buy" -> "Food and rum as much as you can pay for." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "I can offer you bread, cheese, ham, or meat." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +"rum" -> Type=5552, Data=13, Amount=1, Price=150, "Do you want to buy a flask of rum for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here is what you ordered.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Where's your money then?", Idle +Topic=1 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/maealil.npc b/app/SabrehavenServer/data/npc/maealil.npc new file mode 100644 index 0000000..641e43c --- /dev/null +++ b/app/SabrehavenServer/data/npc/maealil.npc @@ -0,0 +1,142 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maealil.npc: Datenbank für die Mystikerin Maealil (Elfenstadt) + +Name = "Maealil" +Outfit = (63,0-0-0-0-0) +Home = [32732,31631,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, please wait a moment %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I am a mystic." +"name" -> "I am known as Maealil." +"time" -> "I don't own one of those little machines." +"mystic" -> "I am a philosopher and healer." + +"elves" -> "We are an ancient race, abandoned by the gods and doomed to find our way alone." +"dwarfs" -> "They cultivate earth but don't understand it." +"humans" -> "They are somewhat orcish in their nature." +"troll" -> "I don't think it's a good idea to keep servants." + +"cenath" -> "My parents were Deraisim but joined the Cenath caste before my birth." +"kuridai" -> "I hope they don't do something foolish one day." +"deraisim" -> "Unfortunately they are to busy to care for the finer things in life." +"abdaisim" -> "They should join our town for their and our own safety." +"teshial" -> "I would love to learn more about the Teshial." +"dream" -> * +"ferumbras" -> "Only another servant of evil." +"crunor" -> "The great tree is the beginning for all things living and Priyla helps us to understand that." +"priyla" -> "The daughter of the stars gives us knowledge and teaches us magic." + +"excalibug" -> "Is that a new kind of bug the Deraisim found?" +"news" -> "I don't know anything of importance." + +"magic" -> "I can heal you or even teach you some spells of healing." +"druid" -> "Druids are great healers." +"sorcerer" -> "They understand so few..." +"spellbook" -> "I have none here." +"spell" -> "I teach the spells 'Light Healing', 'Antidote', 'Antidote Rune', 'Intense Healing', 'Intense Healing Rune, 'Ultimate Healing', and 'Ultimate Healing Rune'." + +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 + +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 + +"light","healing" -> "I'm sorry, but this spell is only for druids and paladins." +"antidote" -> * +"intense","healing" -> * +"ultimate","healing" -> * +"intense","healing","rune" -> "I'm sorry, but this spell is only for druids." +"antidote","rune" -> * +"ultimate","healing","rune" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + + +"blessing",PvPEnforced -> "The lifforce of this world is wannig. There are no more blessings avaliable on this world." + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"stake",QuestValue(17576)=3,Count(5941)<=0 -> "I think you have forgotten to bring your stake." +"stake",QuestValue(17576)=3 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Peace may fill your soul - evil shall be cleansed'. Now, bring your stake to Yberius in the Venore temple for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,4) +Topic=10,"yes" -> "I think you have forgotten to bring your stake." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=4 -> "You should visit Yberius in the Venore temple now." +"stake",QuestValue(17576)>4 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That is a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." + +} diff --git a/app/SabrehavenServer/data/npc/majesticwarwolf.npc b/app/SabrehavenServer/data/npc/majesticwarwolf.npc new file mode 100644 index 0000000..0e05d1d --- /dev/null +++ b/app/SabrehavenServer/data/npc/majesticwarwolf.npc @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "A Majestic Warwolf" +Outfit = (3,0-0-0-0-0) +Home = [33355,31991,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",QuestValue(17535)>8,! -> "Interesting. A human who can speak the language of wolves." +ADDRESS,"hi$",QuestValue(17535)>8,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "YOOOOUHHOOOUU!" + +"bye" -> "YOOOOUHHOOOUU!", Idle + +"addon",QuestValue(17535)=9 -> "I can see in your eyes that you are a honest and friendly person, %N. You were patient enough to learn our language and I will grant you a special gift. Will you accept it?", Topic=1 +"outfit",QuestValue(17535)=9 -> * +Topic=1,"yes", -> "From now on, you shall be known as %N, the bear warrior. You shall be strong and proud as Angros, the great dark bear. He shall guide your path.", SetQuestValue(17535,10), AddOutfitAddon(144,1), AddOutfitAddon(148,1), EffectOpp(13) +Topic=1 -> "Maybe another time." + +"addon",QuestValue(17535)=10 -> "I am proud to see you as strong as the Angros!" +"outfit",QuestValue(17535)=10 -> * + +} diff --git a/app/SabrehavenServer/data/npc/malor.npc b/app/SabrehavenServer/data/npc/malor.npc new file mode 100644 index 0000000..9543449 --- /dev/null +++ b/app/SabrehavenServer/data/npc/malor.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# malor.npc: Datenbank für den Efreetkönig Malor + +Name = "Malor" +Outfit = (51,0-0-0-0-0) +Home = [33044,32621,1] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Greetings, human %N. My patience with your kind is limited, so speak quickly and choose your words well." +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "It might have escaped your limited human perception, but I am already talking to somebody else.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Farewell, human. When I have taken my rightful place I shall remember those who served me well. Even if they are only humans.", Idle +"farewell" -> * + +"name" -> "Is it true you don't know who I am? Well, then listen. My name is Malor. ...", + "You should better memorise that name because you are bound to hear it more often in future." +"job" -> "I am the true leader of all djinn - perhaps not by birth, but certainly by merit. One day all djinn will come to recognise that I alone deserve to be king." +"king" -> "I may not have reached my goal yet, but neither has that accursed Gabel. As long as the Marid and Efreet are disunited neither of us can call himself the king of all djinn." +"djinn" -> "We are strong and proud. One day we will take our rightful place on the throne of creation, and your vulgar race will either serve us or perish. ...", + "Nothing personal, human. It is a natural process. And you humans will find that the djinn can be just masters." +"gabel" -> "That fool. He thought he'd got rid of me for good. But I'm back, and this time I will finish what I have begun. That weak-willed wimp has held on to power far too long." +"efreet" -> "We are djinn! The true djinn! Those who have not let themselves be fooled by the silver-tongued blathering of that perfidious snake called Daraman." +"marid" -> "The so-called Marid have forgotten what it is like to be djinn! They are weak!" +"malor" -> "That is me. I was away for a long time, but now I am back with a vengeance." +"daraman" -> "Of all human liars and schemers he was the worst. This self-styled prophet single-handedly managed to disunite my race and to spark a bloody civil war. ...", + "If somebody fulfilled a wish for me for a change I would bring him back to life and make him pay." +"human" -> "Your race is weak, but incurably treacherous. I will never forgive humanity the fact that it was one of your kind who spread the seed of dissent among the djinn." + +"mal'ouquah" -> "Do you like this place? I have built Mal'ouquah as a home for those among my kind who did not fall for Daraman's sugar covered lies. From here I shall rule the world when the time has come." +"ashta'daramai" -> "Ashta'daramai is the fortress of our sworn enemies, the oh so powerful Marid. The day will come when I see its smouldering walls collapse." +"orc","king" -> "Ah yes. My good old friend the foolish orc. He has rendered me a great service, you know? He released me from that accursed lamp! ...", + "In return I have fulfilled three wishes for him, but somehow I can't help the feeling that he is not wonderfully happy about the way things have turned out." +"zathroth" -> "Our father. He made us a race of masters, not of servants. We will live to fulfill his promise or die trying." +"gods" -> "Are not the creators reflected in their creations? Look around! What do you see? There is nothing but cowardice and treachery in the world of humans. ...", + "How low the gods must be who made them. I have no respect for them." +"tibia" -> "The world of Tibia is ours by right. I will not rest until we have conquered it." +"darashia" -> "Darashia is a very rich city. Once this war is won I will drop by at the Caliph's palace and pay my respects, if you know what I mean." +"scarab" -> "Scarabs are ancient creatures, which is why I respect them. But I will never allow any of these critters to undermine the foundations of my fortress." +"edron" -> "I hear the humans have built impressive cities on the great continent. It looks like many things have changed while I was caught in that stupid lamp." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "Even though it always was a human settlement I have always had a soft spot for the place. I am even thinking about making it my capital once I have taken over the world." +"pharaoh" -> "I have heard that pompous pharaoh believes himself to be some sort of deity. That pathetic bonehead a god? Don't make me laugh!" +"ascension" -> "Ascension? That does ring a bell. Isn't that an element of the pharaoh's doctrines." +"rah" -> "Another one of that loony pharaoh's bright ideas. Nothing but nonsense and balderdash." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "They say the Kha'zeel mountains have been made by gods. If that is true they must have left long ago, because I have lived here for eons, and I have never met one of them." +"kha'labal" -> "Kha'labal. I do not like that desert. Looking at it brings up bad memories." +"war" -> "Gabel and Fa'hradin thought the war was over when they managed to trap me in that accursed lamp. But they have been a bit rash. After all those years I'm still here, and my thirst for revenge is stronger than ever!" +"melchior" -> "Melchior! Hah, that fool! Is he still alive? I never thought the old wretch would make it after I gave him my special treatment and sent him out into the Kha'labal. ...", + "Amazing, really. It has often occurred to me how much humans resemble rats - they are just as hard to kill!" +"baa'leal" -> "I suppose you have met Baa'leal already? The fact that you have survived that encounter shows that you are surprisingly strong for a human. ...", + "I almost feel some respect for you... Well, almost." +"alesar" -> "Oh yes, Alesar! I bet Gabel went mad when he learnt that Alesar switched sides. If only I had been there to watch his face." +"fa'hradin" -> "Fa'hradin is Gabel's lieutenant. I have known him for a long time, and I have always respected him. ...", + "Unfortunately he chose the wrong side when the time to chose sides came. I have not given up hope of winning him over for some reason, but if I meet him on the battlefield I will not hesitate to kill him myself." +"lamp" -> "We djinn use them to sleep." + +"permission",QuestValue(288)<3 -> "I have no reason to give you my permission to trade with Alesar or Yaman." +"permission",QuestValue(288)=3 -> "You are welcome to trade with Alesar and Yaman whenever you want to, %N!" + +"work",QuestValue(287)<3 -> "So you would like to fight for us. Hmm. ...", + "You show true courage, human, but I will not accept your offer at this point of time." +"mission",QuestValue(287)<3 -> * + +"alesar",QuestValue(287)=3,QuestValue(288)=0 -> "I guess this is the first time I entrust a human with a mission. And such an important mission, too. But well, we live in hard times, and I am a bit short of adequate staff. ...", + "Besides, Baa'leal told me you have distinguished yourself well in previous missions, so I think you might be the right person for the job. ...", + "But think carefully, human, for this mission will bring you close to certain death. Are you prepared to embark on this mission?", Topic=1 +"work",QuestValue(287)=3,QuestValue(288)=0 -> * +"mission",QuestValue(287)=3,QuestValue(288)=0 -> * + +"work",QuestValue(288)=1 -> "You haven't finished your final mission yet. Shall I explain it again to you?", Topic=1 +"mission",QuestValue(288)=1 -> * +"lamp",QuestValue(288)=1 -> * + +Topic=1,"yes" -> "Well, listen. We are trying to acquire the ultimate weapon to defeat Gabel: Fa'hradin's lamp! ...", + "At the moment it is still in the possession of that good old friend of mine, the Orc King, who kindly released me from it. ...", + "However, for some reason he is not as friendly as he used to be. You better watch out, human, because I don't think you will get the lamp without a fight. ...", + "Once you have found the lamp you must enter Ashta'daramai again. Sneak into Gabel's personal chambers and exchange his sleeping lamp with Fa'hradin's lamp! ...", + "If you succeed, the war could be over one night later!", SetQuestValue(288,1) +Topic=1 -> "Your choice." + +"work",QuestValue(288)=2 -> "Have you found Fa'hradin's lamp and placed it in Malor's personal chambers? ", Topic=2 +"mission",QuestValue(288)=2 -> * +"lamp",QuestValue(288)=2 -> * + +Topic=2,"yes" -> "Well well, human. So you really have made it - you have smuggled the modified lamp into Gabel's bedroom! ...", + "I never thought I would say this to a human, but I must confess I am impressed. ...", + "Perhaps I have underestimated you and your kind after all. ...", + "I guess I will take this as a lesson to keep in mind when I meet you on the battlefield. ...", + "But that's in the future. For now, I will confine myself to give you the permission to trade with my people whenever you want to. ...", + "Farewell, human!", SetQuestValue(288,3), Idle +Topic=2 -> "Just do it!" +} diff --git a/app/SabrehavenServer/data/npc/malunga.npc b/app/SabrehavenServer/data/npc/malunga.npc new file mode 100644 index 0000000..2ffdabb --- /dev/null +++ b/app/SabrehavenServer/data/npc/malunga.npc @@ -0,0 +1,132 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Malunga" +Outfit = (149,95-78-3-2-0) +Home = [32345,32809,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings. I have only little time to spare, so the conversation will be short. I teach sorcerer spells and buy a few magical ingredients." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Be patient %N, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"farewell" -> * +"spare" -> "I was sent here by the Edron academy for scientific researches. I am also responsible for the magical education of the citizens." +"academy" -> "The academy of Edron as a royal institution has several agendas here. Not all of them are to be discussed with outsiders." +"researches" -> "These isles hold more secrets than those that meet the eye. I am convinced that once a great civilisation prospered here. Some sort of disaster wiped out almost completely any trace of it." +"education" -> "I teach aspiring sorcerers several spells." +"Edron" -> "Edron is a place of learning, magic and secrets. But others are dealing with that already in other places. It is my obligation to handle those issues here." +"secrets" -> "Magic is not available everywhere at the same strength and intensity. There are areas that are dry like a desert, magically spoken, others are rich of magic. At some places magic flows as strong as if there were springs of magic ...", + "At those places the savants build magical centres of great importance. My researches have shown that the chaotic currents of magic must have been more structured once and much stronger than today ...", + "With the right knowledge the civilisation of the past could have worked literally marvellous with those energies ...", + "Even today some use the now chaotic currents of magic to further their evil ends ... but ... that's nothing I am supposed to talk about." +"civilisation" -> "The little we know about the extinct civilisation hints about a magically advanced race of humanoids, possibly of elven heritage, that once inhabited those isles until the disaster struck ...", + "They were probably quite adept in manipulating the magical currents that are extremely strong on these isles." +"disaster" -> "The disaster took place at least some centuries ago. Our researches convinced us that the isles here belonged to a single landmass in the past. That should give you an idea of the extent of this catastrophe." +"cult" -> "It would not surprise me much if some superstitious fools mess around with the chaotic magical currents of this isle. Only the gods may know what harm they could do to themselves and others." +"quaras" -> "The quara are something that is worth some research. Sadly I lack the time and resources for further investigations." +"voodoo" -> "The superstition of the natives pushes this kind of hedge magic. It is intolerable that they are fooling around with powers they don't understand." +"ferumbras" -> "I will not discuss this issue now and here. If you have any questions about him, the academy in Edron is the place to ask ...", + "Considering that this is a rather delicate issue, you should have a good reason to ask though." +"thais" -> "Well, Thais is not the city it used to be. Too many people, too noisy, too dirty." +"venore" -> "Those haggling and scheming merchants sometimes give me more shivers than a demon. At least you know what to expect from a demon." +"king" -> "The king's support of the academy is dwindling. I hope results in my researches will change this significantly." +"triangle of terror" -> "There seems to be some demonic cabal called Triangle of Terror. We don't know anything about its members except that there are three of them." +"raymond striker" -> "I heard he is one of the more notorious pirates." +"pirates" -> "Pirates are a constant pest. As long as their existence does not interfere with my researches, I don't care much about them." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Sorcerers, paladins, knights, and druids." +"spellbook" -> "A spellbook lists all your spells. There you can find the pronunciation of each spell. You can buy one at the magicians' shop." +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Take care on your journeys.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." +} diff --git a/app/SabrehavenServer/data/npc/marcus.npc b/app/SabrehavenServer/data/npc/marcus.npc new file mode 100644 index 0000000..f9df01d --- /dev/null +++ b/app/SabrehavenServer/data/npc/marcus.npc @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Marcus" +Outfit = (128,115-59-78-41-0) +Home = [32316,32861,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N." +ADDRESS,"hi$",! -> "Hello %N." +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye, %N.", Idle +"farewell" -> * +"pirates" -> "Be mourned, pilgrim in I killed my share of pirates in my youth. Scum bags that +give sailors a bad name. Nothing more than bandits." +"voodoo" -> "Hah! What they call voodoo here is nothing compared to the things I have seen on my journeys when I was still young. I could tell you stories that would turn your hairgrey ...", + "but I am not in the mood of storytelling right now." +"ferumbras" -> "Although he's dead, they say that he is the father of all klabautermen." +"klabautermen" -> "A klabauterman is some sort of imp that lives on ships and plays quite evil and sometimes lethal pranks on those who annoy him or don't buy his sympathy with little gifts." +"Eleonore" -> "The governor's little princess, eh? Too fine and important to care about us +ordinary people of course. Why should we careabout her?" + +} diff --git a/app/SabrehavenServer/data/npc/maria.npc b/app/SabrehavenServer/data/npc/maria.npc new file mode 100644 index 0000000..d057c1e --- /dev/null +++ b/app/SabrehavenServer/data/npc/maria.npc @@ -0,0 +1,76 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maria.npc: Datenbank für die Wirtin Maria + +Name = "Maria" +Outfit = (136,77-79-61-131-0) +Home = [32912,32082,9] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "The Hard Rock Tavern greets you, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You're served soon, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Visit us again." + +"bye" -> "Good bye, %N. Tell your friends about us and visit us again.", Idle +"job" -> "I am running this upper part of the Hard Rock Tavern." +"tavern" -> * + +"strange","fellow" -> "I don't know him. He seems very nervous and hes always fumbling around with this suspicious hat." +"david" -> "I am sorry but I don't know him personaly. I heared he entertained people here long before I moved to Venore." +"brassacres" -> * + +"upper","part" -> "Yes, that's here. Below is the Pit Tavern for those fighters that use the pits." +"pits" -> "Well, they do a lot of fighting down there." +"name" -> "I am Maria." +"maria" -> "Yes, I am Maria, Maria Corona." +"time" -> "Don't be that hasty." +"king" -> "In Venore, everyone is a king ... until he runs out of luck or money." +"tibianus" -> * +"army" -> "Good fighters need good entertainment. That's what they get here." +"ferumbras" -> "I think he's more a Thaian problem." +"excalibug" -> "I'd rather have a stainless steel cooking pan than such a knife." +"thais" -> "It's a shame that this lousy city is the heart of the kingdom." +"tibia" -> "In the long run it's money that rules everything in Tibia." +"carlin" -> "As far as the merchants say it's economically unimportant." +"amazon" -> "I can only hope those wild women don't scare away more customers than come here in order to fight against them." +"news" -> "Bah, only the usual swampelves stories." +"rumors" -> * +"swampelves" -> "Well there's a hidden city called Shadowthorn of those warlike elves in the swamps. They are not amused of civilisation at their doorsteps and have been plotting against Venore for years." + +"buy" -> "Food and drinks as much as you can pay for." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "We offer cookies, bread, cheese, ham, and meat, as well as eggs and tomatoes." +"drink" -> "Do you want beer, wine, lemonade, or water?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=5, "Do you want to buy a tomato for %P gold?", Topic=1 +"valentine",ClientVersion>=790 -> Type=6392, Amount=1, Price=100, "Do you want to buy a valentine's cake for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=5*%1, "Do you want to buy %A tomatoes for %P gold?", Topic=1 +%1,1<%1,"valentine",ClientVersion>=790 -> Type=6392, Amount=%1, Price=100*%1, "Do you want to buy %A valentine's cakes for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here is what you ordered.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You penniless beggar! Get out of here!", Idle +Topic=1 -> "Hrmpf!" +} diff --git a/app/SabrehavenServer/data/npc/marina.npc b/app/SabrehavenServer/data/npc/marina.npc new file mode 100644 index 0000000..7a4afb6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/marina.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Marina" +Outfit = (0,5811) +Home = [32329,32528,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, hello %N. A visitor, how nice!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a second please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude." + +"bye" -> "Good bye, %N.", Idle + +"comb" -> "Sorry, I don't have a spare comb. I lost my favourite one when diving around in Calassa." + +# The Mermaid Marina Quest +"silk","yarn",QuestValue(17504)=0 -> "Um. You mean, you really want me to touch that gooey spider silk just because you need yarn? Well... do you think that I'm pretty?", Topic=1 +"spool","of","yarn",QuestValue(17504)=0 -> * +"spider","silk",QuestValue(17504)=0 -> * +Topic=1,"yes" -> "Well, everyone would say that in your position. Do you think that I'm really, absolutely the most stunning being that you have ever seen?", Topic=2 +Topic=1 -> "Get out of there!" +Topic=2,"yes" -> " It's funny how easy it is to get humans to say what you want. Now, proving it will be even more fun! ...", + "You want me to touch something gooey, so you have to touch something gooey for me too. ...", + "I love honey and I haven't eaten it in a while, so bring me 50 honeycombs and worship my beauty a little more, then we will see.", SetQuestValue(17504,1), SetQuestValue(17595,1) +Topic=2 -> "Get out of there!" + +"honeycomb",QuestValue(17504)=1 -> Type=5902, Amount=50, "Did you bring me the 50 honeycombs I requested and do you absolutely admire my beauty?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Oh goodie! Thank you! Okay... I guess since my fingers are sticky now anyway, I will help you. From now on, if you bring me 10 pieces of spider silk, I will create one spool of yarn.", Delete(Type), SetQuestValue(17504,2) +Topic=3,"yes" -> "Sorry, you do not have so many." +Topic=3 -> "Maybe another time." + +"silk","yarn",QuestValue(17504)=2 -> Type=5879, Amount=10, "Okay... a deal is a deal, would you like me to create a spool of yarn from 10 pieces of spider silk?", Topic=4 +"spool","of","yarn",QuestValue(17504)=2 -> * +"spider","silk",QuestValue(17504)=2 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Ew... gooey... there you go.", Delete(Type), Amount=1, Create(5886) +Topic=4,"yes" -> "Sorry, you do not have so many." +Topic=4 -> "Maybe another time." + +#The Shattered Isles Quest +"Raymond","Striker",QuestValue(17502)=7 -> " I think he has a crush on me. Well, silly man, it is only for his own good. This way he can get accustomed to TRUE beauty. And I won't give him up anymore now that he is mine.", SetQuestValue(17502,8) + +"date",QuestValue(17502)=9 -> "Is that the best you can do? A true Djinn would have done something more poetic.", SetQuestValue(17502,10) +"date",QuestValue(17502)=12 -> "This lovely, exotic Djinn is a true poet. And he is asking me for a date? Excellent. Now I can finaly dump this human pirate. He was growing to be boring more and more with each day ...", + "As a little reward for your efforts I allow you to ride my sea turtles. Just look around at the shores and you will find them.", SetQuestValue(17502,13) +} diff --git a/app/SabrehavenServer/data/npc/markwin.npc b/app/SabrehavenServer/data/npc/markwin.npc new file mode 100644 index 0000000..9bdd1c7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/markwin.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# markwin.npc: Datenbank für den Minokönig Markwin + +Name = "Markwin" +Outfit = (23,0-0-0-0-0) +Home = [32418,32147,15] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",QuestValue(222)=0,! -> "No! The hornless have reached my city! BODYGUARDS TO ME!",SetQuestValue(222,1),Summon("Minotaur Guard"),Summon("Minotaur Guard"),Summon("Minotaur Mage"),Summon("Minotaur Mage"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Idle +ADDRESS,"hi$",QuestValue(222)=0,! -> * +ADDRESS,"hello$",QuestValue(245)=2,! -> "Oh, it's you again. What do you want, hornless messenger?" +ADDRESS,"hi$",QuestValue(245)=2,! -> * +ADDRESS,"hello$",! -> "Well ... you defeated my guards! Now everything is over! I guess I will have to answer your questions now." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One more human. I hate them." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes! Leave me alone. Vanish to dust." + +"bye",QuestValue(245)=2 -> "Hm ... good bye.", Idle +"farewell",QuestValue(245)=2 -> * +"bye" -> "Go to hell! Burn!", Burning(20,20), EffectOpp(6), Idle +"farewell" -> * + +"letter",QuestValue(245)=1 -> "A letter from my Moohmy?? Do you have a letter from my Moohmy to me?",Type=3220, Amount=1,topic=1 +Topic=1,"yes",Count(Type)>=Amount -> "Uhm, well thank you, hornless beeing.",SetQuestValue(245,2), Delete(3220) +Topic=1,"yes" -> "Don't mock the king of the minotaurs or you will regret that!" +Topic=1 -> "Uh? What??" + +"job" -> "I am the king of all minotaurs. I have been the king for more than 320 years." +"real" -> "Yes, I am the real king. Palkar is the leader of the outcasts." +"name" -> "I am Markwin, the old and real king of this city." +"time" -> "Don't ask me such stupid questions. My time is over right now." +"tibianus" -> "I am the real king!" +"king" -> * +"outcast" -> "Those are no minos any longer. They left the city and killed their brothers. And they stole the key to my secret lab." +"mintwallin" -> "The former glorious city lies in the dirt. It is my home. I founded it about 180 years ago, when we found this lovely place." +"city" -> * +"chronicle" -> "I am one of the minotaurs that are able to write. So I wrote most of the history of my beloved city Mintwallin in some books." +"books" -> * +"prisoner" -> "He is totally mad. I don't know how he could find the way through the labyrinth. I arrested him in the prison." +"human" -> "I hate them all. Minotaurs have no own spelling, so I used the speech of the humans. Once I was a prisoner of them. Since then I hate them - and since then I can speak and write in their language." +"labyrinth" -> "It protected us for a long time. There are lots of traps in it. And many long tunnels. There haven't been many foes that found their way through it. Only that prisoner once arrived." +"kaplar" -> "I really don't know what it means. But ALL minos say it! Terrible!" +"secret","lab" -> "Hehe - you will never find a way to enter it. The outcast stole the key. You are too weak to conquer it. HARHARHAR." +"enter" -> "To enter the laboratory is pretty difficult." +"enter","lab" -> "First of all you will need a second fellow to help you." +"second","fellow" -> "Yeah - he has to step on a special tile and an entrance will appear at a very poisenous place!" +"place" -> "Na! You will have to find it yourself!" +"second" -> "After you entered the first area you will need the key from the outcasts." +"minotaurs" -> "My fellows all are minotaurs. It is my folk. I am the king of all minos." +"minos" -> * +"key" -> "There are many keys. The outcast stole the key to our secret lab! They should burn!" +"demon" -> "He was the beginning of our end. He is mighty and powerful. He killed many brave minos and after his arrival we weren't able to go up to the surface." +"light" -> "I would like to see the light of the sun again, but you will probably kill me. Go away!" +"sun" -> * +"surface" -> * +"palkar" -> "He is the leader of the outcast. In former times he was my best warrior, but now he is my worst enemy." +"riddle" -> "Riddle? I don't know riddles!" +"karl" -> "The man who explored this part of the map first. Strange guy. He likes to be announced as hunter. I don't like him. He is a human." +"milk" -> "No! I won't tell you the powers of our milk!" +} diff --git a/app/SabrehavenServer/data/npc/marlene.npc b/app/SabrehavenServer/data/npc/marlene.npc new file mode 100644 index 0000000..7ca0ad2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/marlene.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# marlene.npc: Die Tratschtante Marlene (Fields) + +Name = "Marlene" +Outfit = (136,96-111-16-96-0) +Home = [32483,31624,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahhh, welcome %N! Say, have you already heard the latest news about the seamonster, Aneus, or the rumours in this area?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again for some other rumours! *waves with her hand at you*" + +"bye" -> "Good bye and come again for another small talk! *waves with her hand at you*", Idle +"farewell" -> * +"name" -> "My name is Marlene." +"job" -> "I'm Bruno's wife. Besides: Have you already heard the latest news about the seamonster, Aneus, or the rumours in this area?" +"bruno" -> "Bruno is a wonderful husband. But he is seldom at home. *looks a little bit sad*" +"graubart" -> "Ah, old Graubart. A very nice person. But he is strange. He always is busy when I want to talk to him. *lost in thoughts*" + +"aneus" -> "A very nice person. He has a great story to tell with big fights and much magic. Just ask him for his story. ...", + "I heard that he came from far, far away. He must have seen soooo many countries, cities, different races. ...", + "He must have collected so much wisdom. *sigh* I wish I could also travel around the world. ...", + "I would try to visit as many cities and meet as many beings as possible. Who knows what strange races I will meet? ...", + "Maybe I can also find a lovely new dress for me. I have been looking for one for months now but never found a good one. Maybe... *keeps on babbling*" +"yes","maybe" -> "Yes, maybe one day. *sigh*" + + +"seamonster" -> "Only some days ago I was at the docks late in the night and was looking for my husband's ship when suddenly a known noise appeared near the docks. ...", + "I know this noise very well because it is the noise of a ship sailing very fast. I searched the horizon in hope to see my husbands ship. ...", + "But instead of a ship I saw a huge shape far away. It was like a big snake swimming in the sea. ...", + "I couldn't see it clearly because of the fog but I think I saw two lava-red eyes glowing in the nightly fog. ...", + "I ran into the house and hoped that my husband would arrive safely from fishing. And after one hour he finally arrived. ...", + "I told him about what I saw but he didn't believe me because he never saw anything like that in all the years before. But you believe me right? Go and convince yourself. ...", + "Just go to the docks at exactly midnight and be very quiet. Look at the horizon and maybe you will hear and see it, too!" + +"rumour" -> "Well, I heard about evil beings living in a dungeon below us. So once I tried to find them and went down the hole far to the southwest. ...", + "I'm pretty curious, you know. *smiles* So I took the coat of invisibility from my husband and went down there. At first I only found some spiders, snakes, and wolves. ...", + "But after some time I found a ladder to a deeper level of the dungeon but I didn't dare to go down there because I heard many voices. ...", + "The voices were very strange and I ran back to my house because they were very loud and very angry. I hope they will never get the idea to attack the surface beings. ...", + "I heard they are allmighty and have incredible powers! I already packed our stuff for an emergency escape. You never know. Maybe they plan to conquer the whole world. ...", + "I bet that they look very ugly. Most mighty monsters look very ugly. Hmm, you seem to be .very strong. Maybe you can go deeper and explore the area. But be careful, please. ...", + "I heard that they can kill humans with only one hit! And that they have magic abilities twenty times stronger than the mightiest sorcerer in our world." +"thank","you" -> "My pleasure, I always enjoy sharing interesting stories." +} diff --git a/app/SabrehavenServer/data/npc/marvik.npc b/app/SabrehavenServer/data/npc/marvik.npc new file mode 100644 index 0000000..0e7cfb2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/marvik.npc @@ -0,0 +1,150 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# marvik.npc: Datenbank fuer den Druiden Marvik + +Name = "Marvik" +Outfit = (130,0-101-121-95-0) +Home = [32444,32213,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Nice to see you again, %N!" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",! -> "Welcome to my cave, %N. How may I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the chief druid. I am responsible for all members." +"name" -> "I am Marvik. Probably you heard from me." +"time" -> "Eh, I haven't seen daylight for a long time. So, don't ask me, what time it is." +"king" -> "Kings come and go." +"tibianus" -> * +"quentin" -> "He is a great healer." +"lynda" -> "Though she focuses not only on Crunor, she is an enlightened person." +"harkath" -> "I understand, he's a warrior of some kind." +"army" -> "I don't care about armies." +"general" -> * +"ferumbras" -> "A misguided follower of evil." +"sam" -> "An armourer." +"gorn" -> "A shopkeeper." +"frodo" -> "A little more seriousity would suite him well." +"elane" -> "Paladins are quite proud of their magic but lack the understandig of the powers they wield." +"muriel" -> "So much power and so little philosophy ... a dangerous combination indeed." +"gregor" -> "Warriors are the main recipients of our healing powers." +"marvik" -> "Marvik is my name, so what?" +"bozo" -> "I don't like his kind of humour." +"baxter" -> "I don't know him." +"oswald" -> "He saws the seeds of evil through spreading rumours." +"sherry" -> "The McRonalds are true believers." +"donald" -> * +"mcronald" -> * +"crunor" -> "Crunor, the eternal tree, is more than nature. Even more as the sum of each part of nature." +"lugri" -> "A misguided soul who lost the path in the darkness." +"excalibug" -> "Even in my visions, I couldn't get any enlightenment about the whereabouts of this weapon of legend." +"news" -> "Why are you so concerned with news, if you haven't even understood the old things you know?" + +"crunor","caress" -> "It was a small order of druids in the past. Their followers wanted the druids to become more involved with daily affairs of men." + +"member" -> "Our members use their magic power to protect their life and the life of other creatures." +"magic" -> "Everyone who joins the Druids has the opportunity to learn many magic spells." +"power" -> "Everyone who joins the Druids has the opportunity to learn many magic spells." +"druid" -> "I am a druid. Druids concentrate their magic on defence, healing, and nature." +"sorcerer" -> "Sorcerers are very aggressive. They use their power for fighting and killing." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Druids, paladins, knights, and sorcerers." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Muriel, the sorcerer." +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/maryza.npc b/app/SabrehavenServer/data/npc/maryza.npc new file mode 100644 index 0000000..60ffa1e --- /dev/null +++ b/app/SabrehavenServer/data/npc/maryza.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maryza.npc: Datenbank für die Wirtin Maryza + +Name = "Maryza" +Outfit = (160,60-110-58-76-0) +Home = [32634,31889,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","maryza",! -> "Welcome to the Jolly Axeman, %N. Have a good time!" +ADDRESS,"hi$","maryza",! -> * +ADDRESS,"hello$",! -> "Talking to me?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","maryza",! -> "Shut up %N. Busy. You wait!", Queue +BUSY,"hi$","maryza",! -> * +BUSY,"hello$",! -> "Talking to me, %N?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! You lousy....!" + +"bye" -> "Yeah, bye", Idle +"farewell" -> "Yeah, farewell", Idle +"hello$","jimbin",! -> "Yeah, bye", Idle +"hi$","jimbin",! -> "Yeah, bye", Idle +"job" -> "I'm the cook of the Jolly Axeman." +"tavern" -> * +"jimbin" -> "I am so proud of him. In drinking, he's second only to our mighty general." +"name" -> "I am Maryza Firehand, daughter of Earth, from the Molten Rock." +"time" -> "To busy, ask my husband." +"king" -> "Don't like these upper cave guys." +"army" -> "We could better feed some dragons instead of these fools." +"ferumbras" -> "Heard that's what the humans call one of their boggiemen." +"general" -> "A fine drinker and strategist. Wastes his skill with these idiots of the army. What a shame." +"excalibug" -> "Would slice a dragon or two for steaks if i'd get it." +"tark" -> "He loved my dragonsteaks. Heard he died by a cave in while fighting drags in the Plains of Havoc." +"thais" -> "Puny town for puny guys." +"tibia" -> "We don't care much about outsiders anymore." +"carlin" -> "Don't like it, has an elfish touch, ye know?" +"news" -> "The boys of the Savage Axe at the bridge are running wild in these days." +"rumors" -> * +"bloody","mary" -> Type=3113, Amount=1, "YOU &/$#@!", Poison(15,1), EffectOpp(1), EffectMe(3), Create(Type) +"buy" -> "I can offer you some food if ye like." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "I sell normal and brown bread, meat, ham, cookies, rolls, and cheese made of mushrooms." +"book" -> Type=3234, Amount=1, Price=150, "The cookbook of the famous dwarfish kitchen. You're lucky. I have a few copies on sale. Do you like one for %P gold?", Topic=2 +"cookbook" -> * +"book",QuestValue(279)>0,! -> "I'm sorry but I sell only one copy to each customer. Otherwise they would have been sold out a long time ago." +"cookbook",QuestValue(279)>0,! -> * + +"bread" -> Type=3600, Amount=1, Price=4, "Do you wanna buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you wanna buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you wanna buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you wanna buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you wanna buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you wanna buy a roll for %P gold?", Topic=1 +"brown","bread" -> Type=3602, Amount=1, Price=3, "Do you wanna buy a brown bread for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you wanna buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"brown","bread" -> Type=3602, Amount=%1, Price=3*%1, "Do you wanna buy %A brown breads for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No gold, no sale, that's it." +Topic=1 -> "You &/$#@!" + +Topic=2,"yes",CountMoney>=Price -> "Here you are. Happy cooking!", DeleteMoney, Create(Type), SetQuestValue(279,1) +Topic=2,"yes" -> "No gold, no sale, that's it." +Topic=2 -> "I have but a few copies, anyway." +} diff --git a/app/SabrehavenServer/data/npc/mehkesh.npc b/app/SabrehavenServer/data/npc/mehkesh.npc new file mode 100644 index 0000000..9bf7c47 --- /dev/null +++ b/app/SabrehavenServer/data/npc/mehkesh.npc @@ -0,0 +1,100 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mehkesh.npc: Datenbank für den pyramidenhändler mehkesh + +Name = "Mehkesh" +Outfit = (130,19-92-113-40-0) +Home = [33130,32811,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am a trader. I sell potions brewed by the foremost alchemists of the land." +"name" -> "I am the mourned Mehkesh." +"time" -> "Time is but one of the hardships our mortal flesh has to endure." +"temple" -> "The temple spreads the word of our wise pharaoh." +"pharaoh" -> "The pharaoh alone has achieved godhood. But in his infinite mercy he chose to stay with his people to offer them guidance." +"arkhothep" -> * +"ashmunrah" -> "Even though his merciful son offered him undeath I doubt the old pharaoh will ever find his way to ascension." +"scarab" -> "The scarabs are more then just enormous insects. They are keepers of ancient secrets." +"chosen" -> "If we serve the pharaoh during our life time he might allow us to serve him in undeath. Only then can truly start our search for ascension." +"tibia" -> "One day our world will be freed from the false gods and accept the guidance of our pharaoh." +"carlin" -> "Those citys are only pawns of the false gods and their misguided priests." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves should know better then to praise the mortal essence of the elements." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are foolish and their obsession with life damns them to eternal death." +"elves" -> * +"elfes" -> * +"darama" -> "These are the lands of old secrets where the mortals will be shown the true revelations." +"darashia" -> "A city of misguided fools." +"daraman" -> "The prophet caught a glimpse of ascension, but he did not understand it." +"ankrahmun" -> "This city will remain as an eternal testament of our immortal pharaoh's power." + +"mortality" -> "Only if we leave our mortality behind can we achieve salvation and ascension." +"false", "gods" -> "The false gods use our mortal flesh to enslave us." + +"ascension" -> "Ascension is a difficult process. As long as we are mortal we are too distracted to even think about it." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the union of that which once was separate." +"Akh" -> "Our Akh is our mortal flesh until it is replaced with something better - with an undead body." + +"undead" -> "Undeath is the victory over the weaknesses of mortal flesh." +"undeath" -> * +"Rah" -> "The Rah is our essence. The divine part in all of us." +"uthun" -> "The Uthun is the knowledge we gather in the course of time." +"mourn" -> "We are so pathetic in our mortality." + +"arena" -> "The arena is a challenge for every skilled fighter." +"palace" -> "You can find the palace to the east of this market hall." + +"offer" -> "I'm selling life and mana fluids." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/melchior.npc b/app/SabrehavenServer/data/npc/melchior.npc new file mode 100644 index 0000000..3291f19 --- /dev/null +++ b/app/SabrehavenServer/data/npc/melchior.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# melchior.npc: Datenbank für den blinden Bettler Melchior + +Name = "Melchior" +Outfit = (130,0-25-59-115-0) +Home = [33143,32828,7] +Radius = 60 + +Behaviour = { + +ADDRESS,"hello$",male,! -> "Greetings, %N. I do not see your face, but I can read a thousand things in your voice!" +ADDRESS,"hi$",male,! -> * +ADDRESS,"greetings$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome, %N! The lovely sound of your voice shines like a beam of light through my solitary darkness!" +ADDRESS,"hi$",female,! -> * +ADDRESS,"greetings$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Only one minute more, %N. I shall talk to you at once.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the light be with you." + +"bye" -> "Farewell, stranger. May Uman the Wise guide your steps in this treacherous land.", Idle +"farewell" -> * +"name" -> "My late father, may he rest in peace, chose to call me Melchior." +"melchior" -> "That is my name." +"job" -> "I am a poor beggar. I try to make a meagre living here since a cruel fate has left me a blind man." +"blind" -> "Yes, I am. I was not born that way, but a cruel fate caused me to lose my eyesight." +"fate" -> "Fate played a cruel trick on old Melchior. If you want me to, I can tell you my story - talking about one's grievances does help to ease the pain. So - would you like to hear my story?", Topic=1 +"story" -> * + +Topic=1,"yes" -> "While my eyes were still of use to me I worked as a trader. I was not too successful, so I started looking for alternatives. Remembering some old nomad legends I went to explore the Kha'zeel. If only I'd never done that! ...", + "After many days I met a weird creature - it was humanoid, but it was also much larger than any man, and it seemed to be only half-solid in a way. ...", + "I was scared, but I remembered my grandfather's stories and I hailed the creature using the traditional djinn word of greeting. ...", + "It worked. I managed to engage the djinn - for it was one sure enough - in a conversation. In fact, I even managed to come to an agreement with it. The djinns living there needed supplies, and I promised I would bring them some. ...", + "A highly profitable business relationship ensued. Unfortunately, my greed grew every day, and it clouded my sense of judgement. ...", + "Hearing that there was a second djinn fortress I travelled there. Those djinn, who called themselves the Marid, were friendly enough, and soon I traded with them as well. ...", + "Unfortunately, it did not take the other djinn tribe, the Efreet, long to find out what I was up to. ...", + "The Efreets' punishment was cruel: They blinded me and left me in the Kha'labal to die of thirst and exhaustion as food for the scarabs. But that was a favour I could not do them. ...", + "I desperately struggled on and finally I was picked up by a caravan. They took me here, and now I am sort of stuck here in this city of the half-dead." +Topic=1 -> "As you wish, stranger." + +"djinn" -> "The djinns are a wondrous race. Swift and strong they are and larger, much larger than any man. ...", + "And yet, djinns fit into tiny lamps, for they are but half solid creatures who can change into mist whenever they want to! It is as though they lived between the worlds, travelling hither and thither as they please. ...", + "Little is known about their origin, but legend has it Zathroth himself, the dark master of magic, created them to some unknown evil purpose. ...", + "But they are not evil, and perhaps that is why Zathroth eventually abandoned them! Djinns have independent minds and souls just like humans, and just like us they are forlorn creatures struggling to find their place in creation. ...", + "They have fought a bitter, bitter war between themselves over this, a cataclysmic war that led them to the brink of self-destruction. ...", + "Today they are few and far between, but they are still divided into two warring fractions, the gentle Marid and the cruel Efreet, and neither side will rest until the other is utterly defeated. ...", + "If you ever meet a djinn make sure to say the word of greeting immediately. Otherwise he will simply ignore you or worse - if it is an Efreet he will kill you outright. ...", + "And remember, once you decided to follow one group of djinns you can never switch sides, so choose well. No Efreet will ever deal with a follower of the Marid and vice versa." +"greeting",QuestValue(278)<2 -> "The djinns have an ancient code of honour. This code includes a special concept of hospitality. Anybody who utters the word of greeting must not be attacked even if he is an enemy. Well, at least that is what the code says. ...", + "I have found out, though, that this does not work at all times. There is no point to say the word of greeting to an enraged djinn. ...", + "I can tell you the word of greeting if you're interested. It is DJANNI'HAH. Remember this word well, stranger. It might save your life one day. ...", + "And keep in mind that you must choose sides in this conflict. You can only follow the Efreet or the Marid - once you have made your choice there is no way back. I know from experience that djinn do not tolerate double-crossing.", SetQuestValue(278,1) +"word",QuestValue(278)<2 -> * +"djanni'hah",QuestValue(278)<2 -> "You know the traditional djinn word of greeting DJANNI'HAH. Use it wisely!", SetQuestValue(278,1) +"efreet" -> "Beware the Efreet, stranger! They hate all humans, and if they had their way all of us would be killed. If you meet one be sure to say the word of greeting immediately because otherwise you will be killed in a heartbeat. ...", + "And do not approach them if you are a follower of the Marid - they are impossible to fool!" +"marid" -> "The Marid are gentle, kind-hearted djinn, or at least that is how they act towards humans. However, they are quite reclusive, too. They will not talk to human unless he says the word of greeting first. ...", + "And do not approach them if you are a follower of the Efreet - they are impossible to fool!" + +"kha'zeel" -> "That is the name of the huge mountain range to the west of the great desert, the Kha'labal. That's where you will find the djinns' fortresses Ashta'daramai and Mal'ouquah. ...", + "They say it was created by the gods as a vantage point from which they used watch their creation. I think it is true. I used to travel there often, and I swear I often felt the presence of something special, something... divine." +"kha'labal" -> "Aaaah... The great desert. Legend has it that in ancient times it was a beautiful garden. I don't know if that is true, but I must admit I love it just the way it is. ...", + "Travelling through that endless stretch of barren land always gave me a very special peace of mind." +"mal'ouquah" -> "That is the Efreet's gloomy fortress, home of Malor, hidden high up in the Kha'zeel mountains. I used to go there often. Don't make the same mistake, stranger! I would love to think there is somebody who profited from the lesson I had to learn!" +"ashta'daramai" -> "Aah yes - the Marids' fortress. Perched high on the Kha'zeel, it is a marvel to behold. They say Gabel built it on the ruins of his original palace." +"gabel" -> "He is the leader of the Marid! I have never met him myself, but everybody was full of praise for him back at Ashta'daramai. The legend has it that it was him who introduced the djinns to wise Daraman's teachings." +"malor" -> "Malor is the Efreets' leader. He is perhaps not the strongest of all efreet, but his treachery and cruelty are certainly unrivalled. He was defeated a long, long time ago, but he was not killed. ...", + "I don't know why... I have a strange feeling of foreboding whenever I hear his name." +"scarab" -> "Those damn scavengers! I detest them. When I was stumbling through the desert, all blind and desperate, they followed me around. ...", + "They watched my every step, waiting for me to give up and die. But I never did. Damn vermin! You'll have to eat somebody else!" + +"alesar" -> "I know that name. He is a Marid. This djinn is one of the best smiths ever to live. You should see the scimitars he makes - hard as titanium yet light as a desert breeze. ...", + "I kept on trying to get the Marid to sell one to me, but they never did. Too bad - I could have made a fortune." +"fa'hradin" -> "I know that name. He is a Marid, right? I have met him once. He seemed pretty important." +"baa'leal" -> "Cursed be that djinn! It was him who blinded me, and I bet casting me out into the Kha'labal was his idea, too. Believe me, I would try to kill him if only I could." +"haroun" -> "A Marid trader. I have often had dealings with him. He drove me mad because he never accepted any haggling, but then he never ever tried to trick me. He was not really a trader at heart, I suppose. He was more of a monk or maybe a preacher." +"bas'saam" -> "Yes, I know him. He is an Efreet trader. I met him often during my travels, and even though there was no real sympathy we had a certain mutual respect for each other. But all that changed when he found out I had dealings with the Marid." +"bo'ques" -> "He is a cook - a djinn cook. A weird guy. Always used to ask me to procure some strange kind of ingredient or other for him. ...", + "He made me laugh. Can you imagine what that looks like - a djinn wearing an enormous chef hat?" + +"tibia" -> "Tibia is such a beautiful place. I would give it all if I could see it again." +"daraman" -> "Daraman was a holy man, a true prophet. He showed us how we can master grief and affliction through dignity and brotherliness. It is a shame I only came to fully appreciate his teachings when fate had cast me into darkness." +"darashia" -> " Aah yes... Darashia. I would give anything if I could see it again." +"edron" -> "I have never been to cities on other continents. And I suppose now I never will. I would be glad enough to leave this place." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I hate this city. Period. I would never have come here, but I haven't had much of a choice. The caravan that picked me up in the Kha'zeel was headed for this place, and I was glad enough they brought me here. ...", + "But now I really wish I could leave. These people and their ruler give me the creeps!" + +"pharaoh" -> "The pharaoh? He is always in the palace, so I have heard only rumors about him. But I know one thing for sure - he is mad. End of story." +"ruler" -> * +"old","pharaoh" -> "The old pharaoh? I keep on hearing rumours about him, but I do not know if they are true.", Topic=2 +Topic=2,"rumour" -> " It is said his son, the present pharaoh, killed him and turned him into some ghastly undead!" +Topic=2,"rumor" -> * +"palace" -> "The palace lies to the south of the arena and to the west of the temple. Better stay clear of that place. If but half the things I have heard about it are true this palace is not a place for the living anymore." +"arena" -> "Ah yes, the arena. I do not really know what's going on there, because I have never seen it myself. However, I often hear strange noises from there, cheers and jeers and sometimes pityful screams." +"temple" -> "That temple is very old, and for centuries it used to be a place of worship and of contemplation. Now that the priests there are fanatic followers of the pharaoh this is no longer a holy place." + +"ascension" -> "The concept of ascension is central to the pharaoh's creed. I am not sure I really understand it, but apparently it has to do with transformation to undeath. Nice, isn't it?" +"rah" -> "Ah yes - I recognise that. According to the pharaoh that is a living being's soul." +"uthun" -> "According to the pharaoh's teachings this is the total of a living being's memories and personal experiences." +"akh" -> "In the pharaoh's creed, this is what the physical body is called." +"mourn" -> "Spare me that inane twaddle, will you? I am glad enough to be alive, thank you." +} diff --git a/app/SabrehavenServer/data/npc/memech.npc b/app/SabrehavenServer/data/npc/memech.npc new file mode 100644 index 0000000..a28c8ee --- /dev/null +++ b/app/SabrehavenServer/data/npc/memech.npc @@ -0,0 +1,180 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# memech.npc: Datenbank für den pyramidenhändler(waffen) Memech + +Name = "Memech" +Outfit = (131,21-21-40-116-0) +Home = [33135,32810,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Taken away by the hands of time." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell weapons and armor to protect your mortal shell." +"name" -> "I am the mourned Memech." +"time" -> "Time is only a burden to the flesh." +"temple" -> "You will find the temple in the northeastern part of the town." +"pharaoh" -> "Praise to the pharaoh. Blessed be our saviour." +"oldpharaoh" -> "Praised be our pharaoh who gave his father all the time in the world for contemplation and ascension." +"scarab" -> "The scarabs are wise as far as I know. They test each warrior's strength." + +"tibia" -> "This world is but a dying spark of a once great fire." +"carlin" -> "The lost cities of the Tibian continent are caught in their false gods' jaws." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "I have been told the dwarves are worthy fighters. It is a shame their Rah will perish upon death." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are a rare sight on this continent. I know little about this race and their ways." +"elves" -> * +"elfes" -> * +"darama" -> "The continent's name was changed to Darama after Daraman spread his teachings here. I don't know its old name, I'm afraid." +"daraman" -> "Daraman is acknowledged as a prophet, though it is said he was misguided. I think you should better ask somebody in the temple about such issues." +"ankrahmun" -> "Our home is blessed and protected by the power of our pharaoh." + +"pharaoh" -> "The pharaoh is a living god and his power is rising with every day." +"false", "gods" -> "Well, the temple teaches us that the false gods want to steal our Rah." +"ascension" -> "This is nothing I understand. I am but a simple man." +"Akh'rah","Uthun" -> "Thats religious stuff, and I don't know much about it. It's about the union and the separation of Akh, Rah and Uthun." +"Akh" -> "Well, that is the mortal body. It is full of needs and thus sinful." +"undead" -> "Undeath must be great. No need to eat, to sleep or to do other things like that, you know." +"undeath" -> * +"Rah" -> "The Rah is what people from other religions call the soul." +"uthun" -> "That's what we learn and remember. It is who we are because of our memories. At least that's what I understand." +"mourn" -> "Mortality is a bad thing. The dead mourn us for that. Quite nice of them. We should mourn ourselves as well, if I understand the priests correctly." +"arena" -> "The arena is a fun place to visit. You should go there to try out our quality equipment." +"palace" -> "That's where our pharaohs resides. The palace is to the east." +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legss for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." + +"sell","magic","plate","armor" -> "WOW! Do you really want to sell me a MAGIC plate armor?", Topic=3 +Topic=3,"yes" -> Type=3366, Amount=1, Price=6400,"Oh, unbelievable! I would pay %P gold for this wonderful piece of armor. Are you still interested?", Topic=4 +Topic=3 -> "Hmmm, what a pity! I have been looking for such an armor since a long time." +Topic=4,"yes",Count(Type)>=Amount -> "Finally it is mine! Here is your money. Can I be of any further help?", Delete(Type), CreateMoney +Topic=4,"yes" -> "Argl! You do not have one! Trying to tease me? Get lost or I call the guards!",Idle +Topic=4 -> "Maybe my offer is too low? Unfortunately I can not bring up more money, I am just a smith." + +} diff --git a/app/SabrehavenServer/data/npc/meraya.npc b/app/SabrehavenServer/data/npc/meraya.npc new file mode 100644 index 0000000..38c6459 --- /dev/null +++ b/app/SabrehavenServer/data/npc/meraya.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Meraya" +Outfit = (142,78-101-121-116-0) +Home = [32188,32978,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh hello, nice to see you %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I'm already talking to someone." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/mirabell.npc b/app/SabrehavenServer/data/npc/mirabell.npc new file mode 100644 index 0000000..730be2d --- /dev/null +++ b/app/SabrehavenServer/data/npc/mirabell.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mirabell.npc: Datenbank für die Wirtin Mirabell + +Name = "Mirabell" +Outfit = (136,96-12-87-77-0) +Home = [33174,31801,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Horn of Plenty, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back soon, traveller." + +"bye" -> "Come back soon, traveller.", Idle +"job" -> "I am the owner of this tavern, which is known far beyond Edron." +"tavern" -> * +"frodo" -> "He's my cousin and lives in Thais." +"name" -> "My name is Mirabell." +"time" -> "It is %T right now." +"king" -> "King Tibianus III should visit our beautiful isle more often." +"tibianus" -> * +"army" -> "Sadly most of them are too disciplined to visit my tavern." +"ferumbras" -> "I heard horrible things about him." +"excalibug" -> "I heard the Knights of the True Blood are looking for it on this isle." +"thais" -> "Thais will loose influence on Edron more and more." +"tibia" -> "I think Edron is the best place in Tibia." +"carlin" -> "They should return to the Thaian realm." +"edron" -> "I think it is the best place in Tibia." +"news" -> "Oh, there are so many. Just ask other travellers like you." +"rumors" -> * + +"buy" -> "I can offer you food and drinks." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/miraia.npc b/app/SabrehavenServer/data/npc/miraia.npc new file mode 100644 index 0000000..b1b0893 --- /dev/null +++ b/app/SabrehavenServer/data/npc/miraia.npc @@ -0,0 +1,114 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# miraia.npc: Datenbank für die Wirtin Miraia + +Name = "Miraia" +Outfit = (150,95-0-7-115-3) +Home = [33238,32483,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N. Welcome to the Enlightened Oasis." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will serve you in a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings. Come back soon." + +"bye" -> "Daraman's blessings. Come back soon.", Idle +"job" -> "I am the owner of this tavern, this oasis for the thirst, home of shadow and relaxation." +"tavern" -> * +"frodo" -> "He's my cousin and lives in Thais." +"name" -> "My name is Miraia." +"time" -> "Don't worry about time right now." +"caliph" -> "Sadly the caliph does not visit this humble place." +"kazzan" -> * +"ferumbras" -> "Travellers talked to me about his evilness. Thrice damned be his name." +"excalibug" -> "Some foolish adventurers seek for it in the haunted ruins of Drefia." +"thais" -> "Thais is a place of evil and corruption." +"tibia" -> "Here we are far away from the temptations of the world." +"carlin" -> "At least they shun alcohol over there." +"news" -> "Oh, just listen to the tales told by the other visitors." +"rumour" -> * +"rumor" -> * + +"buy" -> "I can offer you food and drinks." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you lemonade, milk, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=3, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=2, "Do you want to buy a mug of water for %P gold?", Topic=1 +"milk" -> Type=2880, Data=9, Amount=1, Price=5, "Do you want to buy a mug of camel milk for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 +%1,1<%1,"milk" -> Type=2880, Data=9, Amount=%1, Price=5*%1, "Do you want to buy %A mugs of camel milk for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"addon",QuestValue(17557)=5,female -> "Hehe, I like your pretty veil!" +"outfit",QuestValue(17557)=5,female -> * +"addon" -> "Hehe, would you like to wear a pretty veil like I do? Well... I could help you, but you would have to complete a task first." +"outfit" -> * + +"mission",male -> "I don't have any tasks available for you handsome." +"task",male -> * + +"mission",QuestValue(17557)=0,female -> "You mean, you would like to prove that you deserve to wear such a veil?", Topic=5 +"task",QuestValue(17557)=0,female -> * +Topic=5,"yes" -> "Alright, then listen to the following requirements. We are currently in dire need of ape fur since the Caliph has requested a new bathroom carpet. ...", + "Thus, please bring me 100 pieces of ape fur. Secondly, it came to our ears that the explorer society has discovered a new undersea race of fishmen. ...", + "Their fins are said to allow humans to walk on water! Please bring us 100 of these fish fin. ...", + "Third, if the plan of walking on water should fail, we need enchanted chicken wings to prevent the testers from drowning. Please bring me two. ...", + "Last but not least, just drop by with 100 pieces of blue cloth and I will happily show you how to create this veil. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=6 +Topic=5 -> "Maybe next time." +Topic=6,"yes" -> "Excellent! Come back to me once you have collected 100 pieces of ape fur.", SetQuestValue(17557,1), SetQuestValue(17594,1) +Topic=6 -> "Maybe next time." + +"ape","fur",QuestValue(17557)=1,female -> Type=5883, Amount=100, "Have you really managed to fulfil the task and brought me 100 pieces of ape fur?", Topic=7 +"mission",QuestValue(17557)=1,female -> * +"task",QuestValue(17557)=1,female -> * +Topic=7,"yes",Count(Type)>=Amount -> "Ahhh, this softness! I'm impressed, %N. You're on the best way to earn that veil. Now, please retrieve 100 fish fins.", Delete(Type), SetQuestValue(17557,2) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe next time." + +"fish","fin",QuestValue(17557)=2,female -> Type=5895, Amount=100, "Were you able to discover the undersea race and retrieved 100 fish fins?", Topic=8 +"mission",QuestValue(17557)=2,female -> * +"task",QuestValue(17557)=2,female -> * +Topic=8,"yes",Count(Type)>=Amount -> "I never thought you'd make it, %N. Now we only need two enchanted chicken wings to start our waterwalking test!", Delete(Type), SetQuestValue(17557,3) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe next time." + +"enchanted","chicken","wing",QuestValue(17557)=3,female -> Type=5891, Amount=2, "Were you able to get hold of two enchanted chicken wings?", Topic=9 +"mission",QuestValue(17557)=3,female -> * +"task",QuestValue(17557)=3,female -> * +Topic=9,"yes",Count(Type)>=Amount -> "Great, thank you very much. Just bring me 100 pieces of blue cloth now and I will happily show you how to make a veil.", Delete(Type), SetQuestValue(17557,4) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe next time." + +"blue","cloth",QuestValue(17557)=4,female -> Type=5912, Amount=100, "Ah, have you brought the 100 pieces of blue cloth?", Topic=10 +"mission",QuestValue(17557)=4,female -> * +"task",QuestValue(17557)=4,female -> * +Topic=10,"yes",Count(Type)>=Amount -> "Ah! Congratulations - I hope this veil will turn out as beautiful as you are. Here, I'll do it for you.", Delete(Type), SetQuestValue(17557,5), AddOutfitAddon(146,2), AddOutfitAddon(150,2), EffectOpp(13) +Topic=10,"yes" -> "You don't have that many." +Topic=10 -> "Maybe next time." + +"mission",QuestValue(17557)=5 -> "Sorry but I don't have any tasks for you." +"task",QuestValue(17557)=5 -> * +} diff --git a/app/SabrehavenServer/data/npc/morgan.npc b/app/SabrehavenServer/data/npc/morgan.npc new file mode 100644 index 0000000..8e7b7a2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/morgan.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Morgan" +Outfit = (134,78-120-122-132-2) +Home = [32324,32599,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello there." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking. Please wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"mission",QuestValue(17520)=1,QuestValue(17522)=0 -> Type=6113, Amount=1, "Hm, if you are that eager to work I have an idea how you could help me out. A distant relative of mine, the old sage Eremo lives on the isle Cormaya, near Edron. ...", + "He has not heard from me since ages. He might assume that I am dead. Since I don't want him to get into trouble for receiving a letter from a pirate I ask you to deliver it personally. ...", + "Of course it's a long journey but you asked for it. You will have to prove us your worth. Are you up to that?", Topic=1 +Topic=1,"yes" -> "Alright, we will see. Here, take this letter and deliver it safely to old Eremo on Cormaya.", Create(Type), SetQuestValue(17522,1) +Topic=1 -> "Maybe another time." + +"mission",QuestValue(17522)=1 -> "Please deliver my letter to Eremo as fast as you can." +"mission",QuestValue(17522)=2 -> "Thank you for delivering my letter to Eremo. I have no more missions for you.", SetQuestValue(17522,3) +"mission" -> "Sorry, I don't have any missions for you." + +"addon" -> "I can forge the finest weapons for knights and warriors. They may wear them proudly and visible to everyone." +"outfit" -> * + +"weapon" -> "Would you rather be interested in a knight's sword or in a warrior's sword?", Topic=2 +"forge" -> "What would you like me to forge for you? A knight's sword or a warrior's sword?", Topic=2 +Topic=2,"knight",QuestValue(17541)=0 -> "Great! Simply bring me 100 Iron Ore and one Crude Iron and I will happily forge it for you.", SetQuestValue(17541,1) +Topic=2,"knight",QuestValue(17541)=1,Count(5880)>=100,Count(5892)>=1 -> "Alright! As a matter of fact, I have one in store. Here you go!", DeleteAmount(5880,100), DeleteAmount(5892,1), SetQuestValue(17541,2), AddOutfitAddon(139,1), AddOutfitAddon(131,1), EffectOpp(13) +Topic=2,"knight",QuestValue(17541)=1 -> "Great! Simply bring me 100 Iron Ore and one Crude Iron and I will happily forge it for you." +Topic=2,"knight",QuestValue(17541)=2 -> "Sorry since you already have one sword I will not forge more for you." + +Topic=2,"warrior",QuestValue(17560)=0 -> "Great! Simply bring me 100 iron ore and one royal steel and I will happily forge it for you.", SetQuestValue(17560,1) +Topic=2,"warrior",QuestValue(17560)=1,Count(5880)>=100,Count(5887)>=1 -> "Alright! As a matter of fact, I have one in store. Here you go!", DeleteAmount(5880,100), DeleteAmount(5887,1), SetQuestValue(17560,2), AddOutfitAddon(142,2), AddOutfitAddon(134,2), EffectOpp(13) +Topic=2,"warrior",QuestValue(17560)=1 -> "Great! Simply bring me 100 iron ore and one royal steel and I will happily forge it for you." +Topic=2,"warrior",QuestValue(17560)=2 -> "Sorry since you already have one sword I will not forge more for you." +Topic=2 -> "Maybe another time." + +"firebird",QuestValue(17567)=4 -> "Ahh. So Duncan sent you, eh? You must have done something really impressive. Okay, take this fine sabre from me, mate.", SetQuestValue(17567,5), AddOutfitAddon(155,1), AddOutfitAddon(151,1), EffectOpp(13) +"firebird",QuestValue(17567)=5 -> "Shhh! Don't repeat the same like a parrot. That is a codeword!" + +} diff --git a/app/SabrehavenServer/data/npc/mortimer.npc b/app/SabrehavenServer/data/npc/mortimer.npc new file mode 100644 index 0000000..7d0d9c7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/mortimer.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angus: Datenbank für den Teamassitstenten der explorers society Mortimer + +Name = "Mortimer" +Outfit = (133,57-113-95-113-0) +Home = [32499,31626,07] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, what can I do for you?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "Good bye.", Idle +"farewell" -> * + +################ Später ab hier besser bd nutzen + +@"explorer.ndb" + +################ + +"mission",QuestValue(300)=12,QuestValue(320)<1 -> "With the objects you've provided our researchers will make steady progress. Still we are missing some test results from fellow explorers ...", "Please travel to our base in Port Hope and ask them to mail us their latest research reports. Then return here and ask about new missions.",SetQuestValue(320,2) + +"research","reports",QuestValue(320)=1 -> "Oh, yes! Tell our fellow explorer that the papers are in the mail already.",SetQuestValue(320,3) +"mission",QuestValue(320)=1 -> * + +##### +"mission",QuestValue(320)=4 -> "The reports from Port Hope have already arrived here and our progress is astonishing. We think it is possible to create an astral bridge between our bases. Are you interested to assist us with this?",topic=33 +##### +"no",topic=33 -> "Perhaps you are interested some other time." +"yes",topic=33 -> "Good, just take this spectral essence and use it on the strange carving in this building as well as on the corresponding tile in our base at Port Hope ...", "As soon as you have charged the portal tiles that way, report about the spectral portals.", Create(4840),SetQuestValue(320,5) + + +##### topic 34 verwendet + +} + + + diff --git a/app/SabrehavenServer/data/npc/morun.npc b/app/SabrehavenServer/data/npc/morun.npc new file mode 100644 index 0000000..9fe2d2b --- /dev/null +++ b/app/SabrehavenServer/data/npc/morun.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# morun.npc: Datenbank für die Stadtwache Morun in Darashia + +Name = "Morun" +Outfit = (129,95-5-26-76-0) +Home = [33240,32393,6] +Radius = 2 + +Behaviour = { +@"guards-darama.ndb" +} diff --git a/app/SabrehavenServer/data/npc/mugluf.npc b/app/SabrehavenServer/data/npc/mugluf.npc new file mode 100644 index 0000000..b85526f --- /dev/null +++ b/app/SabrehavenServer/data/npc/mugluf.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mugluf.npc: Datenbank für den Fleischhändler Mugluf + +Name = "Mugluf" +Outfit = (128,95-0-0-96-0) +Home = [33220,32416,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, seeker of delicacies." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will talk to you as soon as I finished this deal, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May Daraman's wisdom enlighten your soul." + +"bye" -> "May Daraman's wisdom enlighten your soul.", Idle +"name" -> "I am known as Mugluf the younger." +"job" -> "I sell meat, ham and salmon." +"time" -> "Time is nothing but another illusion." +"caliph" -> "Caliph Kazzan is our worldly leader." +"kazzan" -> * +"ferumbras" -> "Who might that be?" +"noodles" -> "That must be an important Thaian noble. Regulary Darashian delicacies are sent for him to Thais." +"excalibug" -> "O seeker of artifacts, I have no need for other weapons then a fork, a spoon, and a knife." +"thais" -> "I think we have some kind of trade agreements with them." +"tibia" -> "The world is an illusion, don't get trapped in it." +"carlin" -> "This town must be far away, we rarely even hear about it here." +"news" -> "People say there is a dark cloud gathering in the west." +"rumour" -> * +"desert" -> "It's a challenge to body and soul. Praised be Daraman to have brought us here to grow on this constant test." + +"offer" -> "I can offer you meat, ham, and salmon." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have meat, ham, and salmon." + +"meat" -> Type=3577, Amount=1, Price=7, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=10, "Do you want to buy a ham for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=6, "Do you want to buy a salmon for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=7*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=10*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=6*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/muriel.npc b/app/SabrehavenServer/data/npc/muriel.npc new file mode 100644 index 0000000..350602a --- /dev/null +++ b/app/SabrehavenServer/data/npc/muriel.npc @@ -0,0 +1,153 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# muriel.npc: Datenbank für den Magier Muriel + +Name = "Muriel" +Outfit = (130,115-94-97-57-0) +Home = [32296,32263,7] +Radius = 2 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> * +ADDRESS,"hello$",! -> "Greetings, %N! Looking for wisdom and power, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the second sorcerer. I am selling spellbooks and spells." +"name" -> "You may call me Muriel." +"time" -> "Time is unimportant." +"king" -> "The king is a patron of the arcane arts." +"tibianus" -> * +"quentin" -> "He has some minor magic powers." +"lynda" -> "Pretty and compentent." +"harkath" -> "He's not as dumb as the average fighter but a warrior nonetheless." +"army" -> "We supply the army with some sorcerer recruits now and then." +"general" -> * +"ferumbras" -> "I wonder how he actually got this awesome powers." +"sam" -> "A simple smith." +"xodet" -> "He has our permission to sell mana fluids." +"frodo" -> "A bar is no place that suits a scholar like me." +"elane" -> "She is quite proud of her puny magic tricks." +"muriel" -> "I don't like jokes about my name!" +"gregor" -> "Knights! Childs with swords. Not worth of any attention." +"marvik" -> "Marvik and his Sorcerers lack spells with real power." +"bozo" -> "He's not a jester but a poor joke himself." +"baxter" -> "I don't know him." +"oswald" -> "Only his boss keeps him from being burned to ashes." +"sherry" -> "Simple farmers." +"donald" -> * +"mcronald" -> * +"lugri" -> "He is rumoured to posses some secrets our guild might find ... interesting." +"lungelen" -> "She keeps the whole wisdom of our ancestors and leads our guild." +"excalibug" -> "The enchantements on this weapon must be awesome." +"news" -> "Our guild is working on a new spell, but I won't give away any details yet." +"flaming","pit" -> "These pits, you refer to, might be the legendary 'Pits of Inferno', also known as the 'Nightmare Pits'." +"pits","inferno" -> "They are rumoured to be hidden somewhere in the Plains of Havoc, far to the east." +"nightmare","pit"-> * + +"wisdom" -> "The wisdom of spellcasting is the source of power." +"ancestor" -> "There were many generations of sorcerers in the past. Today a lot of people want to join us." +"sorcerer" -> "A sorcerer spends his lifetime studying spells to gain power." +"power" -> "Of course, power is the most important thing in the universe." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and Sorcerers." +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you can find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 +"rune" -> "Each spell, that starts with 'Ad', needs a rune. You have to hold a blank rune in one of your hands when you cast it. You can buy runes at the magic shop." +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back when you have enough money." +Topic=4 -> "Hmm, maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/muzir.npc b/app/SabrehavenServer/data/npc/muzir.npc new file mode 100644 index 0000000..bb20b4e --- /dev/null +++ b/app/SabrehavenServer/data/npc/muzir.npc @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# muzir.npc: Datenbank für den Wesir Muzir + +Name = "Muzir" +Outfit = (128,95-4-11-76-0) +Home = [33222,32390,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Daraman's blessings." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy. Please wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"job" -> "I am honoured to be the grandwezir of the caliph." +"name" -> "I am Muzir." ### Ibn ??? ### +"time" -> "It is exactly %T." +"caliph" -> "I am caretaker for the fortune of our beloved and wise caliph." +"kazzan" -> * +"daraman" -> "I take it upon me to involve myself with worldly issues for the prosperity of our community. I hope the taint of wealth does not harm my soul too much." +"wezir" -> "I am responsible for the wealth of our beloved and wise caliph. I can also change money for you." +"wealth" -> * + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/myra.npc b/app/SabrehavenServer/data/npc/myra.npc new file mode 100644 index 0000000..b2752dc --- /dev/null +++ b/app/SabrehavenServer/data/npc/myra.npc @@ -0,0 +1,237 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# myra.npc: Datenbank für die Magierlehrerin Myra + +Name = "Myra" +Outfit = (138,115-0-19-94-3) +Home = [32580,32751,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Sorry, not now.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am a sorcerer. I was sent here by the academy of Edron to function as an adviser in magical matters and as a teacher for sorcerers in need of training." +"name" -> "Myra is my name." +"time" -> "It is %T right now." +"king" -> "We are here on the behalf of the king and try our best to make this colony prosper." +"venore" -> "I find the Venoran activity here disturbing, but, after all, that's not my business." +"thais" -> "Thais lacks the lovely peace of Edron, but as the capital of the Thaian kingdom it offers more chances to study or entertain yourself than this fledgling city." +"carlin" -> "The druids of Carlin could do a lot with all the freedom they have, but they waste their resources in some strange cult and lack any scientific approach to magic." +"edron" -> "I loved my time at the academy. I had my differences with some superiors though, and when it came to select somebody to come here, my name was mentioned once too often I think." +"jungle" -> "I am working on a spell aiming specifically on destroying plant life. I am sure it would be of enormous help and would earn me a positon in the academy once more." + +"tibia" -> "I have already seen more of the world as I had ever planned." + +"kazordoon" -> "I would have even preferred an appointment to the dark halls of Kazordoon than to this colony." +"dwarves" -> "Dwarves are good miners, I can't say much more about them." +"dwarfs" -> * +"ab'dendriel" -> "Elves would probably be more suitable to this environment." +"elves" -> * +"elfs" -> * +"darama" -> "I think all this talk about the conquest of a new continent is simply exaggerated." +"darashia" -> "Living in the desert must be even worse than living here." +"ankrahmun" -> "Although I'd love to study the undeath more closely, I'd not want to study it first hand." +"ferumbras" -> "He wastes all his power to spread terror and destruction. Doesn't this become boring after a while?" +"excalibug" -> "The magic used to create that weapon would be more interesting than the weapon itself." +"apes" -> "They are annoying but easily driven away." +"lizard" -> "The lizards are somewhat mysterious, but who would care to travel through the whole cursed jungle to learn the boring secrets of some fly-eaters?" +"dworcs" -> "Sooner or later we will have to face this threat in the south." + +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and druids." +"rune" -> "Each spell, that starts with 'Ad', needs a rune. You have to hold a blank rune in one of your hands when you cast it. You can buy runes at the magic shop." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." + +"outfit" -> "This Tiara is an award by the academy of Edron in recognition of my service here." +"addon" -> * + +"task",QuestValue(17547)=0 -> "Well... maybe, if you help me a little, I could convince the academy of Edron that you are a valuable help here and deserve an award too. How about it?", Topic=4 +"mission",QuestValue(17547)=0 -> * +"tiara",QuestValue(17547)=0 -> * +Topic=4,"yes" -> "Okay, great! You see, I need a few magical ingredients which I've run out of. First of all, please bring me 70 bat wings. ...", + "Then, I urgently need a lot of red cloth. I think 20 pieces should suffice. ...", + "Oh, and also, I could use a whole load of ape fur. Please bring me 40 pieces. ...", + "After that, um, let me think... I'd like to have some holy orchids. Or no, many holy orchids, to be safe. Like 35. ...", + "Then, 10 spools of spider silk yarn, 60 lizard scales and 40 red dragon scales. ...", + "I know I'm forgetting something.. wait... ah yes, 15 ounces of magic sulphur and 30 ounces of vampire dust. ...", + "That's it already! Easy task, isn't it? I'm sure you could get all of that within a short time. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=5 +Topic=4 -> "However." +Topic=5,"yes" -> "Fine! Let's start with the 70 bat wings. I really feel uncomfortable out there in the jungle.", SetQuestValue(17547,1), SetQuestValue(17594,1) +Topic=5 -> "Maybe another time." + +"bat","wing",QuestValue(17547)=1 -> Type=5894, Amount=70, "Oh, did you bring the 70 bat wings for me?", Topic=6 +"mission",QuestValue(17547)=1 -> * +"task",QuestValue(17547)=1 -> * +Topic=6,"yes",Count(Type)>=Amount -> "Thank you! I really needed them for my anti-wrinkle lotion. Now, please bring me 20 pieces of red cloth.", Delete(Type), SetQuestValue(17547,2) +Topic=6,"yes" -> "You don't have that many." +Topic=6 -> "Maybe another time." + +"red","cloth",QuestValue(17547)=2 -> Type=5911, Amount=20, "Have you found 20 pieces of red cloth?", Topic=7 +"mission",QuestValue(17547)=2 -> * +"task",QuestValue(17547)=2 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Great! This should be enough for my new dress. Don't forget to bring me 40 pieces of ape fur next!", Delete(Type), SetQuestValue(17547,3) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe another time." + +"ape","fur",QuestValue(17547)=3 -> Type=5883, Amount=40, "Were you able to retrieve 40 pieces of ape fur?", Topic=8 +"mission",QuestValue(17547)=3 -> * +"task",QuestValue(17547)=3 -> * +Topic=8,"yes",Count(Type)>=Amount -> "Nice job, %N. You see, I'm testing a new depilation cream. I guess if it works on ape fur it's good quality. Next, please bring me 35 holy orchids.", Delete(Type), SetQuestValue(17547,4) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe another time." + +"holy","orchid",QuestValue(17547)=4 -> Type=5922, Amount=35, "Did you convince the elves to give you 35 holy orchids?", Topic=9 +"mission",QuestValue(17547)=4 -> * +"task",QuestValue(17547)=4 -> * +Topic=9,"yes",Count(Type)>=Amount -> "Thank god! The scent of holy orchids is simply the only possible solution against the horrible stench from the tavern latrine. Now, please bring me 10 rolls of spider silk yarn!", Delete(Type), SetQuestValue(17547,5) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe another time." + +"yarn",QuestValue(17547)=5 -> Type=5886, Amount=10, "Oh, did you bring 10 spools of spider silk yarn for me?", Topic=10 +"mission",QuestValue(17547)=5 -> * +"task",QuestValue(17547)=5 -> * +Topic=10,"yes",Count(Type)>=Amount -> "I appreciate it. My pet doggie manages to bite through all sorts of leashes, which is why he is always gone. I'm sure this strong yarn will keep him. Now, go for the 60 lizard scales!", Delete(Type), SetQuestValue(17547,6) +Topic=10,"yes" -> "You don't have that many." +Topic=10 -> "Maybe another time." + +"lizard","scale",QuestValue(17547)=6 -> Type=5881, Amount=60, "Have you found 60 lizard scales?", Topic=11 +"mission",QuestValue(17547)=6 -> * +"task",QuestValue(17547)=6 -> * +Topic=11,"yes",Count(Type)>=Amount -> "Good job. They will look almost like sequins on my new dress. Please go for the 40 red dragon scales now.", Delete(Type), SetQuestValue(17547,7) +Topic=11,"yes" -> "You don't have that many." +Topic=11 -> "Maybe another time." + +"red","dragon","scale",QuestValue(17547)=7 -> Type=5882, Amount=40, "Were you able to get all 40 red dragon scales?", Topic=12 +"mission",QuestValue(17547)=7 -> * +"task",QuestValue(17547)=7 -> * +Topic=12,"yes",Count(Type)>=Amount -> "Thanks! They make a pretty decoration, don't you think? Please bring me 15 ounces of magic sulphur now!", Delete(Type), SetQuestValue(17547,8) +Topic=12,"yes" -> "You don't have that many." +Topic=12 -> "Maybe another time." + +"magic","sulphur",QuestValue(17547)=8 -> Type=5904, Amount=15, "Have you collected 15 ounces of magic sulphur?", Topic=13 +"mission",QuestValue(17547)=8 -> * +"task",QuestValue(17547)=8 -> * +Topic=13,"yes",Count(Type)>=Amount -> "Ah, that's enough magic sulphur for my new peeling. You should try it once, your skin gets incredibly smooth. Now, the only thing I need is vampire dust. 30 ounces will suffice.", Delete(Type), SetQuestValue(17547,9) +Topic=13,"yes" -> "You don't have that many." +Topic=13 -> "Maybe another time." + +"vampire","dust",QuestValue(17547)=9 -> Type=5905, Amount=30, "Have you gathered 30 ounces of vampire dust?", Topic=14 +"mission",QuestValue(17547)=9 -> * +"task",QuestValue(17547)=9 -> * +Topic=14,"yes",Count(Type)>=Amount -> "Ah, great. Now I can finally finish the potion which the academy of Edron asked me to. I guess, now you want your reward, don't you?", Delete(Type), SetQuestValue(17547,10), Topic=15 +Topic=14,"yes" -> "You don't have that many." +Topic=14 -> "Maybe another time." + +Topic=15,"yes" -> "I thought so. Go to the academy of Edron and tell Zoltan that I sent you. I will send a nomination to him. You were really a great help. Thanks again!", SetQuestValue(17547,11) +Topic=15 -> "Ok, no reward for you then. However, thank you for yours help!" + +"mission",QuestValue(17547)=10 -> "I guess, you want your reward, don't you?", Topic=16 +"task",QuestValue(17547)=10 -> * +"reward",QuestValue(17547)=10 -> * +Topic=16,"yes" -> "I thought so. Go to the academy of Edron and tell Zoltan that I sent you. I will send a nomination to him. You were really a great help. Thanks again!", SetQuestValue(17547,11) +Topic=16 -> "Maybe another time." + +"mission",QuestValue(17547)=11 -> "Go to the academy of Edron and tell Zoltan that I sent you." +"task",QuestValue(17547)=11 -> * +"reward",QuestValue(17547)=11 -> * + +"mission",QuestValue(17547)=12 -> "Sorry, but I don't have any tasks available for you right now." +"task",QuestValue(17547)=12 -> * +} diff --git a/app/SabrehavenServer/data/npc/nahbob.npc b/app/SabrehavenServer/data/npc/nahbob.npc new file mode 100644 index 0000000..312231a --- /dev/null +++ b/app/SabrehavenServer/data/npc/nahbob.npc @@ -0,0 +1,172 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nahbob.npc: Datenbank für den Maridhändler Nah'bob (Waffen und Rüstungen, Marid) + +Name = "Nah'bob" +Outfit = (80,0-0-0-0-0) +Home = [33104,32520,2] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> " Another customer! I've only just sat down! What is it, %N?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Whoa, %N! Easy! Old Bob is busy right now!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Bye then." + +"bye" -> "Bye now. Visit old Bob again one day!", Idle +"farewell" -> * +"name" -> "Well, my name is Nah'bob, but I don't like it. Just call me Bob." +"Bob" -> "Yep, that's me." +"nah'bob" -> "I don't like that name. Call me Bob." +"job",male -> "I'm a trader, mate. You know... selling stuff, buying stuff... that kind of thing. Quite a hassle, actually. ...", + "If you have the permission to trade with us, check out my latest offers." +"job",female -> "I'm a trader, lady. You know... selling stuff, buying stuff... that kind of thing. Quite a hassle, actually. ...", + "If you have the permission to trade with us, check out my latest offers." +"trade",male -> "You want to buy something? Well, be my guest, Mister. Just ask me for my offers!" +"trade",female -> "You want to buy something? Well, be my guest, Lady. Just ask me for my offers!" +"permission" -> "I am not allowed to trade with you unless Gabel gave you the permission to trade with us." + +"gabel" -> "He's the boss around here. You know - the big djinn. He's all right." +"king" -> "King? We do not have kings. Not anymore." +"djinn" -> "Yes, I am a djinn. How long did it take you to notice?" +"efreet" -> "The Efreet are a bunch of freaked out loonies, believe me. Nothing but 'war!' and 'domination!' and 'world rule!' all day long. Those people shoud chill out a bit." +"marid" -> "I guess I am one. I don't know why because I'm not really into all that Daraman stuff about ascetism, you know. ...", + "But then, I don't like the Efreets' gibberish about war and world domination, either. ...", + "In the end I chose the side which seemed more tolerant towards attitudes that are a bit... different. And that was clearly the Marid. ...", + "Besides, Bo'ques chose to side with Gabel. And I would have hated never to eat his cooking again. Did you ever try his Chat d'Aramignon?" +"malor" -> "Now THAT djnn really means stress. Too bad that doofus of an orc king freed him from his prison in the lamp. Gabel should have gotten rid of Malor for good when had the chance to do so." +"mal'ouquah" -> "Mal'ouquah the Efreet's fortress. I have never seen it, and I don't feel any particular desire to do so." +"ashta'daramai" -> "Ashta'daramai is the name of this place - 'Daraman's gift'. It's actually a bit misleading, because it was not Daraman who built it. The name refer's to Daraman's teachings, you know." +"human" -> "I have nothing against humans. ...", + "Sure, there are always those who want to steal from you. ...", + "Or blackmail you. ...", + "Or torture you. ...", + "Or just kill you for no reason. ...", + "But all in all I think humans are amusing little people." +"zathroth" -> "They say Zathroth created us because he wanted us to be mindless, greedy killing machines. Well, I'm not so sure about some of the Efreet, but as for me I think he's done a lousy job." +"tibia" -> "It's a mad, mad world." +"daraman" -> "Ah yes. Daraman. The old prophet. Well - he was all right, I guess. But I am not sure I agree with him on all matters. I mean - that thing about asceticism... I think he was a bit extreme there." +"darashia" -> "They say Darashia is full of relaxed people. To me that sounds like a good place to be." +"scarab" -> "Yuk! I wouldn't eat that!" +"edron" -> "You are talking about some human place, aren't you?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I know Ankrahmun - I've been there before. Lots of pyramids and stuff. Wicked." +"pharaoh" -> "The pharaoh? I have heard about that guy. That man is as mad as a march hare." +"palace" -> "The palace in Ankrahmun used to be a place of orgies and of all kinds of debauchery. Aah - those were the days...!" +"ascension" -> "Yeah right. I don't know what it is about you humans. I have rarely met a human who did not have his head filled with some crazy religious idea." +"kha'zeel" -> "I like the Kha'zeel mountains. It is true they are boring as hell, but personally I don't mind boredom. I hate excitement. Whenever somebody said 'Oh-that's-gonna-be-so-exciting' some really, really bad thing happened." +"kha'labal" -> "Did you know that desert used to be fertile land? You should have seen it. It was all lush and verdant - a veritable paradise. ...", + "There were flowers and birds everywhere, and more delicious food anybody could ever eat. ...", + "But then that blockhead Baa'leal decided it was a good idea to burn it all down. Damn those stupid Efreet!" +"melchior" -> "Of course I remember old Melchior. He used to come up here regularly. Is he dead?", Topic=1 +Topic=1,"yes" -> "Oh. Well, I suppose that is bad. For him, anyway." +Topic=1,"no" -> "So the old skinflint is still alive? Amazing! I could have sworn the Efreet knocked the stuffing out of him. No human can fool a djinn for long, you know." +"alesar" -> "I know Alesar. Ran off to the Efreet, didn't he? Weird story. Always used to be such fanatic followers of Daraman, he and his brother. ...", + "I guess that happens when you get all freaky about some silly idea. I'm sure that wouldn't have happened if he had taken a more relaxed approach to things. ...", + "I remember how I always told him to chill out. But he was stubborn as a headstrong donkey. Just like his brother." +"brother" -> "Haroun is Alesar's brother. Don't talk to him about it, though. Haroun may be a zealous fool, but he's all right. He has suffered enough." +"fa'hradin" -> "I think Fa'hradin has stopped taking things too seriously long time ago. You just got to love the old cynic." +"bo'ques" -> "Honestly, if I did not have any other reason for hanging out here Bo'ques's cooking would make me stay." +"djem" -> "The little girl djema is bored out of her mind here so she often comes round. We've always had a barrel of laughs together, she and me - playing chess, telling stories, playing tricks on Haroun. ...", + "I really like her. I will miss her lots when she's gone. Hey! Don't tell her I have said that, ok?" +"haroun" -> "Ah yes - Haroun. My esteemed fellow trader. Stiff like a butler and boring like a tax collector. He is a devout follower of Daraman, but I think he seriously misunderstood his teachings. ...", + "I don't think it was the old prophet's intention to rid this world of laughter and of smiles." +"baa'leal" -> "Baa'leal? Of course I remember old bulldog face. I wasn't surprised at all when I heard that he sided with Malor. Wasn't a great loss anyway. ...", + "He may be one mean djinn, but he is thicker than a senile ox. To think Malor made him his lieutenant !" + +"wares" -> "My job around here is to buy and sell weapons, armors, helmets, legs, and shields." +"offer" -> * +"goods" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"weapon" -> "I'm selling and buying spike swords, war hammers and obsidian lances. Furthermore I would buy fire swords, ice rapiers, dragon lances, fire axes and broadswords from you, if you have any." +"shield" -> "I'm just selling beholder shields. But I'm buying guardian shields, dragon shields, beholder shields, crown shields and phoenix shields." +"armor" -> "I'm selling and buying noble armors. Furthermore I'm buying crown armors and blue robes." +"helmet" -> "At this time I'm not selling any helmets. I'm only buying crown helmets, crusader helmets and royal helmets." +"trousers" -> "At this time I'm only buying crown legs. Oh, and I'm also looking for boots of haste!" +"legs" -> * + +"spike","sword" -> Type=3271, Amount=1, Price=8000, "Do you want to buy a spike sword for %P gold?", Topic=10 +"war","hammer" -> Type=3279, Amount=1, Price=10000, "Do you want to buy a war hammer for %P gold?", Topic=10 +"obsidian","lance" -> Type=3313, Amount=1, Price=3000, "Do you want to buy an obsidian lance for %P gold?", Topic=10 +"beholder","shield" -> Type=3418, Amount=1, Price=7000, "Do you want to buy a beholder shield for %P gold?", Topic=10 +"noble","armor" -> Type=3380, Amount=1, Price=8000, "Do you want to buy a noble armor for %P gold?", Topic=10 + +%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=8000*%1, "Do you want to buy %A spike swords for %P gold?", Topic=10 +%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=10000*%1, "Do you want to buy %A war hammers for %P gold?", Topic=10 +%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=3000*%1, "Do you want to buy %A obsidian lances for %P gold?", Topic=10 +%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=7000*%1, "Do you want to buy %A beholder shields for %P gold?", Topic=10 +%1,1<%1,"noble","armor" -> Type=3380, Amount=%1, Price=8000*%1, "Do you want to buy %A noble armors for %P gold?", Topic=10 + +"sell","spike","sword" -> Type=3271, Amount=1, Price=1000, "Do you want to sell a spike sword for %P gold?", Topic=11 +"sell","fire","sword" -> Type=3280, Amount=1, Price=4000, "Do you want to sell a fire sword for %P gold?", Topic=11 +"sell","war","hammer" -> Type=3279, Amount=1, Price=1200, "Do you want to sell a war hammer for %P gold?", Topic=11 +"sell","ice","rapier" -> Type=3284, Amount=1, Price=1000, "Do you want to sell an ice rapier for %P gold?", Topic=11 +"sell","broad","sword" -> Type=3301, Amount=1, Price=500, "Do you want to sell a broad sword for %P gold?", Topic=11 +"sell","dragon","lance" -> Type=3302, Amount=1, Price=9000, "Do you want to sell a dragon lance for %P gold?", Topic=11 +"sell","obsidian","lance" -> Type=3313, Amount=1, Price=500, "Do you want to sell an obsidian lance for %P gold?", Topic=11 +"sell","fire","axe" -> Type=3320, Amount=1, Price=8000, "Do you want to sell a fire axe for %P gold?", Topic=11 + +"sell","guardian","shield" -> Type=3415, Amount=1, Price=2000, "Do you want to sell a guardian shield for %P gold?", Topic=11 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=4000, "Do you want to sell a dragon shield for %P gold?", Topic=11 +"sell","beholder","shield" -> Type=3418, Amount=1, Price=1200, "Do you want to sell a beholder shield for %P gold?", Topic=11 +"sell","crown","shield" -> Type=3419, Amount=1, Price=8000, "Do you want to sell a crown shield for %P gold?", Topic=11 +"sell","phoenix","shield" -> Type=3439, Amount=1, Price=16000, "Do you want to sell a phoenix shield for %P gold?", Topic=11 + +"sell","noble","armor" -> Type=3380, Amount=1, Price=900, "Do you want to sell a noble armor for %P gold?", Topic=11 +"sell","crown","armor" -> Type=3381, Amount=1, Price=12000, "Do you want to sell a crown armor for %P gold?", Topic=11 +"sell","crown","legs" -> Type=3382, Amount=1, Price=12000, "Do you want to sell a pair of crown legs for %P gold?", Topic=11 +"sell","crown","helmet" -> Type=3385, Amount=1, Price=2500, "Do you want to sell a crown helmet for %P gold?", Topic=11 +"sell","crusader","helmet" -> Type=3391, Amount=1, Price=6000, "Do you want to sell a crusader helmet for %P gold?", Topic=11 +"sell","royal","helmet" -> Type=3392, Amount=1, Price=30000, "Do you want to sell a royal helmet for %P gold?", Topic=11 +"sell","blue","robe" -> Type=3567, Amount=1, Price=10000, "Do you want to sell a blue robe for %P gold?", Topic=11 +"sell","boots","of","haste" -> Type=3079, Amount=1, Price=30000, "Do you want to sell a boots of haste for %P gold?", Topic=11 + +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=1000*%1, "Do you want to sell %A spike swords for %P gold?", Topic=11 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=4000*%1, "Do you want to sell %A fire swords for %P gold?", Topic=11 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=1200*%1, "Do you want to sell %A war hammers for %P gold?", Topic=11 +"sell",%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=1000*%1, "Do you want to sell %A ice rapiers for %P gold?", Topic=11 +"sell",%1,1<%1,"broad","sword" -> Type=3301, Amount=%1, Price=500*%1, "Do you want to sell %A broad swords for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","lance" -> Type=3302, Amount=%1, Price=9000*%1, "Do you want to sell %A dragon lances for %P gold?", Topic=11 +"sell",%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=500*%1, "Do you want to sell %A obsidian lances for %P gold?", Topic=11 +"sell",%1,1<%1,"fire","axe" -> Type=3320, Amount=%1, Price=8000*%1, "Do you want to sell %A fire axes for %P gold?", Topic=11 + +"sell",%1,1<%1,"guardian","shield" -> Type=3415, Amount=%1, Price=2000*%1, "Do you want to sell %A guardian shields for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=4000*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=11 +"sell",%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=1200*%1, "Do you want to sell %A beholder shields for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","shield" -> Type=3419, Amount=%1, Price=8000*%1, "Do you want to sell %A crown shields for %P gold?", Topic=11 +"sell",%1,1<%1,"phoenix","shield" -> Type=3439, Amount=%1, Price=16000*%1, "Do you want to sell %A phoenix shields for %P gold?", Topic=11 + +"sell",%1,1<%1,"noble","armor" -> Type=3380, Amount=%1, Price=900*%1, "Do you want to sell %A noble armors for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","armor" -> Type=3381, Amount=%1, Price=12000*%1, "Do you want to sell %A crown armors for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","legs" -> Type=3382, Amount=%1, Price=12000*%1, "Do you want to sell %A pairs of crown legs for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","helmet" -> Type=3385, Amount=%1, Price=2500*%1, "Do you want to sell %A crown helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"crusader","helmet" -> Type=3391, Amount=%1, Price=6000*%1, "Do you want to sell %A crusader helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"royal","helmet" -> Type=3392, Amount=%1, Price=30000*%1, "Do you want to sell %A royal helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"blue","robe" -> Type=3567, Amount=%1, Price=10000*%1, "Do you want to sell %A blue robes for %P gold?", Topic=11 +"sell",%1,1<%1,"boots","of","haste" -> Type=3079, Amount=%1, Price=30000*%1, "Do you want to sell %A boots of haste for %P gold?", Topic=11 + +Topic=10,QuestValue(283)<3,! -> "I'm sorry, pal. But you need Gabel's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Ok. Here you are!", DeleteMoney, Create(Type) +Topic=10,"yes" -> "Well, come back if you have enough gold." +Topic=10 -> "Well, obviously not." + +Topic=11,QuestValue(283)<3,! -> "I'm sorry, pal. You don't have Gabel's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Ok. Here is your gold.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one!" +Topic=11,"yes",Amount>1 -> "You do not have that many!" +Topic=11 -> "Well, obviously not." +} diff --git a/app/SabrehavenServer/data/npc/nelliem.npc b/app/SabrehavenServer/data/npc/nelliem.npc new file mode 100644 index 0000000..fe7676c --- /dev/null +++ b/app/SabrehavenServer/data/npc/nelliem.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Nelliem.npc: Datenbank für den Gartenbedarfhändler Nelliem + +Name = "Nelliem" +Outfit = (160,115-100-105-76-0) +Home = [32883,32086,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N, traveller from afar..." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hold on, %N, I am busy. Just stand in the line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Go...! Learn the secret to green thumbs and may Crunor be good to you..." + +"bye" -> "Go...! Learn the secret to green thumbs and may Crunor be good to you...", Idle +"farewell" -> * +"job" -> "To keep my thumbs green and to sell our garden equipment, as you can see on that shelves." +"crunor" -> "May he bless all plants." +"name" -> "I am Nelliem." +"time" -> "It's a good time to sow some seeds." + +"equipment" -> "I sell shovels, picks, scythes, machetes, ropes, pitchforks, rakes, hoes, brooms, fishing rods, sixpacks of worms and brandnew crowbars from Kazordoon." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"shovel" -> Type=3457, Amount=1, Price=20, "Do you want to buy a shovel for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=25, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a dwarfensteel crowbar for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"pitchfork" -> Type=3451, Amount=1, Price=25, "Do you want to buy a pitchfork for %P gold?", Topic=1 +"rake" -> Type=3452, Amount=1, Price=20, "Do you want to buy a rake for %P gold?", Topic=1 +"hoe" -> Type=3455, Amount=1, Price=15, "Do you want to buy a hoe for %P gold?", Topic=1 +"broom" -> Type=3454, Amount=1, Price=12, "Do you want to buy a broom for %P gold?", Topic=1 + +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=20*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=25*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"pitchfork" -> Type=3451, Amount=%1, Price=25*%1, "Do you want to buy %A pitchforks for %P gold?", Topic=1 +%1,1<%1,"rake" -> Type=3452, Amount=%1, Price=20*%1, "Do you want to buy %A rakes for %P gold?", Topic=1 +%1,1<%1,"hoe" -> Type=3455, Amount=%1, Price=15*%1, "Do you want to buy %A hoes for %P gold?", Topic=1 +%1,1<%1,"broom" -> Type=3454, Amount=%1, Price=12*%1, "Do you want to buy %A brooms for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No money, no deal!" +Topic=1 -> "Then not." +} diff --git a/app/SabrehavenServer/data/npc/nemal.npc b/app/SabrehavenServer/data/npc/nemal.npc new file mode 100644 index 0000000..7de21ac --- /dev/null +++ b/app/SabrehavenServer/data/npc/nemal.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nemal.npc: Datenbank für den Blinden Nemal (Desert) + +Name = "Nemal" +Outfit = (129,57-97-45-115-0) +Home = [32615,32108,10] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello. How are you? Maybe you can help me: Do my shoes have the same colour?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Is there someone else? Step closer!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Where are you? Are you gone?" + +"bye" -> "Farewell!", Idle +"farewell" -> "May the light be with you!", Idle +"name" -> "My name is Nemal." +"job" -> "I have no job. I'm a wanderer." +"thais" -> "Yes, I used to live there a while ago. Nice town." +"carlin" -> "Aaaahh, Carlin, yes. I know this town. Strange graveyard there, isn't it?" +"king" -> "King...king...yes, there is one. I still can remember the time when we had an other king. What was his name again?" +"weapon" -> "I always have weapons with me. You never know what's behind the next corner." +"help" -> "Hmm.. help? How can I help you?" +"time" -> "I had a watch. It was nice, but I can't see the hands anymore." +"sword" -> "I have a sword. It's very sharp. But I don't give it away, you never know." +"desert" -> "Desert? Where is one?" +"excalibug" -> "I heard the name, but I don't trust rumours." +"fight" -> "Better know how to fight!" +"guild" -> "Hmmm.. I wouldn't ever join a guild, but i know of the existance of some." +"god" -> "I don't believe in gods, but a lot of people do. I never saw a god, why should I trust in someone I never met?" +"way" -> "There are many ways. I don't know all of them." +"door" -> "Closed doors need keys. What a pity keys are not lying around like rubbish!" +"library" -> "I can't read or write. What use would I have of a library?" +"secret" -> "Secrets should remain secret. No need of making them public." +"treasure" -> "You can find some treasures here - some are bigger, some are smaller, some are of true value, some are of materialistic value." +"book" -> "I can't read. I've never learned it." +"gharonk" -> "My father knew this language." +"offer" -> "I don't sell things. If you really need something, better walk straight to one of the towns or ask another adventurer." +"blind" -> "Yes, I seem to be blind. But I am not sure - maybe the dungeons are too dark!" + +"potion","regain","vision" -> "I heard of a potion of regained vision ... but I can't remember how to make it! Maybe you can help me. Do you know something about it?", Topic=2 +Topic=2,"yes" -> "So, did you bring the ingredients with you, stranger?", Topic=3 +Topic=2 -> "Oh. Maybe someone else could do it, then." +Topic=3,"yes",Count(3601)>0,Count(3603)>0,Count(3604)>0,Count(3658)>0,Count(3590)>0 -> "You seem to have them with you. Can you tell me, how many minutes I have to cook them?", Amount=1, Delete(3601), Delete(3603), Delete(3604), Delete(3658), Delete(3590), Topic=4 +Topic=3,"yes" -> "It doesn't seem to me as if you have the correct ingredients with you, stranger!" +Topic=3 -> "Maybe you can find them!" +Topic=4,"31" -> "Ah. It seems to work. But what are the words I have to speak?", Topic=5 +Topic=4 -> "Oh no, I don't think this is right." +Topic=5,"nalus","murtu" -> "Thank you! NALUS MURTUUU! ... I can see again! To show you, how grateful I am, I'll give you a key. Be wise when using it. I can't tell you, where it matches, but ... take good care, it is useless without mental powers!", Data=4037, Create(2969) +Topic=5 -> "Oh no, I don't think these are the right words." +} diff --git a/app/SabrehavenServer/data/npc/nezil.npc b/app/SabrehavenServer/data/npc/nezil.npc new file mode 100644 index 0000000..03067a2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/nezil.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nezil.npc: Datenbank für den Händler Nezil + +Name = "Nezil" +Outfit = (160,115-78-116-57-0) +Home = [32661,31912,9] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$","nezil",! -> "Hiho %N, Nezil at your service." +ADDRESS,"hi$","nezil",! -> * +ADDRESS,"hiho$","nezil",! -> * +ADDRESS,"hello$",! -> "Uhm, me or my sis', %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","nezil",! -> "Hey %N, I am busy. Stand in line.", Queue +BUSY,"hi$","nezil",! -> * +BUSY,"hiho$","nezil",! -> * +BUSY,"hello$",! -> "Uhm, me or my sis', %N?", Idle +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"hello$","bezil" -> "Good bye.", Idle +"hi$","bezil" -> * +"hiho$","bezil" -> * +"bye" -> * +"farewell" -> * +"job" -> "We sell equipment of all kinds. Is there anything you need?" +"equipment" -> "We sell shovels, picks, scythes, bags, ropes, backpacks, cups, scrolls, documents, parchments, and watches. We also sell lightsources." +"goods" -> * +"light" -> "We sell torches, candlesticks, candelabra, and oil." +"name" -> "I am Nezil Whetstone, son of Fire, of the Savage Axes. I and my sis' Bezil are selling stuff, ye' know?" +"bezil" -> "She's my sis'." +"time" -> "I think it's about %T. If you'd bought a watch you'd know for sure." +"food" -> "Sorry, visit the Jolly Axeman Tavern for that." + +"goods" -> "Let me see ... we have shovels, picks, scythes, bags, ropes, backpacks, scrolls, documents, parchments, watches, fishing rods, sixpacks of worms and some lightsources." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2862, Amount=1, Price=4, "Do you wanna buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you wanna buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you wanna buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you wanna buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=40, "Do you wanna buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you wanna buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you wanna buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you wanna buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you wanna buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you wanna buy a dwarfensteel crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you wanna buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you wanna buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you wanna buy a bottle for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you wanna buy a water hose for %P gold?", Topic=1 +"oil" -> Type=2874, Amount=1, Price=20, Data=7, "Do you wanna buy oil for %P gold?", Topic=2 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2862, Amount=%1, Price=4*%1, "Do you wanna buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you wanna buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you wanna buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2870, Amount=%1, Price=10*%1, "Do you wanna buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=40*%1, "Do you wanna buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you wanna buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you wanna buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you wanna buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you wanna buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you wanna buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you wanna buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you wanna buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you wanna buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Amount=%1, Price=10*%1, Data=1, "Do you wanna buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Amount=%1, Price=20*%1, Data=7, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here, catch it!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Nice joke, pauper!" +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Nice joke, pauper!" +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you are not having one." +Topic=3 -> "Maybe next time." + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." +} diff --git a/app/SabrehavenServer/data/npc/noodles.npc b/app/SabrehavenServer/data/npc/noodles.npc new file mode 100644 index 0000000..4343787 --- /dev/null +++ b/app/SabrehavenServer/data/npc/noodles.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# noodles.npc: Datenbank für den Pudel Noodles + +Name = "Noodles" +Outfit = (32,0-0-0-0-0) +Home = [32315,32178,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> Type=3577, Amount=1," Woof! " +ADDRESS,"hi$",! -> * +ADDRESS,! -> "Grrrr!", Idle +BUSY,"hi$",! -> "Grrr! Woof!" +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Woof?? " + +"bye" -> "Woof! ", Idle +"farewell" -> * +"how","are","you"-> "Wooooof! " +"king" -> * +"tibianus" -> * +"cat$" -> "GRRRRRRR! WOOOOOOF! WOOOOOF! WOOOOOF!" +"queen" -> * +"eloise" -> * + + +"pork",Count(Type)>=Amount -> "Woof! Woof! ", Delete(Type) + +"sniff","banana", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","banana", QuestValue(233)=7, QuestValue(251)=1 -> Type=3104, Amount=1," ",Topic=2 +"sniff","fur", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","fur", QuestValue(233)=7, QuestValue(251)=1 -> Type=3105, Amount=1," ",Topic=2 +"sniff","cheese", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=1 -> Type=3120, Amount=1," ",Topic=3 + + +"sniff","banana", QuestValue(233)=7, QuestValue(251)=2 -> Type=3104, Amount=1," ",Topic=2 +"sniff","fur", QuestValue(233)=7, QuestValue(251)=2 -> Type=3105, Amount=1," ",Topic=3 +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=2 -> Type=3120, Amount=1," ",Topic=2 + +"sniff","banana", QuestValue(233)=7, QuestValue(251)=3 -> Type=3104, Amount=1," ",Topic=3 +"sniff","fur", QuestValue(233)=7, QuestValue(251)=3 -> Type=3105, Amount=1," ",Topic=2 +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=3 -> Type=3120, Amount=1," ",Topic=2 + + + +topic=2,"like","that",Count(Type)>=Amount -> "Woof!" +topic=3,"like","that",Count(Type)>=Amount -> "Meeep! Grrrrr! ",SetQuestValue(233,8),Idle + + +"ferumbras" -> "Meeep! Meeep!",Idle +"th" -> "" +"ar" -> "Woof!" +"bo" -> "" +"an" -> "Grrrr!" +"go" -> "Woof! Woof!" +} diff --git a/app/SabrehavenServer/data/npc/norbert.npc b/app/SabrehavenServer/data/npc/norbert.npc new file mode 100644 index 0000000..7df4be5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/norbert.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norbert.npc: Datenbank für den Schneider Norbert + +Name = "Norbert" +Outfit = (128,6-79-93-14-0) +Home = [32953,32103,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",Level>40,! -> "Wow! The great %N visiting our shop! LOOK PEOPLE, LOOK HERE!." +ADDRESS,"hi$",Level>40,! -> * +ADDRESS,"hello$",! -> "Welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute with this customer here, dear %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"name" -> "I am Norbert." +"job" -> "I am a salesperson here, but one day I might become a tailor, or a supermodel perhaps!" +"time" -> "Now it's %T. Did you notice this is a xelor watch I am wearing?" +"xelor" -> "Xelor, the dwarf of the chromancers guild, makes the most stylish watches in all the land." +"king" -> "Even the king of Thais, blessed be his name, can't buy a better wardrobe then ours." +"tibianus" -> * +"army" -> "I don't think they dress that well." +"ferumbras" -> "Those evil mages dress so ugly." +"excalibug" -> "I fear such a weapon will ruin a silk shirt with one blow." +"thais" -> "Thaian wear is not that stylish anymore." +"tibia" -> "Our tailors are influenced by styles of the whole known world." +"carlin" -> "Women could do better then to wear armor. Women in leather scare my in particular." +"hugo" -> "He's our boss, a great tailor and designer." +"chief" -> * +"news" -> "I heared the colour of the next season will be orange." +"rumour" -> * +"rumor" -> * + +"offer" -> "I sell very stylish clothes indeed." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"clothes" -> "I have wonderful jackets, coats, lovely doublets, even warlike leather armor, and impressive studded armor." + +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=12, "Oh, do you want to buy one of my wonderful jackets for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"studded","armor" -> Type=3378, Amount=1, Price=90, "Do you want to buy a studded armor for %P gold?", Topic=1 + +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=12*%1, "Oh, do you want to buy %A of my wonderful jackets for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=90*%1, "Do you want to buy %A studded armors for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough money." +Topic=1 -> "What a pity, perhaps next time." +} diff --git a/app/SabrehavenServer/data/npc/norf.npc b/app/SabrehavenServer/data/npc/norf.npc new file mode 100644 index 0000000..2f4a5ca --- /dev/null +++ b/app/SabrehavenServer/data/npc/norf.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norf.npc: Datenbank für den Mönch Norf + +Name = "Norf" +Outfit = (57,0-0-0-0-0) +Home = [32346,32363,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, Pilgrim." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am here to provide one of the five blessings." +"name" -> "My name is Norf." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "Here in the whiteflower temple you may receive the blessing of spiritual shielding. But we must ask of you to sacrifice 10.000 gold. Are you still interested?",Price=10000, Topic=5 +"shielding" -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." + +"fire" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(104) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the shielding of your spirit, pilgrim.", DeleteMoney, EffectOpp(13),SetQuestValue(104,1), Bless(1) +Topic=5,! -> "Ok. Suits me." + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." + +} diff --git a/app/SabrehavenServer/data/npc/norma.npc b/app/SabrehavenServer/data/npc/norma.npc new file mode 100644 index 0000000..50f9e3f --- /dev/null +++ b/app/SabrehavenServer/data/npc/norma.npc @@ -0,0 +1,184 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norma.npc: Datenbank für die Händlerin Norma (Newbie) + +Name = "Norma" +Outfit = (136,78-76-72-96-2) +Home = [32098,32180,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "I'm sorry %N, but I only serve premium account customers.", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Not now, not now, sorry %N. Please wait a moment.", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "I'm sorry %N, but I only serve premium account customers." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant. What can I do for you?" +"name" -> "My name is Norma. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is safe from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only some dared to take the risk, and a risk it was!" +"dallheim" -> "Some call him a hero." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich woman!" +"thais" -> "Thais is a crowded town." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a dublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "Sorry, I fear Al Dee owns the last ones on this isle." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +"addon",QuestValue(18501)=0 -> "Pretty, isn't it? I made it myself, but I could teach you how to do that if you like. What do you say?", Topic=3 +"hat",QuestValue(18501)=0 -> * +Topic=3,"yes" -> "Okay, here we go, listen closely! I need a few things... a basic hat of course, maybe a legion helmet would do. Then about 100 chicken feathers...", + "and 50 honeycombs as glue. That's it, come back to me once you gathered it!", SetQuestValue(18501,1), SetQuestValue(17594,1) +Topic=3 -> "Maybe another time." + +"addon",QuestValue(18501)=1 -> "Oh, you're back already? Did you bring a legion helmet, 100 chicken feathers and 50 honeycombs?", Topic=4 +"hat",QuestValue(18501)=1 -> * +Topic=4,"yes",Count(3374)>=1,Count(5890)>=100,Count(5902)>=50 -> "Great job! That must have taken a lot of work. Okay, you put it like this... then glue like this... here!", DeleteAmount(3374,1), DeleteAmount(5890,100), DeleteAmount(5902,50), SetQuestValue(18501,2), AddOutfitAddon(136,2), AddOutfitAddon(128,2), EffectOpp(13) +Topic=4,"yes" -> "You don't have required ingredients." +Topic=4 -> "Maybe another time." + +"addon",QuestValue(18501)>1 -> "I hope you enjoy the hat!" +"hat",QuestValue(18501)>1 -> * +} diff --git a/app/SabrehavenServer/data/npc/norris.npc b/app/SabrehavenServer/data/npc/norris.npc new file mode 100644 index 0000000..9fe1c49 --- /dev/null +++ b/app/SabrehavenServer/data/npc/norris.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Norris" +Outfit = (128,57-37-116-76-0) +Home = [32224,32770,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "What do you want, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'm busy now, %N!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye!" + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/nydala.npc b/app/SabrehavenServer/data/npc/nydala.npc new file mode 100644 index 0000000..1dbd84f --- /dev/null +++ b/app/SabrehavenServer/data/npc/nydala.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nydala.npc: Möbelverkäuferin Nydala in Carlin + +Name = "Nydala" +Outfit = (136,76-86-86-96-0) +Home = [32327,31827,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Carlin Furniture Store, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Nydala. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/obi.npc b/app/SabrehavenServer/data/npc/obi.npc new file mode 100644 index 0000000..7a4171d --- /dev/null +++ b/app/SabrehavenServer/data/npc/obi.npc @@ -0,0 +1,172 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# obi.npc: Datenbank für den Händler Obi + +Name = "Obi" +Outfit = (128,39-63-96-38-0) +Home = [32109,32204,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please stand in line %N. I'll be with you in a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine, I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much, much indeed. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant, just a humble merchant. What can I do for you?" +"name" -> "My name is Obi, just Obi, the honest merchant. Do you want to buy something?" +"time" -> "It is about %T. Yes, %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is safe from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only I dared to take the risk, and a risk it was!" +"seymour" -> "He is the head of the local academy. I encouraged him to sponsor you guy, but no one listens to Obi, no one listens to me, as usually." +"hyacinth" -> "I don't like him, I dislike him deeply. He is so greedy that he doesn't want to share his profit from life fluids." +"dallheim" -> "What a hero, what a hero." +"amber" -> "She is beautiful, very, very beautiful. I hope I can impress her in some way." +"willie" -> "This guy does not understand that he should entrust me with the foodbusiness, too. He really should do that and have more time for his farm." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich man!" +"sam" -> "My good old cousin Sam. Oh, how I miss him, how I miss him." +"thais" -> "Oh, Thais, I'll be back, I'll be back one day." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "I am sorry, we are out of pick axes. I heard that old greedy Al Dee has some but he will charge a fortune." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/ocelus.npc b/app/SabrehavenServer/data/npc/ocelus.npc new file mode 100644 index 0000000..7bb3cd3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ocelus.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Ocelus" +Outfit = (80,0-0-0-0-0) +Home = [32311,32622,4] +Radius = 3 + +Behaviour = { + +ADDRESS,"hello$",! -> "Greetings, dear visitor %N." +ADDRESS,"hi$",! -> * +ADDRESS,"djanni'hah$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, human %N. I'll be with you in a minute." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell, human." + +"bye" -> "Farewell!", Idle +"farewell" -> * +"visitor" -> "As you can see I am a djinn. We usually don't have jobs in your sense of the word. I am just here to watch and to listen to the music of the sea." +"djinn" -> "We djinns are a powerful and proud race. But look out human, not all djinns are as nice as I am. Especially the green djinns don't like humans at all." +"quaras" -> "The quara are the leftover minions of a powerful undersea race that once battled for the control of the magic currents here." +"magic currents" -> "Yes, magic is strong on these isles. In the past, two ancient races lived here. One on land and the others in the shadows of the deep sea ...", + "The creatures of the sea tried to gain control over the island. They started a war against the land dwellers during which the isle was shattered and both civilisations destroyed ...", + "All that has remained are distant memories, some of their work and the fact that the magic of the isles has turned into chaos." +"king" -> "Human kings come and go. Djinns are not such short-lived creatures and don't care much about humans." +"thais" -> "The noisy human cities are of absolutely no interest for me." +"raymond striker" -> "Now that Marina and me have arranged a date, I hope this human is forgotten soon." +"marina",QuestValue(17502)>12 -> "Marina and I have arranged a date already, I am so excited." +"pirates" -> "Many humans sail the seas, I can't tell them apart. You humans look all the same to me." +"voodoo" -> "Some humans are tapping the energy of these isles with some strange results. Perhaps that explains the voodoo we've heard about." + +"Eleonore",QuestValue(17502)=8 -> "I heard the birds sing about her beauty. But how could a human rival the enchanting beauty of a mermaid?", Topic=1 +Topic=1,"mermaid" -> "Oh yes, I noticed that lovely mermaid. From afar of course. I would not dare to step into the eyes of such a lovely creature. ...", + "I guess I am quite shy. Oh my, if I were not blue, I would turn red now. If there would be someone to arrange a date with her.", Topic=2 +Topic=2,"date" -> "Will you ask the mermaid Marina if she would date me?", Topic=3 +Topic=3,"yes" -> "Thank you. How ironic, a human granting a djinn a wish.", SetQuestValue(17502,9) +Topic=3, -> "Too bad." + +"mermaid",QuestValue(17502)=10 -> "Oh my. Its not easy to impress a mermaid I guess. Please get me a love poem. I think elves are the greatest poets so their city seems like a good place to look for one.", SetQuestValue(17502,11) +"marina",QuestValue(17502)=10 -> * +"date",QuestValue(17502)=10 -> * + +"poem",QuestValue(17502)=11 -> Type=5952, Amount=1, "Did you get a love poem from Ab'Dendriel?", Topic=4 +Topic=4,"yes",Count(Type)>=Amount -> "Excellent. Here, with this little spell I enable you to recite the poem like a true elven poet. Now go and ask her for a date again.", SetQuestValue(17502,12) +Topic=4,"yes" -> "It looks like you forgot it somewhere." +Topic=4 -> "Maybe another time." + +} diff --git a/app/SabrehavenServer/data/npc/odemara.npc b/app/SabrehavenServer/data/npc/odemara.npc new file mode 100644 index 0000000..c6c6cea --- /dev/null +++ b/app/SabrehavenServer/data/npc/odemara.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# odemara.npc: Datenbank für die Edelsteinhändlerin Odemara + +Name = "Odemara" +Outfit = (138,22-99-5-76-0) +Home = [33015,32058,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear %N. Have a look at our offers." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am deeply sorry, dear %N, but I am busy with a customer. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am responsible for buying and selling gems, pearls, and the like." +"name" -> "I am Odemara Taleris, it's a pleasure to meet you." +"time" -> "It's %T." +"offer" -> "We offer a great assortment of gems and pearls." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "We trade small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "We trade white and black pearls." + +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you don't have enough money." +Topic=1 -> "Too bad, perhaps we can trade on the next occasion you visit us." + +Topic=2,"yes",Count(Type)>=Amount -> "Excellent. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "I am sorry, but you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Too bad, perhaps we can trade on the next occasion you visit us." +} diff --git a/app/SabrehavenServer/data/npc/oldadall.npc b/app/SabrehavenServer/data/npc/oldadall.npc new file mode 100644 index 0000000..bbeacb9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/oldadall.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oldadall.npc: Datenbank für den Fährman old adall + +Name = "Old Adall" +Outfit = (130,95-26-115-76-0) +Home = [32628,32772,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am a ferryman now, in my youth I was a sailor though. If you want to travel to one of the ends of the town, just ask me for a passage." +"sailor" -> "Aye, matey. I was a sailor and have seen much of this world." +"name" -> "My name is Adall but everyone is calling me old Adall." +"time" -> "Hehe, old men have no need for watches. Watches are mocking you, you know?" +"mocking" -> "Watches seem to tell you that not much time is left and how much time you have already lost. They only remind you of everything that is already gone and all the things you will never achieve." +"watch" -> * +"king" -> "I saw the king and even his father. Thais used the be my home port in the old times." +"venore" -> "Those newcomers have quite an attitude and it is growing worse with the years. Ambition, ambition. It's all about ambition." +"thais" -> "It used to be a lovely town but over the years it has become crowded and noisy." +"carlin" -> "Hehe, it's not such a bad town for a visit, as long as you have your own alcohol on board and are not catched drunk in the city. I had to learn it the hard way and was arrested twice in my youth." +"edron" -> "Edron is not the lovely little isle people tend to think it is. There are secrets and ancient evil beneath the ground. Things that had better been left burried, have been unearthed." +"jungle" -> "The challenge the jungle is providing is something for the young and daring. I am not going to leave the security of the town, I'll just stay here and watch how things turn out." + +"tibia" -> "The world offers much to discover. Whether you find your fortune or your doom, it is a private thing between you and fate though." + +"kazordoon" -> "I have never seen this inland town on my own." +"dwarves" -> "Some dwarves joined the colony. They are looking for treasures and minerals in the jungle." +"dwarfs" -> * +"ab'dendriel" -> "A curious town of curious people but I have seen odder things during my travels." +"elves" -> "Elves are somewhat strange but most get along well with humans." +"elfs" -> * +"darama" -> "One might think it's a strange place for an old man to settle down but I never had a child and I like to see this settlement grow and come of age in my last days ." +"darashia" -> "The people of Darashia are friendly. Still there is nothing exciting that would justify a voyage there." +"ankrahmun" -> "That city has given me the creeps for as long as I have known about it. Whenever we sailed there, I had a bad feeling of impending doom." +"ferumbras" -> "I wonder if he's the Thaian version of the boogey man." +"boogey","man" -> "The boogey man is only a myth to scare the children and keep them away from the jungle." +"excalibug" -> "I heard about that weapon in each and every harbour I have visited. Never heard more than rumours though." +"apes" -> "The apes are for Port Hope what the the orcs are for Thais." +"lizard" -> "I think they are suspicious and just because they are far away does not mean they are nice neighbours." +"dworcs" -> "They are horrible little creatures. I have seen my share of various orc breeds during my travels and those dworcs are the worst of all." + +"trip" -> "I can bring you either to the east end of Port Hope or to the west end of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"east" -> Price=7, "Do you seek a passage to the east end of Port Hope for %P gold?", Topic=1 +"west" -> Price=7, "Do you seek a passage to the west end of Port Hope for %P gold?", Topic=2 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + + +#"trip" -> Price=7, "Would you like to travel to the east end of Port Hope or to the west end of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * +#Topic=1,"east",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"west",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"east",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +#Topic=1,"east",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"west",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +#Topic=1,"west",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "Fine you fool, considering I was asking you WHERE you want to travel 'yes' is a very smart answer!" + +} diff --git a/app/SabrehavenServer/data/npc/olddragon.npc b/app/SabrehavenServer/data/npc/olddragon.npc new file mode 100644 index 0000000..b1b7a3a --- /dev/null +++ b/app/SabrehavenServer/data/npc/olddragon.npc @@ -0,0 +1,16 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# olddragon.npc: Datenbank für den alten Drachenlord + +Name = "An Old Dragonlord" +Outfit = (39,0-0-0-0-0) +Home = [32796,31557,2] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",Count(3723)>=1,QuestValue(66)<1,! -> "AHHH MUSHRRROOOMSSS! NOW MY PAIN WILL BE EASSSED FOR A WHILE! TAKE THISS AND LEAVE THE DRAGONSSS' CEMETERY AT ONCE!", Amount=1, Delete(3723), Create(3206), SetQuestValue(66,1), Idle +ADDRESS,"hi$",Count(3723)>=1,QuestValue(66)<1,! -> * +ADDRESS,QuestValue(66)=1,! -> "LEAVE THE DRAGONS' CEMETERY AT ONCE!", Idle + +ADDRESS -> "AHHHH THE PAIN OF AGESSS! I NEED MUSSSSHRROOOMSSS TO EASSSE MY PAIN! BRRRING ME MUSHRRROOOMSSS!", Idle +ADDRESS -> * +} diff --git a/app/SabrehavenServer/data/npc/oldrak.npc b/app/SabrehavenServer/data/npc/oldrak.npc new file mode 100644 index 0000000..351b083 --- /dev/null +++ b/app/SabrehavenServer/data/npc/oldrak.npc @@ -0,0 +1,53 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oldrak.npc: Datenbank fuer den Moench Oldrak + +Name = "Oldrak" +Outfit = (57,0-0-0-0-0) +Home = [32816,32260,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Rarely I can welcome visitors in these days." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care, it's dangerous out there." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I guard this humble temple as a monument for the order of the nightmare knights." +"name" -> "My name is Oldrak." +"monster" -> "These plains are not safe for ordinary travellers. It will take heroes to survive here." +"help" -> "I can't help you, sorry!" +"goshnar" -> "The greatest necromant who ever cursed our land with the steps of his feet. He was defeated by the nightmare knights." +"nightmare","knights" -> "This ancient order was created by a circle of wise humans who were called 'the dreamers'. The order became extinct a long time ago." +"extinct" -> "Many perished in their battles against evil, some went mad, not able to stand their nightmares any longer. Others were seduced by the darkness." +"dreamers" -> "They learned the ancient art of dreamwalking from some elves they befriended." +"dreamwalking" -> "While the dreamwalkers of the elves experienenced the brightest dreams of pleasure, the humans strangely had dreams of dark omen." +"dark","omen" -> "They dreamed of doom, destruction, talked to dead, tormented souls, and gained unwanted insight into the schemes of darkness." +"schemes","darkness" -> "They figured out how to interpret their dark dreams and so could foresee the plans of the dark gods and their minions." +"plan","dark" -> "Using this knowledge they formed an order to thwart these plans, and because they battled their nightmares as brave as knights, they named their order accordingly." +"necromant","nectar" -> "It is rumoured to open the entrance to the pits of inferno, also called the nightmare pits. Even if I knew about this secret I wouldn't tell you." +"plains","havok" -> "Before the battles raged across them, they were called the fair plains." +"time" -> "Now, it is %T." +"tibia" -> "That's where we are. The world of Tibia." +"god" -> "They created Tibia and all life on it ... and unlife, too." +"unlife" -> "Beware the foul undead!" +"undead" -> * +"excalibug" -> "A weapon of myth and legend. It was lost in ancient times ... perhaps lost forever." +"hugo" -> "Ah, the bane of the Plains of Havoc, the hidden beast, the unbeatable foe. I live here for years and I am sure it's only a myth." +"myth",QuestValue(211)<1 -> "There are many tales about the fearsome Hugo. It's said it is an abomination, accidently created by Yenny the Gentle. It's halve demon, halve something else and people say it's still alive after dozens of years.",SetQuestValue(211,1) +"myth",QuestValue(211)>0 -> "There are many tales about the fearsome Hugo. It's said it is an abomination, accidently created by Yenny the Gentle. It's halve demon, halve something else and people say it's still alive after dozens of years." + + + +"yenny" -> "Yenny, known as the Gentle, was one of most powerfull magicwielders in ancient times and known throughout the world for her mercy and kindness." + +"holy","tible",QuestValue(17638)=0 -> "The holy book is located south of the temple near the shore." +"holy","tible",QuestValue(17638)=1 -> Price=1000, "If you lost your own one. Would you like me to grant you access to take another one?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> "Thank you. Then go and take it.", DeleteMoney, SetQuestValue(17638,0) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will need it another time." +} diff --git a/app/SabrehavenServer/data/npc/olrik.npc b/app/SabrehavenServer/data/npc/olrik.npc new file mode 100644 index 0000000..e68ec45 --- /dev/null +++ b/app/SabrehavenServer/data/npc/olrik.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# olrik.npc: Datenbank für den Diener und Postillion Olrik (Elfenstadt) + +Name = "Olrik" +Outfit = (128,115-79-117-76-0) +Home = [32675,31698,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Greetings, %N." +ADDRESS,"hello$",female,! -> "Greetings, %N. May I help you?" +ADDRESS,"hi$",male,! -> "Greetings, %N." +ADDRESS,"hi$",female,! -> "Greetings, %N. May I help you?" +ADDRESS,"ashari$",! -> "Greetings." +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "A moment please, %N.", Queue +BUSY,"hi$",male,! -> * +BUSY,"ashari$",male,! -> * +BUSY,"hello$",female,! -> "%N! A moment please, my lady. I look forward to talk to you soon.", Queue +BUSY,"hi$",female,! -> * +BUSY,"ashari$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"asha","thrazi" -> * + +"kevin" -> "He is the boss. Ther is allways some boss, so I don't bother about that." +"postner" -> * +"postmasters","guild" -> "Ah, they pay me and I do my work and thats it. Its some nice extra cash but I don't care much about that guild." +"join" -> "If you realy think you have to join the postmasters guild ask Kevin Postner at the headquarter." +"headquarter" -> "The postmasters guild headquarter is located south of the mountain known as the big old one, were the city of kazordoon can be found." + +"measurements",QuestValue(234)>0,QuestValue(240)<1 -> "My measurements? Listen, lets make that a bit more exciting ... No, no, not what you think! I mean let's gamble. I will roll a dice. If I roll a 6 you win and I'll tell you what you need to know, else I win and get 5 gold. Deal?", Amount=Random(1,6),Topic=5 +Topic=5,"no" -> "This way you'll never get my measurements." + +Topic=5,"yes",CountMoney>=5,Amount=6 -> Price=5,"Ok, here we go ... 6! You have won! How lucky you are! So listen ...",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(240,1) + +Topic=5,CountMoney>=5 -> Price=5, "Ok, and its ... %A! You have lost. He he. Another game?", DeleteMoney, Topic=6 +Topic=5,CountMoney<5 -> "I am sorry, but you don't have so much money." + +Topic=6,"yes" -> "Ok, no weights in the dice, no dirty tricks, are you ready?", Amount=Random(1,6),Topic=7 +Topic=6 -> "This way you'll never get my measurements." + +Topic=7,"yes",CountMoney>=5,Amount=6 -> Price=5,"Ok, here we go ... 6! You have won! How lucky you are! So listen ...",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(240,1) + +Topic=7,"no" -> "This way you'll never get my measurements." + +Topic=7,CountMoney>=5 -> Price=5, "Ok, and its ... %A! You have lost. He he. Another game?", DeleteMoney, Topic=6 +Topic=7,CountMoney<5 -> "I am sorry, but you don't have so much money." + +"job" -> "I am a servant of the ambassador and running the post office. Ask me if you have questions about the Royal Tibia Mail System." +"name" -> "My name is Olrik." +"time" -> "It's %T." + +"elves" -> "What a noble and graceful race." +"dwarfs" -> "Uhm, let's say I prefer the company of elves." +"carlin" -> "A city full of women who surly only see whimps instead of real men like me has some appeal." +"venore" -> "Those generous merchants are charming people. I am always looking forward their visits." +"humans" -> "I feel so clumsy around those elves." +"troll" -> "What nasty creatures." +"cenath" -> "They are so wise and have an aura of mystic around them." +"kuridai" -> "They are so diligent in the things they do and such awesome fighters." +"deraisim" -> "They are so familiar with the woods and move with unparalleled grace." +"abdaisim" -> "I look forward to meet them one day." +"teshial" -> "I wonder where they have gone." +"ferumbras" -> "I heared he is dead, which is a good thing." +"crunor" -> "I honor all gods." +"tibianus" -> "He is our beloved ruler." +"roderick" -> "The ambassador is the best choice for this position of responsibility." +"excalibug" -> "Pardon?" +"news" -> "We learn few important things about the tides of time here." +"magic" -> "You should talk to the elves about that." + +@"gen-post.ndb" + +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you do not have enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you do not have enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/app/SabrehavenServer/data/npc/omur.npc b/app/SabrehavenServer/data/npc/omur.npc new file mode 100644 index 0000000..3e5e976 --- /dev/null +++ b/app/SabrehavenServer/data/npc/omur.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Omur.npc: Datenbank für den Gemüsehändler Omur + +Name = "Omur" +Outfit = (128,95-0-6-116-0) +Home = [33228,32416,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome at the humble booth of Omur, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute to finish this deal, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your soul flourish like dunegrass after a rainfall." + +"bye" -> "May your soul flourish like dunegrass after a rainfall.", Idle +"name" -> "I am called Omur." ### Ibn ??? ### +"job" -> "I sell rare fruits and vegatables from our lands and distant places." +"time" -> "Don't become a slave of a watch." +"caliph" -> "Ah, Caliph Kazzan; thrice praised be his name. May his life be as long as the beard of the king of all djinns." +"kazzan" -> * +"ferumbras" -> "I think I have heard a traveller from the west mention that name." +"excalibug" -> "Is that the name of a djinn?" +"thais" -> "We import some goods from there in exchange for ours." +"tibia" -> "The world is nothing but a vain seduction." +"carlin" -> "I know almost nothing about that town. It must be exotic and entertaining. A place of distractions from the true path." +"news" -> "Sometimes the desertwind carries the crys and mourning of the tortured souls from Drefia far into the desert." +"rumour" -> * +"desert" -> "It's not called the Devourer for nothing." + +"do","you","sell" -> "I can offer you fruits and vegetables." +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, and melons. What do you want?" +"vegetable" -> "I have carrots, pumpkins and tomatoes. What do you want?" + +"orange" -> Type=3586, Amount=1, Price=7, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=3, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=5, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=10, "Do you want to buy a melon for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=4, "Do you want to buy a carrot for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=5, "Do you want to buy a tomato for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=7*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=3*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=5*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=10*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=4*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=5*%1, "Do you want to buy %A tomatos for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, I'm sorry, but I can't give you credit." +Topic=1 -> "Don't you like my wares?" +} diff --git a/app/SabrehavenServer/data/npc/oracle.npc b/app/SabrehavenServer/data/npc/oracle.npc new file mode 100644 index 0000000..bbc6f6f --- /dev/null +++ b/app/SabrehavenServer/data/npc/oracle.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oracle.npc: Datenbank fuer das Orakel auf Rookgaard + +Name = "The Oracle" +Outfit = (0,2031) +Home = [32104,32190,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",Level>=8,! -> "%N, ARE YOU PREPARED TO FACE YOUR DESTINY?" +ADDRESS,"hi$",Level>=8,! -> * +ADDRESS,"greet",Level>=8,! -> * +ADDRESS,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"greet",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Level>=8,! -> "WAIT UNTIL IT IS YOUR TURN!", Queue +BUSY,"hi$",Level>=8,! -> * +BUSY,"greet",Level>=8,! -> * +BUSY,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!" +BUSY,"hi$",! -> * +BUSY,"greet",! -> * +BUSY,! -> NOP +VANISH,! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!" + +"yes",premium -> "IN WHICH TOWN DO YOU WANT TO LIVE: CARLIN, EDRON, THAIS, OR VENORE?", Topic=1 +"yes" -> "IN WHICH TOWN DO YOU WANT TO LIVE: CARLIN, THAIS, OR VENORE?", Topic=1 +"bye",! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!", Idle + -> * + +Topic=1,"thais" -> Data=1, "IN THAIS! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"carlin" -> Data=2, "IN CARLIN! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"venore" -> Data=3, "IN VENORE! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"edron",premium -> Data=4, "IN EDRON! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Amount=Random(1,3), Topic=2 +Topic=1,"edron" -> "YOU NEED A PREMIUM ACCOUNT IN ORDER TO GO THERE!", Topic=1 +Topic=1,premium -> "CARLIN, EDRON, THAIS, OR VENORE?", Topic=1 +Topic=1 -> "CARLIN, THAIS, OR VENORE?", Topic=1 + +Topic=2,"knight",Amount=1 -> Type=4, Price=3300, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"knight",Amount=2 -> Type=4, Price=3286, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"knight",Amount=3 -> Type=4, Price=3276, Amount=1, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"paladin" -> Type=3, Price=3277, Amount=5, "A PALADIN! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"sorcerer" -> Type=1, Price=3074, Amount=1, "A SORCERER! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"druid" -> Type=2, Price=3066, Amount=1, "A DRUID! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2 -> "KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 + +Topic=3,Data=1,"yes" -> "SO BE IT!", Profession(Type), Town(1), Idle, EffectOpp(11), Teleport(32369,32241,7), EffectOpp(11), Create(Price) +Topic=3,Data=2,"yes" -> "SO BE IT!", Profession(Type), Town(2), Idle, EffectOpp(11), Teleport(32360,31782,7), EffectOpp(11), Create(Price) +Topic=3,Data=3,"yes" -> "SO BE IT!", Profession(Type), Town(7), Idle, EffectOpp(11), Teleport(32957,32076,7), EffectOpp(11), Create(Price) +Topic=3,Data=4,"yes" -> "SO BE IT!", Profession(Type), Town(5), Idle, EffectOpp(11), Teleport(33217,31814,8), EffectOpp(11), Create(Price) +} diff --git a/app/SabrehavenServer/data/npc/orcking.npc b/app/SabrehavenServer/data/npc/orcking.npc new file mode 100644 index 0000000..3e901d3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/orcking.npc @@ -0,0 +1,56 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# orcking.npc: Datenbank für den Orckönig + +Name = "The Orc King" +Outfit = (19,0-0-0-0-0) +Home = [32983,31728,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$", QuestValue(218) = 0,! -> "Arrrrgh! A dirty paleskin! To me my children! Kill them my guards!",SetQuestValue(218,1),Summon("Orc Leader"),Summon("Orc Leader"),Summon("Orc Leader"),Summon("Orc Warlord"),Summon("Orc Warlord"),Summon("Slime"),Summon("Slime"),Summon("Slime"),Idle + +ADDRESS,"hi$", QuestValue(218) = 0,! -> * +ADDRESS,"hello$",! -> "Harrrrk! You think you are strong now? You shall never escape my wrath! I am immortal!" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Harrrk!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes, flee this place, but you will never escape my revenge!" + + +"bye" -> "We will meet again.", Idle +"farewell" -> * + +"name" -> "I am Charkahn the Slayer! The immortal father of the orcs and master of this hive." +"job" -> * +"hive" -> "I can sense the presence and the feelings of my underlings and minions. I embrace the rage of the horde." +"minion" -> "The orcish horde of this hive is under my control. I sense their emotions and their needs and provide them with the leadership they need to focus their hate and rage." +"underling" -> * +"horde" -> * +"hate" -> "Hate and rage are the true blessings of Blog, since they are powerful weapons. They give the hive strength. I provide them with direction and focus." +"rage" -> * +"direction" -> "To conquer, to destroy and to dominate. Orcs are born to rule the world." +"focus" -> * +"blog" -> "The Raging One blessed us with his burning hate. We are truly his children and therefore divine." +"divine" -> "The orcs are the bearers of Blogs rage. This makes us the ultimate fighters and the most powerful of all races." +"orc" -> * +"slime" -> "Pah! Don't mock me, mortal! This shape is a curse which the evil djinn bestowed upon me!" +"djinn" -> "This cursed djinn king! I set him free from an enchanted lamp, and he cheated me!" +"malor" -> * +"cheat" -> "Because I freed him he granted me three wishes. He was true to his word in the first two wishes." +"wish" -> "He built this fortress over Uldrek's grave within a single night. Also, he granted me my second wish and gave me immortality. Test it and try to kill me if you want. Har Har!" +"third" -> "I wished to father more healthy and fertile children as any orc has ever done. But the djinn cheated me and made me a slime! Then he laughed at me and left for his abandoned fortress in the Deathwish Mountains." +"deathwish" -> "His ancient fortress on Darama was deserted as the evil Djinn fled this world after his imprisonment. Now the time has come for the evil Djinns to return to their master although this will certainly awaken the good Djinn too." +"abandoned" -> * +"good","djinn" -> "I will not share anything more about that topic with you paleskins." +"awaken" -> * +"paleskins" -> "You are as ugly as maggots, although not quite as as tasty." +"lamp" -> "For Eons he was trapped in an enchanted lamp by some ancient race. Now he's free to roam the world again. Although he cheated me I appreciate what he and his brethren will do to this world, now it's the time of the Djinn again!" +"lamp",QuestValue(283)=1,QuestValue(284)=0,! -> "I can sense your evil intentions to imprison a djinn! You are longing for the lamp, which I still possess. ...", + "Who do you want to trap in this cursed lamp?",Topic=1 +"lamp",QuestValue(288)=1,QuestValue(284)=0,! -> * +Topic=1,"malor",! -> "I was waiting for this day! Take the lamp and let Malor feel my wrath!",Amount=1,Create(3231),SetQuestValue(284,1) +Topic=1 -> "I don't know your enemy, paleskin! Begone!", Idle +} diff --git a/app/SabrehavenServer/data/npc/ormuhn.npc b/app/SabrehavenServer/data/npc/ormuhn.npc new file mode 100644 index 0000000..71397dc --- /dev/null +++ b/app/SabrehavenServer/data/npc/ormuhn.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ormuhn.npc: Datenbank für die arenaleiter ormuhn + +Name = "Ormuhn" +Outfit = (18,0-0-0-0-0) +Home = [33160,32810,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am the arena master. I supervise all challenges that take place in this arena and train true fighters." +"name" -> "I am called Ormuhn." +"time" -> "Time only matters to you while you are mortal. Another instrument in the hands of the false gods to fool us all." +"temple" -> "The temple takes care of your Uthun. In this arena we challenge your Akh." +"pharaoh" -> "The pharaoh, our mighty leader, is an unliving god." +"oldpharaoh" -> "The pharaoh will know why he granted him a chance to ascend." +"scarab" -> "Scarabs might be sacred, but they are also a challenge. If you are able to overcome one of them, its spirit will forgive you. The everlasting sand will grant him rebirth anyway." +"chosen" -> "I am one of the chosen. To become like me you have to serve the pharaoh and his temple faithfully." + +"tibia" -> "Tibia is a place kept in the thrall of the greedy false gods. One day though our pharaoh will free Tibia and guide all of its people to ascension." + +"carlin" -> "These cities are nests of corruption and lies. For those who know the treason of the false gods is almost tangible there." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves are worthy warriors. Still their mortal Akh makes them prey to true death and so their lives are wasted." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are nothing but feeble treehuggers." +"elves" -> * +"elfes" -> * +"darama" -> "This continent will be the first to prosper under the guidance of our pharaoh." +"darashia" -> "The people there are not totally lost to the false gods yet. Who knows? They may be saved yet." +"daraman" -> "A mere mortal prophet. As was to be expected, his mortality blurred his visions of ascension." +"ankrahmun" -> "This is the shelter of the mortal flock who listens to the teachings of our pharaoh." + +"mortality" -> "Your curse of mortality can be lifted if you only prove youself worthy in the eyes of our pharaoh." +"false", "gods" -> "These greedy reapers of souls are the true scourge of poor mortals like you. Your living Akh makes you vulnerable for their attacks. Withstand them and you will get a chance to be raised to the exalted state of undeath." + +"ascension" -> "Ascension is achieved in many steps. The first and most important step is unquestioning loyal service to our pharaoh." +"Akh'rah","Uthun" -> "You should discuss such topics with our priests. I don't care too much for these matters." +"Akh" -> "As far as I know this is what you would call your body. Ask a priest for further information." +"undead" -> "We are the chosen ones." +"undeath" -> * +"Rah" -> "That is your so-called soul. Ask a priest for further information." +"uthun" -> "All the things you remember form your uthun. Ask a priest for further information." +"mourn" -> "Living flesh is so ... pathetic." + +"arena" -> "If you wish to test your mortal Akh you are at the right place." +"palace" -> "The palace is guarded by the elite forces of the chosen." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "May enlightenment be your path.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/oswald.npc b/app/SabrehavenServer/data/npc/oswald.npc new file mode 100644 index 0000000..c6f498f --- /dev/null +++ b/app/SabrehavenServer/data/npc/oswald.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oswald.npc: Datenbank für Durin-Helfer Oswald + +Name = "Oswald" +Outfit = (128,115-0-67-114-0) +Home = [32381,32220,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Oh, hello %N. What is it?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Be patient, draw a number, %N.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, and don't come back too soon." + +"bye",Level>0 -> "Finally!", Idle +"farewell",Level>0 -> * +"bye",Level<2 -> "Good bye, master %N. Have a nice day!", Idle +"farewell",Level<2 -> * +"how","are","you"-> "If there weren't so many people harassing me, life could be great." +"sell" -> "Hey, I am not a shopkeeper, I am an important man!" +"harassing" -> "You need not ask me about that, you are perfect in that." +"job" -> "I am honored to be the assistant of the great, the illustrious, the magnificent Durin!" +"durin" -> "Just between you and me, he can be quite a tyrant." +"important" -> "I am honored to be the assistant of the great, the illustrious, the magnificent Durin!" +"name" -> "My name is Oswald, but let's proceed, I am a very busy man." +"time" -> "It is nearly tea time, so please hurry!" +"help" -> "I inform higher officials of your need... sometimes." +"monster" -> "AHHHH!!! WHERE??? WHERE???", Idle +"dungeon" -> "If you want to see dungeons just don't pay your taxes." +"sewer" -> "Our sewer system is very modern, but crowded with rats and wannabe heroes." +"assistant" -> "I have a job of great responsibility, mostly I keep annoying persons away from my boss." +"annoying" -> "You better don't ask, you wouldn't like the answer." +"thank","you" -> "You are... uhm... welcome. Are you finished already?" +"god" -> "I think the gods are too busy to care about us mortals, hmm... that makes me feel godlike, too." +"king" -> "Ah, yes, yes, hail to King Tibianus! Long live the king and so on..." +"sam" -> "A simple shopkeeper with minor intelligence." +"benjamin" -> "What do you expect from ex-soldiers? He is nuts! Hacked on the head far too often." +"gorn" -> "He sells his scrolls far too expensive." +"quentin" -> "I heard he was a ladies' man in younger days. In our days he is rumoured to wear women clothes now and then." +"bozo" -> "Isn't he the artist formerly known as the prince?" +"rumour" -> "You know a rumour? TELL ME! TELL ME! TELL ME!", Topic=3 +"gossip" -> * +"news" -> * +"mud" -> "I heared Sam dated a female mud-wrestler once." +"weapon" -> "It's rumoured that Sam does not forge all weapons himself, but buys them from his cousin, who is married to a cyclops." +"magic" -> "I overheard a conversation of officials, that magic will be forbidden soon." +"power" -> "There are people who talk about a rebellion against King Tibianus." +"rebellion" -> "There are people who talk about a rebellion against King Tibianus." +"spell" -> "I was told sometimes that sorcerers are toasted by misfired spells of their own." +"muriel" -> "He is rumoured to summon kinky demons to... well you know." +"elane" -> "They say she killed over a dozen husbands already." +"marvik" -> "Who knows what this old man is up to in his hideout when no one is watching?" +"gregor" -> "I was told he lost a body part or two in duels... if you know what I mean." +"lugri" -> "Some say he is Ferumbras in disguise." +"excalibug" -> "It's beyond all doubt that certain sinister elements in our city have certain knowledge about this myth." +"chester" -> "I never found any rumour concerning him, isn't that odd?" +"ardua" -> "She's a bitch, trust me. She was the girlfriend of the evil Partos some time ago." +"partos" -> "What a shame. He claimed to be the king of thiefs and was caught stealing some fruit." +"gamel" -> "This man lives in the darkness like a rat and is also as handsome as one of them. He surely is up to no good and often consorts with sinister strangers." +"sinister","strangers" -> "Just last week a one eyed man, who had a room at Frodo's, met him in the middle of the night." +"goshnar" -> "They say he isn't truly dead. He was... or is a necromant after all." +"necromant","nectar" -> "You are not the first one to ask about that. Am I the only one that preferes wine to such disgusting stuff?" + +Topic=3 -> "Fascinating! Absolutely fascinating!" +} diff --git a/app/SabrehavenServer/data/npc/padreia.npc b/app/SabrehavenServer/data/npc/padreia.npc new file mode 100644 index 0000000..864b892 --- /dev/null +++ b/app/SabrehavenServer/data/npc/padreia.npc @@ -0,0 +1,137 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# padreia.npc: Datenbank fuer die Druidin Padreia + +Name = "Padreia" +Outfit = (138,0-87-85-95-0) +Home = [32300,31816,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Crunor's blessings. I am glad to see you again, %N!" +ADDRESS,"hi$",Druid,! -> "Crunor's blessings. I am glad to see you again, %N!" +ADDRESS,"hello$",! -> "Welcome to our humble guild, wanderer. May I be of any assistance to you?" +ADDRESS,"hi$",! -> "Welcome to our humble guild, wanderer. May I be of any assistance to you?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> "Please wait, %N.", Queue +BUSY,! -> NOP +VANISH,Druid,! -> "Farewell, %N. May Crunor be with you, my child." +VANISH,! -> "Farewell. May Crunor be with you." + +"bye",Druid -> "Farewell, %N.", Idle +"farewell",Druid-> * +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the grand druid of Carlin. I am responsible for the guild, the fields, and our citizens health." +"grand","druid" -> * +"name" -> "I am Padreia, Grand Druid of our fine city." +"time" -> "Time is just a crystal pillar. The center of creation and life." +"member" -> "Our members wield magic powers of protection and healing." +"magic" -> "Every member of the Druids is able to learn the numerous spells of our craft." +"power" -> * +"druid" -> "We are druids, preservers of life. Our magic is about defense, healing, and nature." +"sorcerer" -> "Sorcerers are destrucitve. Their power lies in destruction and pain." +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Druids, paladins, knights, and sorcerers." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Rachel." +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +"crunor","caress" -> "Don't ask. They were only an unimportant footnote of history." +"footnote",QuestValue(211)=2 -> "They thought they have to bring Crunor to the people, if people did not find to Crunor of their own. To achieve that they founded the inn Crunor's Cottage, south of Mt. Sternum.",SetQuestValue(211,3) +"footnote",QuestValue(211)<2 -> "I have to attend other business, ask later please." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"cough", "syrup" -> Type=4828, Price=50, "Do you want to buy a bottle of cough syrup for %P gold?", Topic=10 +Topic=10,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "Sorry, you do not have enough gold." +Topic=10 -> "Maybe you will need it another time." + + +} diff --git a/app/SabrehavenServer/data/npc/paolo.npc b/app/SabrehavenServer/data/npc/paolo.npc new file mode 100644 index 0000000..059ef58 --- /dev/null +++ b/app/SabrehavenServer/data/npc/paolo.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Paolo" +Outfit = (160,78-102-124-117-0) +Home = [32163,32964,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello there!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I'm talking to someone?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/parlan.npc b/app/SabrehavenServer/data/npc/parlan.npc new file mode 100644 index 0000000..8a67156 --- /dev/null +++ b/app/SabrehavenServer/data/npc/parlan.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Parlan" +Outfit = (128,113-37-116-76-0) +Home = [32185,32795,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello there!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I'm talking to someone?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/partos.npc b/app/SabrehavenServer/data/npc/partos.npc new file mode 100644 index 0000000..77658ba --- /dev/null +++ b/app/SabrehavenServer/data/npc/partos.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# partos.npc: Datenbank für den Sträfling Partos + +Name = "Partos" +Outfit = (128,116-56-95-122-0) +Home = [32323,32280,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little kingdom, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, don't go away, I am ready soon and don't get visitors often.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I wish I could do that, too." + +"bye" -> "Good bye, visit me again. I will be here, promised.", Idle +"farewell" -> * +"job" -> "Guess it! I give you a hint: I am not in this cell to clean it up! ...", + "I wished, I would have never left Ankrahmun." +"news" -> "I hardly hear any news down here." +"name" -> "My name is Partos, but you can call me Party." +"party" -> "Yeah! Come in and let's have a party." +"thais" -> "I love the city. I just wish I could see some other part of it now and then." +"city" -> * +"tibia" -> "I love this world. I just wish I could see some other part of it now and then." +"how","are","you"-> "I am great! Free food, free room, and now and then someone coming down here to ask me silly questions. Wouldn't you love that, too?" +"sell" -> "I would like to sell you a secret, but I'm out of business for too long." +"jail" -> "You mean that's a JAIL? They told me it's the finest hotel in town! THAT explains the lousy roomservice!" +"prison" -> * +"crime" -> "Bah, I did nothing serious. I just had a little fun. In Ankrahmun nobody would have cared about these kind of things..." +"criminal" -> * +"god$" -> "The gods seldom show up down here, so don't ask me." +"gods$" -> * +"citizen" -> "Rich enough to spare a little, don't you agree? Well, they didn't agree." +"king" -> "Yeah, a king is a man that can rob people by law, and not by night like me." +"monster" -> "At least I am safe from them down here." +"gold" -> "Gold got me in here." +"money" -> * +"fight" -> "Hey, most people I killed were even worse than me." +"slay" -> * +"noodles" -> "I bet one could get some fine ransom, if he dognappes this furball." +"quentin" -> "By the gods, he visits us 'criminals' now and then to 'save' us. Who is going to save me from this boredom on two legs?" +"army" -> "Bah, the king's pawns. I spit on them." +"time" -> "Geee, someone stole my watch. Bad company down here." +"ankrahmun" -> "Yes, I've lived in Ankrahmun for quite some time. Ahh, good old times! ...", + "Unfortunately I had to relocate. ...", + "Business reasons - you know." + +"waterpipe" -> "My waterpipe? I lost it. But it doesn't matter. I quit smoking anyway." +"djinn",QuestValue(286)=1,! -> "What!? I bet, Baa'leal sent you! ...", + "I won't tell you anything! Shove off!", SetQuestValue(286,2), Idle +"baa'leal",QuestValue(286)=1,! -> * +"supplies",QuestValue(286)=1,! -> * +"mal'ouquah",QuestValue(286)=1,! -> * + +"djinn" -> "I won't talk about that." +"baa'leal" -> * +"supplies" -> * +"mal'ouquah" -> * + +#"excalibug" -> "Excalibug? No way that I tell you something about it!" +#"grapes" -> Type=3592, Amount=1, "Do you have any grapes with you?", Topic=1 + +#Topic=1,"yes",Count(Type)>=Amount -> "What do you want for that ...ohhh... tasty ...uhm... sweet ...drool... delicous ...hmm... grapes?", Delete(Type), Topic=2 +#Topic=1,"yes" -> "Go away, if you don't have any grapes." +#Topic=1 -> * +#Topic=2,"excalibug" -> "My late mentor once told me he found a wallcarving about this sword in a cave beneath the castle.", Topic=2 +#Topic=2,"wallcarving" -> "That part of the dungeon was recently blocked by a cave-in. It was unsecure before, and only a fool would have entered there. I stayed out and alive." +} diff --git a/app/SabrehavenServer/data/npc/peggy.npc b/app/SabrehavenServer/data/npc/peggy.npc new file mode 100644 index 0000000..35507f3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/peggy.npc @@ -0,0 +1,42 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Peggy" +Outfit = (136,20-35-27-21-0) +Home = [32294,32807,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"farewell" -> * +"name" -> "My name is Peggy. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/pemaret.npc b/app/SabrehavenServer/data/npc/pemaret.npc new file mode 100644 index 0000000..6dd86fd --- /dev/null +++ b/app/SabrehavenServer/data/npc/pemaret.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pemaret.npc: Fischer Pemaret auf Cormaya + +Name = "Pemaret" +Outfit = (128,79-10-127-127-0) +Home = [33287,31956,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Greetings, young man. Looking for a passage or some fish, %N?" +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Greetings, young lady. Looking for a passage or some fish, %N?" +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=10,! -> Price=10, "Here we go %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=20,! -> Price=20, "Here we go %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=10,! -> Price=10, "Here we go %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=20,! -> Price=20, "Here we go %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +BUSY,"bring","me","to","eremo",Premium,! -> "Here we go %N!", Queue, EffectOpp(11), Teleport(33315,31882,7), EffectOpp(11) +ADDRESS,"bring","me","to","eremo",Premium,! -> "Here we go %N!", Queue, EffectOpp(11), Teleport(33315,31882,7), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Pemaret, the fisherman." +"job" -> "I'm a fisherman and I take along people to Edron. You can also buy some fresh fish." +"tibia" -> "I love to sail on the seas of Tibia." +"sea" -> * +"cormaya" -> "It's a lovely and peaceful isle. Did you already visit the nice sandy beach?" +"isle" -> * +"beach" -> "There is a nice sandy beach in the west of Cormaya." + +"ship" -> "My boat is ready to bring you to Edron." +"boat" -> * +"passage" -> * + +"edron" -> Price=20, "Do you want to get to Edron for %P gold?", Topic=1 +"edron",QuestValue(250)>2 -> Price=10, "Do you want to get to Edron for %P gold?", Topic=1 + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy a fresh fish for %P gold?", Topic=2 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fresh fishes for %P gold?", Topic=2 + +"eremo" -> "Oh, you know the good old sage Eremo. I can bring you to his little island. Do you want me to do that?", Topic=3 +"sage" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Maybe later." + +Topic=2,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "I am sorry, but you do not have enough gold." +Topic=2 -> "Maybe later." + +Topic=3,"yes" -> "Here we go!", Idle, EffectOpp(11), Teleport(33315,31882,7), EffectOpp(11) +Topic=3 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/penny.npc b/app/SabrehavenServer/data/npc/penny.npc new file mode 100644 index 0000000..fcc9057 --- /dev/null +++ b/app/SabrehavenServer/data/npc/penny.npc @@ -0,0 +1,31 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# penny.npc: Datenbank für die GM-Gehilfin Penny + +Name = "Penny" +Outfit = (137,96-79-95-96-0) +Home = [32315,31936,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome home, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome home, Lady %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hi$",male,! -> "Just a minute, Sir %N.", Queue +BUSY,"hello$",male,! -> * +BUSY,"hi$",female,! -> "Just a minute, Lady %N.", Queue +BUSY,"hello$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "May Justice be with you!" + +"bye" -> "May Justice be with you!", Idle +"farewell" -> * +"name" -> "I am miss Penny, your secretary." +"job" -> "I'm your secretary. I'm organizing all those criminal records and your mail." +"criminal" -> " It's an evil world, isn't it?" +"record" -> * +"mail" -> "You can get a letter from me." +"letter" -> "Here you are.", Create(3505) + +} diff --git a/app/SabrehavenServer/data/npc/perac.npc b/app/SabrehavenServer/data/npc/perac.npc new file mode 100644 index 0000000..1664ad7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/perac.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# perac.npc: Datenbank für den Bogner Perac + +Name = "Perac" +Outfit = (129,78-52-68-114-0) +Home = [32295,31784,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to a customer." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the fletcher of Carlin. I am selling bows, crossbows, and ammunition. Do you need anything?" +"name" -> "I am Perac, fletcher and marksman extraordinaire." +"marksman" -> "I am a paladin and the best marksman in the land." +"time" -> "Don't bother me. Go and buy a watch." +"ghostlands" -> "I was there ... once. I got out before the illusions drove me mad. Better stay out of that area!" + + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow or bolts for a crossbow?" +"ammunition" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +"task",QuestValue(17647)=0,paladin -> Amount=17648, "Young paladin, I see you need ammunition but those are too expensive, right. Hmm... I can't give you for free. ...", + "However, if you could kill 50 minotaurs to prove your trustworthy willingness I will reward you the bow and 300 arrows. Deal?", Topic=120 + +"task",QuestValue(17648)=50,QuestValue(17647)=1,paladin -> "Well done, %N. Here is your bow and arrows!", SetQuestValue(17647,2), SetQuestValue(17649,0), Type=3350, Amount=1, Create(Type), Type=3447, Amount=300, Create(Type) + +"task",QuestValue(17649)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young paladin. Come back once you are done.", SetQuestValue(17649,Amount), SetQuestValue(Amount,0), SetQuestValue(17647,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." + +} diff --git a/app/SabrehavenServer/data/npc/percysilverhand.npc b/app/SabrehavenServer/data/npc/percysilverhand.npc new file mode 100644 index 0000000..3be242e --- /dev/null +++ b/app/SabrehavenServer/data/npc/percysilverhand.npc @@ -0,0 +1,19 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Percy Silverhand" +Outfit = (132,0-112-74-109-1) +Home = [32330,32767,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +} diff --git a/app/SabrehavenServer/data/npc/perod.npc b/app/SabrehavenServer/data/npc/perod.npc new file mode 100644 index 0000000..853b8b6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/perod.npc @@ -0,0 +1,145 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# perod.npc: Datenbank für den Händler Perod + +Name = "Perod" +Outfit = (128,58-68-12-114-0) +Home = [32635,32739,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, dear %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N. I am already talking to someone.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell general goods which are, if I am allowed to say that, crucial when you explore the jungle." +"name" -> "I am Perod, how could you forget that, %N? We fought back-to-back in those troll caves on Rookgard a long time ago." +"time" -> "I won't tell you, but you can buy one of my quality watches to find out." +"king" -> "The king is far away and yet we are still his subjects. Strange, isn't it?" +"venore" -> "After I had left Thais I found a new home in Venore and I never regreted it." +"thais" -> "Thais lacked any prospect for a change. I quickly figured out that Venore is the place to be." +"carlin" -> "Carlin is a dull city with strange habits." +"edron" -> "I lived in Edron as a treasure hunter for a while, but then the place became too crowded." +"jungle" -> "The jungle is full of adventures and secrets that wait to be explored. Some years ago I would have surely enjoyed that. ...", + "But now that I setteled down here, I don't feel excited anymore by the thought of exploring an inhospitable forest full of animals that want to kill me." + +"tibia" -> "I have travelled a lot and still I have not seen everything. So I abandoned my life as an explorer and became an employee of a trading company domiciled in Venore." + +"kazordoon" -> "The hidden city of the dwarves can be quite confusing for a newcomer. I got lost there a dozen times before I became familiar with that city." +"dwarves" -> "Some dwarves live in the city, you'll find them in the tavern." +"dwarfs" -> * +"ab'dendriel" -> "The elves of Ab'Dendriel built a city seemingly out of trees. I wonder how they can stand the winter in those odd houses." +"elves" -> * +"elfs" -> * +"darama" -> "A new continent means new chances. But my days as an adventurer are over. My new chances lie in trade and commerce." +"darashia" -> "My trips there were very short, I don't like the desert and I did not like that town." +"ankrahmun" -> "It's one of the few cities I have never visited and no one will ever get me even close to that city of undeads and mummies." +"ferumbras" -> "During my days as an adventurer, I was thrilled by the thought to fight him. Looking back I must say it is better that I have never met him." +"excalibug" -> "Oh boy, how long have we searched for that weapon. I still wonder sometimes where it might be hidden, but I have no clue." +"apes" -> "Their occasional raids give me a chance to train my fighting skills." +"lizard" -> "I bet those lizards hide some ancient treasures in their settlements." +"dworcs" -> "It's the jungle variant of the orcs. I guess no matter where you go, there is always some orc waiting behind some bush, ready to thrust his blade in your body." + + +"offer" -> "I offer fishing rods, sixpacks of worms, shovels, picks, scythes, bags, ropes, backpacks, plates, jugs, mugs, cups, bottles, buckets, scrolls, documents, parchments, footballs, watches, books, torches, machetes, presents and ammunition." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + + +"magic" -> "Ask somwhere else in the market." +"fluid" -> * + +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2864, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2872, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=40, "Do you want to buy a machete for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"greeting","card",ClientVersion>=790 -> Type=6386, Amount=1, Price=30, "Do you want to buy a greeting card for %P gold?", Topic=1 +"valentine","card",ClientVersion>=790 -> Type=6538, Amount=1, Price=30, "Do you want to buy a valentine's card for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2864, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2872, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=40*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"greeting","card",ClientVersion>=790 -> Type=6386, Amount=%1, Price=30*%1, "Do you want to buy %A greeting cards for %P gold?", Topic=1 +%1,1<%1,"valentine","card",ClientVersion>=790 -> Type=6538, Amount=%1, Price=30*%1, "Do you want to buy %A valentine's cards for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms would you like to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + + +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=2 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=2 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=2 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=2 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=2 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=2 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=2 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=2 + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/phillip.npc b/app/SabrehavenServer/data/npc/phillip.npc new file mode 100644 index 0000000..b4c7797 --- /dev/null +++ b/app/SabrehavenServer/data/npc/phillip.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# phillip.npc: Datenbank für den Lehrer Phillip + +Name = "Phillip" +Outfit = (128,116-54-68-76-0) +Home = [32369,31764,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",Level<4,! -> "Hello, pupil %N. I hope I can help you on your quest for knowledge." +ADDRESS,"hi$",Level<4,! -> * +ADDRESS,"hello$",Level<15,! -> "Hello, seeker of knowledge %N. How may I assist you?" +ADDRESS,"hi$",Level<15,! -> * +ADDRESS,"hello$",Level<25,! -> "Hello, mighty adventurer %N. Can I teach you something you don't know?" +ADDRESS,"hi$",Level<25,! -> * +ADDRESS,"hello$",! -> "Hello, famous %N. It should be you teaching me!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Patience, %N. Listen to my words and learn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Did the bell ring?" + +"bye" -> "Go and be careful. Remember what you have learned!", Idle +"farewell" -> * +"how","are","you"-> "I am fine, thank you very much." +"sell" -> "My business is knowlegde and it is for free." +"job" -> "I am honored to be teacher in this school." +"teacher" -> "I run this school, there are other travelling teachers who we call Loremasters." +"loremaster" -> "If you are lucky you'll meet one in your journeys." +"name" -> "My name is Phillip." +"time" -> "It is %T." +"help" -> "I will provide you with all knowledge I have." +"monster" -> "Monsters come in different shape and power. It's said there is a zoo in the dwarfs' town." +"dungeon" -> "Dungeons are places of danger and puzzles. In some of them a bright mind will serve you more then a blade." +"sewer",female -> "The sewers of Carlin are a disgusting place. Better never crawl around in these stinking tunnels." +"sewer",male -> "An interesting place you should consider to visit." +"thank","you" -> "You don't have to thank me, it's only my duty." +"god" -> "To learn about gods, visit the temples and talk to the priests." +"king" -> "The southern king is called Tibianus. He and our queen Eloise are in a constant struggle." +"queen" -> * +"rumour" -> "I don't like rumours." +"gossip" -> * +"news" -> * +"weapon" -> "To learn about weapons read appropriate books or talk to the smiths." +"magic" -> "To learn about magic talk to the guild leaders." +"rebellion" -> "Rebellion? What for? We are contend with our situation." +"in","tod","we","trust" -> "Tod will come and save us all. He will bring freedom and beer to the men of Carlin." +"lugri" -> "This servant of evil is protected by the dark gods and can't be harmed." +"ferumbras" -> "He is a follower of evil. His powers were boosted by a sinister force and he is beyond human restrictions now." +"excalibug" -> "This weapon is said to be very powerful and unique. It was hidden in ancient times and now is thought to be lost." +} diff --git a/app/SabrehavenServer/data/npc/pino.npc b/app/SabrehavenServer/data/npc/pino.npc new file mode 100644 index 0000000..c1d115f --- /dev/null +++ b/app/SabrehavenServer/data/npc/pino.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pino: Datenbank für den Teppichpiloten Pino in Edron + +Name = "Pino" +Outfit = (128,115-0-67-114-0) +Home = [33192,31783,3] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","femor",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +BUSY,"bring","me","to","femor",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +ADDRESS,"bring","me","to","femor",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +ADDRESS,"bring","me","to","femor",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) + +BUSY,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=20,! -> Price=20, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +BUSY,"bring","me","to","darashia",Premium,CountMoney>=30,! -> Price=30, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=20,! -> Price=20, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,CountMoney>=30,! -> Price=30, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) + +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye!" + +"bye" -> "Good bye!", Idle +"name" -> "Pino at your service." +"job" -> "I am a carpetpilot. I can fly you to the Femor Hills or Darashia." +"service" -> * +"time" -> "It's %T right now." +"tibia" -> "What a wonderful world. Especially if you look down on it." + +"passage" -> "I can fly you to Darashia on Darama or to the Femor Hills if you like. Where do you want to go?" +"transport" -> * +"ride" -> * +"trip" -> * + +"darashia" -> Price=40, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama" -> * +"hill" -> Price=60, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=2 +"femor" -> * +"femur" -> "You are probably talking about the FEMOR hills." + +"darashia",QuestValue(250)>2 -> Price=30, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama",QuestValue(250)>2 -> * +"hill",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=2 +"femor",QuestValue(250)>2 -> * + +Topic=1,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/prisoner.npc b/app/SabrehavenServer/data/npc/prisoner.npc new file mode 100644 index 0000000..548e6b4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/prisoner.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# prisoner.npc: Datenbank für den Prisoner alias Mad Mage + +Name = "A Prisoner" +Outfit = (130,81-40-55-94-0) +Home = [32393,32137,13] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Huh? What? I can see! Wow! A non-mino. Did they capture you as well?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not much space right here. Hehe. I haven´t had a visitor for some time and right now there are two! Hehehe! Great!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,QuestValue(1)>0,! -> "Good bye! Don't forget about the secrets of mathemagics." +VANISH,QuestValue(1)=0,! -> "Wait! Don't leave! I want to tell you about my surreal numbers." + + +"bye" -> "Next time we should talk about my surreal numbers.", Idle +"farewell" -> * +"job" -> "Job? JOB? Hey man - I am in prison! But you know - once upon a time - I was a powerful mage! A mage ... come to think of it .., what is that - a mage?" +"name" -> "My name is - uhm - hang on? I knew it yesterday, didn't I? Doesn't matter!" +"time" -> "Better save time than comitting a crime. I am a poet and I know it!" +"sorcerer" -> "I am the mightiest sorcerer from here to there! Yeah!" +"power" -> "Power. Hmmm. Once while we were crossing the mountains together a man named Aureus said to me that parcels are equal to power. Any idea what that meant?" +"books" -> "I have many books in my home. But only powerful people can read them. I bet you will only see three dots after the headline! Hehehe! Hahaha! Excellent!" +"mad","mage" -> "Hey! That's me! You got it! Thanks mate - now I remember my name!" +"riddle" -> "Great riddle, isn´t it? If you can tell me the correct answer, I will give you something. Hehehe!" +"something" -> "No! I won´t tell you. Shame coz it would be useful for you - hehehe." +"apple" -> Type=3585, Amount=1, "Apples! Real apples! Man I love them! Can I have one? Oh please say yes!", Topic=1 +"escape" -> "How could I escape? They only give me rotten food here. I can´t regain my powers because I have no mana!" +"key" -> "Sure I have the key! Hehehe! Perhaps I will give it to you. IF you can solve my riddle." +"mino" -> "They are trying to capture me! Or hang on! Haven't they already captured me? Hmmm - I will have to think about this." +"markwin" -> "He is the worst of them all! He is the king of the minos! May he burn in hell!" +"labyrinth" -> "It´s easy to find your way through it! Just follow the pools of mud. Hehe - useful hint, isn´t it?" +"way" -> * +"palkar" -> "He is the leader of the outcasts. I hope he will never conquer the city of Mintwallin. That would be the end of me!" +"karl" -> "Tataah!" +"demon" -> "The only monster I cannot conjure. But soon I will be powerful enough!" +"monster" -> "Yeah! There are many monsters guarding my home. Only the bravest hero will be able to slay them!" +"conjure" -> * +"home" -> * + +"number", QuestValue(1) < 1 -> "My surreal numbers are based on astonishing facts. Are you interested in learning the secret of mathemagics?", Topic=7, Amount=Random(1,4) +"math", QuestValue(1) < 1 -> * +"1+1$", QuestValue(1) < 1 -> * +"1$","+$","1$", QuestValue(1) < 1 -> * +"1$","plus","1$", QuestValue(1) < 1 -> * +"one","plus","one", QuestValue(1) < 1 -> * + +"number", QuestValue(1) > 0 -> "You already know the secrets of mathemagics! Now go and use them to learn." +"math", QuestValue(1) > 0 -> * +"1+1$", QuestValue(1) > 0 -> * +"1$","+$","1$", QuestValue(1) > 0 -> * +"1$","plus","1$", QuestValue(1) > 0 -> * +"one","plus","one", QuestValue(1) > 0 -> * +Topic=7, "yes" -> "But first tell me your favourite colour please!", Topic=8 +Topic=8,QuestValue(2)=1,"red",! -> "Very interesting. So are you ready to proceed in you lesson in mathemagics?",Topic=9 +Topic=8,QuestValue(2)=2,"blue",! -> * +Topic=8,QuestValue(2)=3,"black",! -> * +Topic=8,QuestValue(2)=4,"white",! -> * +Topic=8,QuestValue(2)=5,"orange",! -> * +Topic=8,QuestValue(2)=6,"green",! -> * +Topic=8,QuestValue(2)=7,"yellow",! -> * +Topic=8,QuestValue(2)=8,"brown",! -> * +Topic=8,QuestValue(2)=9,"violet",! -> * +Topic=8,QuestValue(2)=10,"pink",! -> * +Topic=8,QuestValue(2)=11,"silver",! -> * +Topic=8,QuestValue(2)=12,"gold",! -> * +Topic=8,QuestValue(2)=13,"grey",! -> * + +Topic=8,! -> "I think you are not in touch with yourself, come back if you have tuned in on your own feelings." + +Topic=9, "yes", Amount=1 -> "So know that everthing is based on the simple fact that 1 + 1 = 49!", SetQuestValue(1,1) +Topic=9, "yes", Amount=2 -> "So know that everthing is based on the simple fact that 1 + 1 = 94!", SetQuestValue(1,2) +Topic=9, "yes", Amount=3 -> "So know that everthing is based on the simple fact that 1 + 1 = 13!", SetQuestValue(1,3) +Topic=9, "yes", Amount=4 -> "So know that everthing is based on the simple fact that 1 + 1 = 1!", SetQuestValue(1,4) + + +"sell","rune" -> Type=3147, Amount=1, Price=10, "You want to sell me blank runes! I will give you 50000 gold for each rune! Interested?", Topic=2 + +"dp-d-ks-p-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-d-sk-p-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-d-ks-p-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-d-sk-p-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-p-ks-d-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-p-sk-d-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-p-ks-d-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-p-sk-d-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 + +Topic=1,"yes",Count(Type)>=Amount -> "Mnjam. Excellent! Thanks, man!", Delete(Type) +Topic=1,"yes" -> "Do you want to trick me? You don´t have one lousy apple!" +Topic=1 -> "Ooooooooooo." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Take my money. I can summon new money anytime - hehehe.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You do not have one." +Topic=2 -> "Hmmmmm." + +Topic=3,"yes",Count(Type)>=Amount -> "Mnjam - excellent apples. Now - about that key. You are sure want it?", Delete(Type), Topic=4 +Topic=3,"yes" -> "Get some more apples first!" +Topic=3 -> "Then go away!", Idle + +Topic=4,"yes" -> "Really, really?", Topic=5 +Topic=4 -> "Then go away!", Idle + +Topic=5,"yes" -> "Really, really, really, really?", Topic=6 +Topic=5 -> "Then go away!", Idle + +Topic=6,"yes" -> Type=2969, Data=3666, Amount=1, "Then take it and get happy - or die, hehe.", Create(Type) +Topic=6 -> "Then go away!", Idle +} diff --git a/app/SabrehavenServer/data/npc/puffels.npc b/app/SabrehavenServer/data/npc/puffels.npc new file mode 100644 index 0000000..f36025c --- /dev/null +++ b/app/SabrehavenServer/data/npc/puffels.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# puffels.npc Datenbank fuer den Zauberlehrer Puffels + +Name = "Puffels" +Outfit = (21,0-0-0-0-0) +Home = [33269,31850,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Yeah, another fool disturbing me, what a joy." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Whatever." + +"bye" -> "Whatever.", Idle +"job" -> "I have to teach the spells of least importance to some fools." +"name" -> "I am Magister Puffels, any problem with that?" +"time" -> "Where might I hide a watch, you fool?" +"king" -> "I give nothing for kings, queens... or other people at all." +"tibianus" -> * +"army" -> "Fine army that is. Half of them have already deserted." +"ferumbras" -> "The day will come even he makes a fatal casting mistake... I know what I am talking about." +"excalibug" -> "I have no use for such stuff." +"thais" -> "I was there once, almost died. The fools there mistook me for an ordinary rat, can you believe that!?" +"tibia" -> "Bah, the whole Tibia can &#&$*# my #$&*!" +"carlin" -> "I don't care about some remote cities." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I heard in Thais the new brand of cheese was... uhm..." +"rumors" -> * +"hugo" -> "Hugo? I heared it was an accident that created this beast." +"beast" -> "I don't know more about it." + +"spellbook" -> "Ask a shop owner for that." +"spell" -> "I have 'Magic Rope', 'Levitate', 'Haste', 'Berserk', 'Force Strike', 'Energy Strike', and 'Flame Strike'." + +"magic","rope" -> String="Magic Rope", Price=200, "Do you want to learn the spell 'Magic Rope' for %P gold?", Topic=1 +"levitate" -> String="Levitate", Price=500, "Do you want to learn the spell 'Levitate' for %P gold?", Topic=1 +"haste" -> String="Haste", Price=600, "Do you want to learn the spell 'Haste' for %P gold?", Topic=1 +"berserk",Knight -> String="Berserk", Price=2500, "Do you want to learn the spell 'Berserk' for %P gold?", Topic=1 +"berserk" -> "This spell is only for knights." +"force","strike",Druid -> String="Force Strike", Price=600, "Do you want to learn the spell 'Force Strike' for %P gold?", Topic=1 +"force","strike",Sorcerer -> * +"force","strike" -> "This spell is only for sorcerers and druids." +"energy","strike",Druid -> String="Energy Strike", Price=800, "Do you want to learn the spell 'Energy Strike' for %P gold?", Topic=1 +"energy","strike",Sorcerer -> * +"energy","strike" -> "This spell is only for sorcerers and druids." +"flame","strike",Druid -> String="Flame Strike", Price=800, "Do you want to learn the spell 'Flame Strike' for %P gold?", Topic=1 +"flame","strike",Sorcerer -> * +"flame","strike" -> "This spell is only for sorcerers and druids." + +Topic=1,"yes",SpellKnown(String)=1 -> "You already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "You must have level %A to learn this spell." +Topic=1,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=1,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "I thought so." +} diff --git a/app/SabrehavenServer/data/npc/pydar.npc b/app/SabrehavenServer/data/npc/pydar.npc new file mode 100644 index 0000000..7205e98 --- /dev/null +++ b/app/SabrehavenServer/data/npc/pydar.npc @@ -0,0 +1,145 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pydar.npc: Datenbank für den Pyromancer Pydar (Zwergenstadt) + +Name = "Pydar" +Outfit = (160,95-94-132-118-0) +Home = [32655,31893,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",QuestValue(44)=1,! -> "Be greeted %N! I can smell the scent of a phoenix on you!" +ADDRESS,"hi$",QuestValue(44)=1,! -> * + + +ADDRESS,"hello$",! -> "Welcome, pilgrim %N! May the flame guide you!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the fire in your heart never die." + +"scent",QuestValue(44)=1 -> "The phoenix seems to be fond of you! If you had a real phoenix egg on you, I could provide you the blessing of the spark of the phoenix more easy and cheaper!" +"phoenix","egg" -> * + +"bye" -> "May the fire in your heart never die, %N!", Idle +"farewell" -> * + +"suns" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." + +"job" -> "I am the head pyromancer of Kazordoon." +"name" -> "My name is Pydar Firefist, Son of Fire, from the Savage Axes." +"tibia" -> "That is our world." +"kazordoon" -> "Our city was founded in ancient times. Abandoned by the gods we once fought for, we created a secure haven for our people." +"big","old" -> "This mountain is said to be the oldest in the world. It is the place where fire and earth meet and separate at the same time." +"elves" -> "Stupid race. They have no understanding of the ways of the world." +"humans" -> "They took the place dwarves once held in the world. They don't see that they are destined to fall just like we did." +"orcs" -> "The arch enemy. We could have destroyed them long ago, but this would have meant doing a favour to the gods which betrayed us." +"minotaurs" -> "Another pawn the gods do not care for any longer. A discarded toy like all of the elder races." +"pyromancer" -> "We are the keepers and shepherds of the elemental force of fire." +"god" -> "The ways of the gods are imprehensible to mortals. On the other hand, the elements are raw forces and can be understood and tamed." +"keeper" -> * +"shepherd" -> * +"fire" -> "Unlike the gods, the elements don't use mortals as toys, A skilled mind can understand and even control them to some extent." +"flame" -> * +"durin" -> "Though we are through with the so-called gods, Durin, the first dwarf to aquire divine powers of his own, is considered a protector of our race." +"life" -> "Life feeds on fire and ultimately fire will feed on life." +"plant" -> "I don't care much about plants." +"citizen" -> "Many brave people are citizens of our town." +"kroox" -> "He is a smith. If you are looking for exquisite weapons and armour just talk to him." +"jimbin" -> "He and his wife are running the Jolly Axeman tavern." +"maryza" -> "She and her husband are running the Jolly Axeman tavern." +"bezil" -> "Bezil and Nezil are buying and selling equipment of all kinds." +"nezil" -> * +"uzgod" -> "Uzgod is a weaponsmith just like those in the old legends." +"etzel" -> "Etzel is a true master of the elements. He is a role-model for our youngsters, jawoll." +"gregor" -> "The leader of the Thaian Knights' guild is a man of few words." +"duria" -> "She is the first knight of Kazordoon. She is responsible for teaching our young warriors how to handle an axe." +"emperor" -> "Our emperor has his halls in the upper caves." +"kruzak" -> * +"geomancer" -> "They are followers of the path of earth." +"technomancer" -> "Those heretics believe they have discovered a new elemental force they can control easily. These fools, they'll bring doom on us all!" +"motos" -> "He is the fiercest axefighter of our times and a fine strategist." +"general" -> * +"army" -> "Our armies can defend Kazordoon against any threat by means of its strong fortifications." +"ferumbras" -> "If he ever dares enter Kazordoon I will gladly dump him into the lava. Tthe sacred flame shall bring justice upon him." +"excalibug" -> "A weapon too powerful to be wielded by mortals. It has to be returned to the fire which gave birth to it." +"news" -> "I am a busy man. I have no time for idle chitchat." +"monster" -> "May the great flame devour them all!" +"fire","devil" -> "They mock the great flame by their existence. BLAST THEM ALL! Jawoll!" +"help" -> "I an not here to help; you have to help yourself." +"quest" -> "Ask around. There's a lot to do, jawoll." +"task" -> * +"what","do" -> * +"gold" -> "Gold has been given birth to by the great flame. So it is wise to give some back to the fire now and then." +"money" -> * +"equipment" -> "Bezil and Nezil are runing a shop where you can buy all the stuff you need." +"fight" -> "You should fight like fire, fearless and without mercy." + +"heal$",Burning>0 -> "You are burning. Take it as a blessing and don't cry like a baby, jawoll." +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "Weakling! If you are not prepared to face the heat, stay out of the fire! Oh all right, I will heal you a little.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is wannig. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> " You can receive the spiritual shielding in the whiteflowertemple south of thais." +"shielding" -> * + +"spark",QuestValue(44)=1,Count(3215)>0 -> "Since the phoenix smiles upon you, you might receive this blessing for 9.000 gold while you have a phoenix egg with you. So are you ready?",Price=9000, topic=6 +"phoenix",QuestValue(44)=1,Count(3215)>0 -> * +Topic=6,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=6,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=6,"yes",Count(3215)>0,CountMoney "Oh. You do not have enough money." +Topic=6,"yes",Count(3215)>0,! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0),Bless(5) +Topic=6,! -> "Perhaps another time." + +"spark",QuestValue(44)=1,Count(3215)<1 -> "Since the phoenix smiles upon you, could have received this blessing cheaper if you had a phoenix egg with you. But because you don't have it with you, its still 10.000 gold. Is that ok?",Price=10000, topic=7 +"phoenix",QuestValue(44)=1,Count(3215)<1 -> * +Topic=7,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=7,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=7,"yes",CountMoney "Oh. You do not have enough money." +Topic=7,"yes",! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0),Bless(5) +Topic=7,! -> "Perhaps another time." + + +"spark" -> "The spark of the phoenix is given by me and by the great geomancer of the local earthtemple. Do you wish to receive my part of blessing of the phoenix for 10.000 gold?",Price=10000, topic=5 +"phoenix" -> * + + +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." +# "suns" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." +# nach oben gestellt wg. antwort auf fire +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=5,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim.", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0), Bless(5) +Topic=5,! -> "Perhaps another time." + + + +"time" -> "It's the fourth age of the yellow flame." +} diff --git a/app/SabrehavenServer/data/npc/queen.npc b/app/SabrehavenServer/data/npc/queen.npc new file mode 100644 index 0000000..5cf67f7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/queen.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# queen.npc: Datenbank für die Königin von Carlin + +Name = "Queen Eloise" +Outfit = (138,96-94-79-115-0) +Home = [32315,31753,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello","queen",! -> "I greet thee, my loyal subject." +ADDRESS,"hail","queen",! -> * +ADDRESS,"salutations","queen",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,male,! -> "Typical behaviour for males!" +VANISH,! -> "What a strange behaviour for a lady!" + +"uniform",QuestValue(233)=5 -> "I remember about those uniforms, they had a camouflage inlay so they could be worn the inside out too. I will send some colorsamples via mail to Mr. Postner.",SetQuestvalue(233,6) +"uniform" -> "The uniforms of our guards and soldiers are of unparraleled quality of course." + + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "I am Queen Eloise. It is my duty to reign over this marvellous city and the lands of the north." +"justice" -> "We women try to bring justice and wisdom to all, even to males." +"name" -> "I am Queen Eloise. For you it's 'My Queen' or 'Your Majesty', of course." +"news" -> "I don't care about gossip like a simpleminded male would do." +"tibia" -> "Soon the whole land will be ruled by women at last!" +"land" -> * +"how","are","you"-> "Thank you, I'm fine." +"castle" -> "It's my humble domain." +"sell",male -> "Sell? Your question shows that you are a typical member of your gender!" +"sell",female -> "I beg you pardon? A queen that sells things? Be serious!" +"god" -> "We honor the gods of good in our fair city, especially Crunor, of course." +"citizen" -> "All citizens of Carlin are my subjects. I see them more as my childs, though, epecially the male population." +"noodles" -> "This beast scared my cat away on my last diplomatic mission in this filthy town." +"ferumbras" -> "He is the scourge of the whole continent!" +"treasure" -> "The royal treasure is hidden beyond the grasps of any thieves by magical means." +"monster" -> "Go and hunt them! For queen and country!" +"help" -> "Visit the church or the townguards for help." +"quest" -> "I will call for heroes as soon as the need arises again." +"mission" -> * +"gold" -> "Our city is rich and prospering." +"money" -> * +"tax" -> * +"sewer" -> "I don't want to talk about 'sewers'." +"dungeon" -> "Dungeons are places where males crawl around and look for trouble." +"equipment" -> "Feel free to visit our town's magnificent shops." +"food" -> * +"time" -> "Don't worry about time in the presence of your Queen." +"hero" -> "We need the assistance of heroes now and then. Even males prove useful now and then." +"adventurer" -> * +"tax","collector"-> "The taxes in Carlin are not high, more a symbol than a sacrifice." +"queen" -> "I am the Queen, the only rightful ruler on the continent!" +"army" -> "Ask one of the soldiers about that." +"enemy" -> "Our enemies are numerous. We have to fight vile monsters and have to watch this silly king in the south carefully." +"enemies" -> * +"thais" -> "They dare to reject my reign over them!" +"city","south" -> * +"carlin" -> "Isn't our city marvellous? Have you noticed the lovely gardens on the roofs?" +"city" -> * +"shop" -> "My subjects maintain many fine shops. Go and have a look at their wares." +"merchant" -> "Ask around about them." +"craftsmen" -> * +"guild" -> "The four major guilds are the Knights, the Paladins, the Druids, and the Sorcerers." +"minotaur" -> "They havn't troubled our city lately. I guess, they fear the wrath of our druids." +"paladin" -> "The paladins are great hunters." +"legola" -> * +"elane" -> "It's a shame that the High Paladin does not reside in Carlin." +"knight" -> "The knights of Carlin are the bravest." +"trisha" -> * +"sorceror" -> "The sorcerers have a small isle for their guild. So if they blow something up it does not burn the whole city to ruins." +"lea$" -> * +"druid" -> "The druids of Carlin are our protectors and advisors. Their powers provide us with wealth and food." +"padreia" -> * +"good" -> "Carlin is a center of the forces of good, of course." +"evil" -> "The forces of evil have a firm grip on this puny city to the south." +"order" -> "The order, Crunor gives the world, is essential for survival." +"chaos" -> "Chaos is common in the southern regions, where they allow a man to reign over a realm." +"excalibug" -> "A mans tale ... that means 'nonsense', of course." +"reward" -> "If you want a reward, go and bring me something this silly King Tibianus wants dearly!" +"tbi" -> "A dusgusting organisation, which could be only created by men." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/app/SabrehavenServer/data/npc/quentin.npc b/app/SabrehavenServer/data/npc/quentin.npc new file mode 100644 index 0000000..3b94784 --- /dev/null +++ b/app/SabrehavenServer/data/npc/quentin.npc @@ -0,0 +1,139 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# quentin.npc: Datenbank für den Mönch Quentin + +Name = "Quentin" +Outfit = (57,0-0-0-0-0) +Home = [32369,32239,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, adventurer %N! If you are new in Tibia, ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking that bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "Job? I have no job. I just live for the gods of Tibia." +"name" -> "My name is Quentin." +"tibia" -> "That is where we are. The world of Tibia. Admire it's beauty." +"god" -> "They created Tibia and all life on it." +"life" -> "On Tibia there are many forms of life. There are plants and people and monsters." +"plant" -> "Just walk around, you will see grass, trees, and bushes." +"people" -> "I am a simple monk. I just know Sam, Frodo, and Gorn. They all live in the main street to the north." +"sam" -> "He is our blacksmith. He sells weapons and armour." +"frodo" -> "He is the owner of Frodo's Hut, the tavern north of this temple." +"gorn" -> "He is selling equipment. If you still have no backpack you should go and ask him for one." +"elane" -> "She is the leader of the local Paladins' guild." +"muriel" -> "Muriel is a famous sorcerer. She is the keeper of arcane secrets that are known only to few mortals." +"gregor" -> "The leader of the Knights' guild is a man of few words." +"marvik" -> "I admire the healing skills of Marvik." +"king" -> "Our king resides in the castle to the west." +"tibianus" -> * +"lynda" -> "She is a highly competent priest." +"harkath" -> "A hard man but his heart is in the right right place." +"army" -> "I don't know much about the Tibian army. Ask general Harkath Bloodblade about that." +"ferumbras" -> "Hush! Do not mention the Evil One in these walls." +"general" -> "Harkath Bloodblade is his name." +"bozo" -> "He is the king's jester, but he believes himself to be the king of fools." +"baxter" -> "He is the guard of the royal castle." +"oswald" -> "This man is spreading horrible rumours all the time." +"sherry" -> "The McRonalds run the local farm." +"mcronald" -> * +"donald" -> * +"lugri" -> "Please do not mention the fallen one." +"excalibug" -> "Legends tell us that that Excalibug is a gift of the gods. Banor used in his battles. They say it was passed on to one of his followers." +"news" -> "Sorry, I know nothing new. Please ask Frodo about that topic." +"monster" -> "There are really too many of them in Tibia. But who am I to challenge the wisdom of the gods?" +"help" -> "First you should try to get some gold to buy better equipment." +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "If you need money you should slay monsters and take their gold. Look for spiders and rats." +"money" -> * +"spider" -> "There are spiders' nests beyond our city near Gorn's shop and at the McRonalds' farm in the east." +"rat" -> "There are sewers underneath the city. They say these sewers are brimming with rats." +"sewer" -> "You can enter the sewers thorugh a sewer grate. But watch out. There are many rats. And don't forget to bring a torch." +"equipment" -> "First you should buy a bag or backpack. That way your hands will be free to hold a weapon and a shield." +"fight" -> "Take a weapon into your hand and select a target. If you are wounded you should eat some food to heal your wounds." +"slay" -> "Take a weapon into your hand and select a target. If you are wounded you should eat some food to heal your wounds." +"eat" -> "If you would like to heal your wounds you should eat some food. Frodo sells excellent meals. But if you are very weak you can also come to me. I will heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." + +"stake",QuestValue(17576)=0 -> "A blessed stake to defeat evil spirits? I do know an old prayer which is said to grant sacred power and to be able to bind this power to someone, or something. ...", + "However, this prayer needs the combined energy of ten priests. Each of them has to say one line of the prayer. ...", + "I could start with the prayer, but since the next priest has to be in a different location, you probably will have to travel a lot. ...", + "Is this stake really important enough to you so that you are willing to take this burden?", Topic=1 +Topic=1,"yes" -> "Alright, I guess you need a stake first. Maybe Gamon can help you, the leg of a chair or something could just do. Try asking him for a stake, and if you have one, bring it back to me.", SetQuestValue(17576,1), SetQuestValue(17595,1) +Topic=1 -> "Fine. You are free to decline my offer." + +"stake",QuestValue(17576)=1,Count(5941)<=0 -> "I guess you couldn't convince Gamon to give you a stake, eh?" +"stake",QuestValue(17576)=1 -> Type=5941, Amount=1, "Ah, I see you brought a stake with you. Are you ready to receive my line of the prayer then?", Topic=2 +Topic=2,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Light shall be near - and darkness afar'. Now, bring your stake to Tibra in the Carlin church for the next line of the prayer. I will inform her what to do.", SetQuestValue(17576,2) +Topic=2,"yes" -> "Where did you lost that stake?" +Topic=2 -> "I will wait for you." +"stake",QuestValue(17576)=2 -> "You should visit Tibra in the Carlin church now." +"stake",QuestValue(17576)>2 -> "You already received my line of the prayer." + +} diff --git a/app/SabrehavenServer/data/npc/quero.npc b/app/SabrehavenServer/data/npc/quero.npc new file mode 100644 index 0000000..c64615d --- /dev/null +++ b/app/SabrehavenServer/data/npc/quero.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# quero.npc: Datenbank für den Barden Quero + +Name = "Quero" +Outfit = (128,55-30-23-115-0) +Home = [32390,32220,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to someone, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I make instruments and sometimes I'm wandering through the lands of Tibia as a bard." +"name" -> "My name is Quero." +"time" -> "Sorry, I don't know what time it is." + +"music" -> "I love the music of the elves." +"elf" -> "They live in the northeast of Tibia." +"elves" -> * +"bard" -> "Selling instruments isn't enough to live on and I love music. That's why I wander through the lands from time to time." + +"benjamin" -> "He's nice." + +"offer" -> "You can buy a lyre, lute, drum, and simple fanfare." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=1 +"drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=1 +"simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=1 + +%1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyres for %P gold?", Topic=1 +%1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lutes for %P gold?", Topic=1 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drums for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfares for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/rachel.npc b/app/SabrehavenServer/data/npc/rachel.npc new file mode 100644 index 0000000..4686374 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rachel.npc @@ -0,0 +1,100 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rachel.npc: Datenbank für die Magierin Rachel + +Name = "Rachel" +Outfit = (136,58-84-86-114-0) +Home = [32343,31828,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",Sorcerer,male,! -> "Welcome back, brother %N!", Topic=1 +ADDRESS,"hello$",Sorcerer,female,! -> "Welcome back, sister %N! Isn't your name %N?", Topic=1 +ADDRESS,"hi$",Sorcerer,male,! -> "Welcome back, brother %N! Wasn't your name %N?", Topic=1 +ADDRESS,"hi$",Sorcerer,female,! -> "Welcome back, sister %N! Wasn't your name %N?", Topic=1 +ADDRESS,"hi$",! -> "Welcome %N! Whats your need?" +ADDRESS,"hello$",! -> "Welcome %N! Whats your need?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N! One after the other.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "These impatient young brats!" + +"bye" -> "Good bye, %N", Idle +"farewell" -> * +"job" -> "I am the head alchemist of Carlin. I keep the secret recipies of our ancestors. Besides, I am selling mana and life fluids, spellbooks, wands, rods and runes." +"name" -> "I am the illusterous Rachel, of course." +"time" -> "Time is of no meaning to us sorcerers." +"wisdom" -> "Wisdom arises from patience." +"patience" -> "You have to free yourself from unpatience to learn the deeper secrets of magic." +"ancestor" -> "We are a guild of old traditions and even older secrets." +"sorcerer" -> "Spells are the minor parts that make a sorcerer. To be one is a state of mind, not of a full spellbook." +"power" -> "Power is important, but it is just the way, not the ultimate goal." +"goal" -> "This secrect will be taught you by life, not by me." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and druids." +"spell$" -> "I am too busy to teach you, ask in your guild about that." +"spells" -> * + +"rune" -> "I sell blank runes and spell runes." +"spellbook" -> Type=3059, Amount=1, Price=150, "A spellbook is a nice tool for beginners. Do you want to buy one for %P gold?",Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy one for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=3 +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=3 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?",Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A runes for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=3 +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=3 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"sell","talon" -> Type=3034, Amount=1, Price=320, "Do you want to sell one of the magic gems called talon for %P gold?", Topic=6 +"sell",%1,1<%1,"talon" -> Type=3034, Amount=%1, Price=320*%1, "Do you want to sell %A magic gems called talon for %P gold?", Topic=6 + +Topic=1,"yes" -> "I thought so, what do you want?" +Topic=1,"no" -> "First lesson: DON'T LIE TO RACHEL!", Burning(10,4), EffectMe(15), EffectOpp(16) +Topic=1,sorcerer -> "I thought only intelligent persons are allowed to become sorcerers." +Topic=1 -> "I am glad that only intelligent persons are allowed to become sorcerers." + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, maybe next time." + +Topic=3,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold for an empty vial.", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "Hmm, but next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=5 +"vial" -> * +"flask" -> * +Topic=5,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=5,"yes" -> "You don't have any empty vials." +Topic=5 -> "Hmm, but please keep Tibia litter free." + +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you do not have one." +Topic=6,"yes", Amount>1 -> "Sorry, you do not have so many." +Topic=6 -> "Maybe next time." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/rahkem.npc b/app/SabrehavenServer/data/npc/rahkem.npc new file mode 100644 index 0000000..021f7f1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rahkem.npc @@ -0,0 +1,222 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rahkem.npc: Datenbank für den pyramidenpriester rahkem + +Name = "Rahkem" +Outfit = (130,0-77-87-116-0) +Home = [33194,32848,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Accept my thanks for your gift of silence." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am a humble preacher of the true revelation in the temple of the mourned flesh. I heal and teach magic spells to those who are spiritual enough." +"name" -> "I am the mourned Rahkem." +"time" -> "Time is a tool in the hands of the false gods, but it also serves to free us from our mortal prisons." + +"temple" -> "Here we mourn our mortal existence. Our flesh is our weakness and our curse, the bait for all the trials and tribulations the false gods let loose on the world." +"pharaoh" -> "Our immortal ruler, may he be blessed, is the keeper of our enlightenment and our saviour." +"ashmunrah" -> "The foolish old pharaoh withheld knowledge and power from his son, knowing that he would surpass him in every aspect. But in his infinite mercy his son granted him the chance to ascend." +"scarab" -> "The eternal burrowers are the keepers of all the secrets their kind has unearthed in countless aeons." + +"uman" -> "The beings Uman and Zathroth merged forever in the blaze that followed when the last of the true gods perished." +"zathroth" -> * +"banor" -> "Banor was the most devout minion of the false gods. Their lickspittle lapdog. Seeing they needed additional strength they granted him some of their powers, and he became a lesser false god himself." +"tibia" -> "It is likely that our world is a part of one of the dead true gods or one of his manifestations that somehow escaped destruction. We must assume this is all that is left of the original universe." +"carlin" -> "The cities that bow to the false gods will be afflicted with plague and fear until they embrace the wisdom of the pharaoh." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves suffered, but they have drawn wrong conclusions. If they do not listen to the revelations of our immortal pharaoh, pain and grief will prove to be better teachers this time." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The foolish elves hold on to life too hard to see the way to salvation. However, if we teach them to remove the shackles of flesh through pain and suffering, they might begin to see their mistake." +"elves" -> * +"elfes" -> * +"darama" -> "The continent was named after Daraman, the prophet during the reign of Ashmunrah. The new pharaoh acknowledged the power that is in names and did not change the name when he acceded to the throne." +"darashia" -> "The followers of Daraman suffer the curse of the flesh. They can't reach ascension because they never really take the all-important initial step - they only pretend to do so." +"initial","step" -> "In his preachings Daraman taught that you can conquer the tempations of the flesh through denial. However, the truth is that this constant struggle between temptation and the will blurs your vision, so no follower of Daraman can focus on ascension." +"daraman" -> "We call Daraman the one-eyed prophet, for he clearly saw that ascension is possible, but he was blind to the fact that mortality itself and not mere temptation is the first obstacle that must be overcome." +"Ankrahmun" -> "This city is a marvel of old. Our forefathers built it here on the ruins of an even older civilisation." +"pharaoh" -> "The pharaoh, praised may he be, was the first to learn the truth about mortality, ascension and the false goods." +"mortality" -> "Mortality is our prison. It makes us vulnerable for the temptations of the false gods." +"false", "gods" -> "The so-called gods are just the weakest of their kind. They are pitiful remnants from the terrible godswar between the elder gods which tore the universe appart." +"godswar" -> "In ancient times the elder gods waged war upon each other. Those that call themselves gods today were the lowest of their minions. When the last of the true gods died the great suffering begun." +"great","suffering" -> "The universe is dying. Death placed his mark on everything. Only the pharaoh can grant us freedom from mortality and open up the path of true ascension to us." +"ascension" -> "The sentient beings are all that is left of the essence of the elder gods. We can awake the dormant powers that slumber in us all. But ascension is a thorny path to follow." +"thorny","path" -> "Our mortal shells make us vulnerable to the temptations of the false gods. Only by leaving our mortality behind, we can study the true path of ascension. The balance of Akh'rah Uthun has to be changed to our favour." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the unity of the Akh, our body, the Rah, our soul and the Uthun, our memories and experiences." +"balance" -> "As long as it is mortal the body breeds temptations and distractions. Its needs make it easy for the false gods to lead us from the path of enlightenment and to ultimately steal our souls." +"steal","souls" -> "When a mortal is bound to one of the false gods by his faith this god will harvest his Rah on his death and strip away his Uthun, casting it into the void." +"Akh" -> "Your flesh is traitorous and weak. The pharaoh grants the power to conquer death to those who serve him well. Once they have entered this state of being neither dead nor alive they are ready to enter the path of ascension." +"undead" -> "Undeath is freedom from mortal needs. It is the first obvious step to divinity." +"undeath" -> * +"Rah" -> "The Rah is the ultimate treasure. The false gods need the stolen Rah to sustain their usurped powers." +"uthun" -> "The memory is what makes our personality. It is what defines us ... and its utterly worthless to the gods. For this reason destroy it to harvest our Rah." +"mourn" -> "We mortals are all to be mourned for our prison of flesh. Only through loyal servitude to the pharaoh, praised be his existence, may we escape this prison and find our true destiny." + +"arena" -> "The arena is a fitting place to test your mortal shell and to feed the power of the Rah and the Uthun." +"palace" -> "The residence of our immortal king is a temple in its own right because it is the home of a true god." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I can sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you received the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and received this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "May enlightenment be your path.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"stake",QuestValue(17576)=8,Count(5941)<=0 -> "I think you have forgotten to bring your stake, pilgrim." +"stake",QuestValue(17576)=8 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Let there be power and compassion'. Now, bring your stake to Brewster in Port Hope for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,9) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, pilgrim." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=9 -> "You should visit Brewster in Port Hope now." +"stake",QuestValue(17576)>9 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That is a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." + +} diff --git a/app/SabrehavenServer/data/npc/rashid.npc b/app/SabrehavenServer/data/npc/rashid.npc new file mode 100644 index 0000000..92e7d59 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rashid.npc @@ -0,0 +1,134 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Rashid" +Outfit = (146,100-100-119-115-2) +Home = [32314,31933,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, a customer! Be greeted, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please stand next in line %N!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Farewell, %N, may the winds guide your way.", Idle +"farewell" -> * + +"job" -> "I am a travelling trader. I don't buy everything, though. And not from everyone, for that matter." +"customer" -> * +"name" -> "I am Rashid, son of the desert." +"time" -> "It's almost time to journey on." +"ab'dendriel" -> "Elves... I don't really trust them. All this talk about nature and flowers and treehugging... I'm sure there's some wicked scheme behind all this." +"ankrahmun" -> "My beloved hometown! Ah, the sweet scent of the desert sands, the perfect shape of the pyramids... stunningly beautiful." +"carlin" -> "I have to go to Carlin once in a while, since the queen wishes to see my exclusive wares in regular intervals." +"cormaya" -> "Cormaya? Not a good place to make business, it's way too far and small." +"darashia" -> "It's not the real thing, but almost as good. The merchants there claim ridiculous prices, which is fine for my own business." +"edron" -> "Ah yes, Edron! Such a lovely and quiet island! I usually make some nice business there." +"fibula" -> "Too few customers there, it's not worth the trip." +"greenshore" -> "Um... I don't think so." +"kazordoon" -> "I don't like being underground much. I also tend to get lost in these labyrinthine dwarven tunnels, so I rather avoid them." +"liberty bay" -> "When you avoid the slums, it's a really pretty city. Almost as pretty as the governor's daughter." +"northport" -> "Um... I don't think so." +"port hope" -> "I like the settlement itself, but I don't set my foot into the jungle. Have you seen the size of these centipedes??" +"senja" -> "Um... I don't think so." +"svargrond" -> "I haven't discovered this city yet. Maybe one day." +"thais" -> "I feel uncomfortable and rather unsafe in Thais, however the business is running very good in this city." +"vega" -> "Um... I don't think so." +"venore" -> "Although it's the flourishing trade centre of Tibia, I don't like going there. Too much competition for my taste." +"king" -> "Kings, queens, emperors and kaliphs... everyone claims to be different and unique, but actually it's the same thing everywhere." +"tibianus","talon" -> "This currency is very stable. However I prefer gold." + +"buy" -> "I am sorry but I do not sell anything. However I am interested in buying several things." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"weapon" -> "I buy many different weapons. What do you want to sell?" +"helmet" -> "I buy many different helmets. What do you want to sell?" +"armor" -> "I buy many different armors. What do you want to sell?" +"shield" -> "I buy many different shields. What do you want to sell?" +"trousers" -> "I buy many different legs. What do you want to sell?" +"legs" -> * +"you","buy" -> "I do buy. You want to sell anything?" + +"war","axe" -> Type=3342, Amount=1, Price=9000, "You want sell a war axe for %P gold?", Topic=2 +"beastslayer","axe" -> Type=3344, Amount=1, Price=1500, "You want sell a beastslayer axe for %P gold?", Topic=2 +"daramanian","axe" -> Type=3328, Amount=1, Price=1000, "You want sell a daramanian waraxe for %P gold?", Topic=2 +"heavy","machete" -> Type=3330, Amount=1, Price=90, "You want sell a heavy machete for %P gold?", Topic=2 +"daramanian","mace" -> Type=3327, Amount=1, Price=110, "You want sell a daramanian mace for %P gold?", Topic=2 + +"dragon","scale","mail" -> Type=3386, Amount=1, Price=40000, "You want sell a dragon scale mail for %P gold?", Topic=2 +"dwarven","armor" -> Type=3397, Amount=1, Price=30000, "You want sell a dwarven armor for %P gold?", Topic=2 +"golden","armor" -> Type=3360, Amount=1, Price=20000, "You want sell a golden armor for %P gold?", Topic=2 +"leopard","armor" -> Type=3404, Amount=1, Price=1000, "You want sell a leopard armor for %P gold?", Topic=2 +"pirate","shirt" -> Type=6095, Amount=1, Price=500, "You want sell a pirate shirt for %P gold?", Topic=2 + +"skull","helmet" -> Type=5741, Amount=1, Price=40000, "You want sell a skull helmet for %P gold?", Topic=2 +"beholder","helmet" -> Type=3408, Amount=1, Price=7500, "You want sell a beholder helmet for %P gold?", Topic=2 +"devil","helmet" -> Type=3356, Amount=1, Price=1000, "You want sell a devil helmet for %P gold?", Topic=2 +"pirate","hat" -> Type=6096, Amount=1, Price=1000, "You want sell a pirate hat for %P gold?", Topic=2 + +"steel","boots" -> Type=3554, Amount=1, Price=30000, "You want sell a steel boots for %P gold?", Topic=2 +"pirate","boots" -> Type=5461, Amount=1, Price=3000, "You want sell a pirate boots for %P gold?", Topic=2 +"crocodile","boots" -> Type=3556, Amount=1, Price=1000, "You want sell a crocodile boots for %P gold?", Topic=2 +"pirate","knee","breeches" -> Type=5918, Amount=1, Price=200, "You want sell a pirate knee breeches for %P gold?", Topic=2 + +"demon","shield" -> Type=3420, Amount=1, Price=30000, "You want sell a demon shield for %P gold?", Topic=2 +"medusa","shield" -> Type=3436, Amount=1, Price=9000, "You want sell a medusa shield for %P gold?", Topic=2 +"castle","shield" -> Type=3435, Amount=1, Price=5000, "You want sell a castle shield for %P gold?", Topic=2 +"scarab","shield" -> Type=3440, Amount=1, Price=2000, "You want sell a scarab shield for %P gold?", Topic=2 +"dark","shield" -> Type=3421, Amount=1, Price=400, "You want sell a dark shield for %P gold?", Topic=2 +"tortoise","shield" -> Type=6131, Amount=1, Price=150, "You want sell a tortoise shield for %P gold?", Topic=2 +"bone","shield" -> Type=3441, Amount=1, Price=80, "You want sell a bone shield for %P gold?", Topic=2 + +"ancient","amulet" -> Type=3025, Amount=1, Price=200, "You want sell an ancient amulet for %P gold?", Topic=2 +"scarab","amulet" -> Type=3018, Amount=1, Price=200, "You want sell a scarab amulet for %P gold?", Topic=2 + +"light","shovel" -> Type=5710, Amount=1, Price=300, "You want sell a light shovel for %P gold?", Topic=2 + +%1,1<%1,"war","axe" -> Type=3342, Amount=%1, Price=9000*%1, "You want sell %A war axes for %P gold?", Topic=2 +%1,1<%1,"beastslayer","axe" -> Type=3344, Amount=%1, Price=1500*%1, "You want sell %A beastslayer axes for %P gold?", Topic=2 +%1,1<%1,"daramanian","axe" -> Type=3328, Amount=%1, Price=1000*%1, "You want sell %A daramanian waraxes for %P gold?", Topic=2 +%1,1<%1,"heavy","machete" -> Type=3330, Amount=%1, Price=90*%1, "You want sell %A heavy machetes for %P gold?", Topic=2 +%1,1<%1,"daramanian","mace" -> Type=3327, Amount=%1, Price=110*%1, "You want sell %A daramanian maces for %P gold?", Topic=2 + +%1,1<%1,"dragon","scale","mail" -> Type=3386, Amount=%1, Price=40000*%1, "You want sell %A dragon scale mails for %P gold?", Topic=2 +%1,1<%1,"dwarven","armor" -> Type=3397, Amount=%1, Price=30000*%1, "You want sell %A dwarven armors for %P gold?", Topic=2 +%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=20000*%1, "You want sell %A golden armors for %P gold?", Topic=2 +%1,1<%1,"leopard","armor" -> Type=3404, Amount=%1, Price=1000*%1, "You want sell %A leopard armors for %P gold?", Topic=2 +%1,1<%1,"pirate","shirt" -> Type=6095, Amount=%1, Price=500*%1, "You want sell %A pirate shirts for %P gold?", Topic=2 + +%1,1<%1,"skull","helmet" -> Type=5741, Amount=%1, Price=40000*%1, "You want sell %A skull helmets for %P gold?", Topic=2 +%1,1<%1,"beholder","helmet" -> Type=3408, Amount=%1, Price=7500*%1, "You want sell %A beholder helmets for %P gold?", Topic=2 +%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=1000*%1, "You want sell %A devil helmets for %P gold?", Topic=2 +%1,1<%1,"pirate","hat" -> Type=6096, Amount=%1, Price=1000*%1, "You want sell %A pirate hats for %P gold?", Topic=2 + +%1,1<%1,"steel","boots" -> Type=3554, Amount=%1, Price=30000*%1, "You want sell %A steel boots for %P gold?", Topic=2 +%1,1<%1,"pirate","boots" -> Type=5461, Amount=%1, Price=3000*%1, "You want sell %A pirate boots for %P gold?", Topic=2 +%1,1<%1,"crocodile","boots" -> Type=3556, Amount=%1, Price=1000*%1, "You want sell %A crocodile boots for %P gold?", Topic=2 +%1,1<%1,"pirate","knee","breeches" -> Type=5918, Amount=%1, Price=200*%1, "You want sell %A pirate knee breeches for %P gold?", Topic=2 + +%1,1<%1,"demon","shield" -> Type=3420, Amount=%1, Price=30000*%1, "You want sell %A demon shields for %P gold?", Topic=2 +%1,1<%1,"medusa","shield" -> Type=3436, Amount=%1, Price=9000*%1, "You want sell %A medusa shields for %P gold?", Topic=2 +%1,1<%1,"castle","shield" -> Type=3435, Amount=%1, Price=5000*%1, "You want sell %A castle shields for %P gold?", Topic=2 +%1,1<%1,"scarab","shield" -> Type=3440, Amount=%1, Price=2000*%1, "You want sell %A scarab shields for %P gold?", Topic=2 +%1,1<%1,"dark","shield" -> Type=3421, Amount=%1, Price=400*%1, "You want sell %A dark shields for %P gold?", Topic=2 +%1,1<%1,"tortoise","shield" -> Type=6131, Amount=%1, Price=150*%1, "You want sell %A tortoise shields for %P gold?", Topic=2 +%1,1<%1,"bone","shield" -> Type=3441, Amount=%1, Price=80*%1, "You want sell %A bone shields for %P gold?", Topic=2 + +%1,1<%1,"ancient","amulet" -> Type=3025, Amount=%1, Price=200*%1, "You want sell %A ancient amulets for %P gold?", Topic=2 +%1,1<%1,"scarab","amulet" -> Type=3018, Amount=%1, Price=200*%1, "You want sell %A scarab amulets for %P gold?", Topic=2 + +%1,1<%1,"light","shovel" -> Type=5710, Amount=%1, Price=300*%1, "You want sell %A light shovels for %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount,RealPremium -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes",Count(Type)>=Amount -> "I'm sorry %N, but I only serve premium account customers." +Topic=2,"yes" -> "You not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe you sell it next time." + +} diff --git a/app/SabrehavenServer/data/npc/ratamari.npc b/app/SabrehavenServer/data/npc/ratamari.npc new file mode 100644 index 0000000..e97b5ba --- /dev/null +++ b/app/SabrehavenServer/data/npc/ratamari.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ratamari.npc: Datenbank für den Djinnspion Rata'mari + +Name = "Rata'mari" +Outfit = (21,0-0-0-0-0) +Home = [33036,32625,7] +Radius = 1 + +Behaviour = { + +ADDRESS,"piedpiper",QuestValue(281)>0,! -> "Meep? I mean - hello! Sorry, %N... Being a rat has kind of grown on me." +ADDRESS,! -> Idle +BUSY,"piedpiper",QuestValue(281)>0,! -> "And now there's more of you? Great! More attention is just what I need. Step back and wait, %N!", Queue +BUSY,! -> NOP +VANISH -> "Meep!" + +"bye" -> "Remember - this conversation never took place!", Idle +"farewell" -> * +"name" -> "I have many names and faces. But I suppose you can call me Rata'mari." +"rata'mari" -> "Shh! The walls have ears, you know!?" +"password" -> "'Pied Piper'. Hilarious. Fa'Hradin has a very strange sense of humour." +"piedpiper" -> * +"job" -> "I'm a spy. Now guess what I've come here for!" +"trade" -> "Trade? Look at me! Do I look as if I had any pockets to stash stuff in?" +"daraman" -> "Daraman? Well, he was a great prophet, but... look, this is not a good point of time to discuss philosophy, ok?" +"rat" -> "Your power of observation is stunning. Yes, I'm a rat." +"human" -> "So Fa'hradin turned you into a human? That's really hard, buddy. Rats, humans... what comes next?" +"fa'hradin" -> "That damn dabbler! 'I am going to disguise you', he said. 'Nobody will ever recognise you', he said! Now look at me! That botching fool! And I can't even bite his ankles!" +"djinn" -> "I used to be one, too. That was before Fa'hradin had the bright idea to turn me into a flea-ridden rodent." +"efreet" -> "After many months of careful study I have come to the conclusion the efreet are much more different from us Marid then I thought! Their skin is green, for a start!" +"marid" -> "I haven't seen my brothers for a long time." +"mal'ouquah" -> "I hate this place. It is cold and damp! And the local rats are real snobs!" +"ashta'daramai" -> "I miss the place. I really feel homesick, you know? ...", + "It makes my mouth water just to think of all the delicious cheese Bo'ques is hiding in his private larder." +"gabel" -> "Gabel is our undisputed leader, even though he is too modest to brag with it. Even though Fa'hradin coordinates all military operations it is always Gabel who has the final say." +"king" -> "No more kings for us! We are a democratic people now! Well, sort of." +"malor" -> "I have found out all kinds of things about him! He is left-handed, his favourite dish is hyena chop roasted in sandwasp honey marinade, and he has this weird habit of scratching his right ear whenever he is angry - which happens quite often, I might add." +"zathroth" -> "Zathroth was the creator of our race. Which doesn't mean we like him. But too be honest, I don't think this is the time and place to discuss religious matters." +"tibia" -> "A nice world. I think I prefer it to all others. Not that I have seen any others, of course." +"darashia" -> "I have heard nice things about that city. I wish I had an assignment there rather than in this god-forsaken place." +"scarab" -> "A scarab? What? Where? Hey, don't give me shock like that! Did you know they eat rats?!" +"edron" -> "I have heard lots about the human cities to the north. Perhaps I will be sent there one day. That would be a lovely change." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That is the one place where I would hate to work even more. My sources there have told me the city is now controlled by some loony who thinks he is a god or something." +"pharaoh" -> "They say the new pharaoh is completely out of his mind. Rumour has it that he became an undead on his own free will! I think that says it all." +"palace" -> "The palace in Ankrahmun used to be renowned for its splendour and its hospitable atmosphere. Now I suppose rats are the only living creatures that are still tolerated in this place. Hang on... I hope this does not give Gabel ideas." +"ascension" -> "I am not much into religion, but from what I know this is an important part of that foolish pharaoh's creed." +"rah" -> "Yes... rings a bell. Has to do with Ankrahmun's pharaoh, hasn't it?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "Gosh, these mountains! Can you imagine what they look like to somebody who is moving three inches above the floor? They are so... massive! " +"kha'labal" -> "The Kha'labal is a huge desert to the east. It is a cruel, inhospitable land. Not even a rat could survive there very long." +"lamp" -> "Oh to sleep in warm, comfy lamp! It's been such a long time!" +"melchior" -> "Hm. No - doesn't ring a bell." +"alesar" -> "His defection was a serious blow to our cause. Both Gabel and Fa'hradin are more concerned about it than they dare admit. ...", + "Alesar is the most gifted smith the djinn race has ever produced, and now he works for the enemy. I am not entirely sure why he defected, but I am convinced it had nothing to do with money. ...", + "Alesar has been a devout follower of Daraman for as long as I can remember, and he thought little of worldly possessions. In fact, from what I've seen Malor and Baa'leal were quite as astonished about it all as Gabel and Fa'hradin. ...", + "All I know is that Alesar used to be a kind, helpful djinn. Then one day he disappeared. When he returned he had changed. He had become taciturn and bitter. And all of a sudden he hated humans. All of them. ...", + "I think he suffered a deep spiritual crisis. Whatever caused this crisis is anyone's guess." +"baa'leal" -> "Baa'leal is Malor's lieutenant. He is fiercely loyal to his boss, and that is one of the main reasons why no Efreet has ever dared challenge Malor's authority. If it hadn't been for him a new leader would have come up in Malor's absence. ...", + "I guess that is why despite all of his shortcomings he still has Malor's trust and support. He is not the brightest djinn under the sun, you know." + +"report", QuestValue(282)=0 -> "You have come for the report? Great! I have been working hard on it during the last months. And nobody came to pick it up. I thought everybody had forgotten about me! ...", + "Do you have any idea how difficult it is to hold a pen when you have claws instead of hands? ...", + "But - you know - now I have worked so hard on this report I somehow don't want to part with it. At least not without some decent payment. ...", + "All right - listen - I know Fa'hradin would not approve of this, but I can't help it. I need some cheese! I need it now! ...", + "And I will not give the report to you until you get me some! Meep!", SetQuestValue(282,1) +"report", QuestValue(282)=1 -> "Ok, have you brought me the cheese, I've asked for?", Topic=1 +"cheese", QuestValue(282)=1 -> * +Topic=1,"yes",Count(3607)>0,! -> "Meep! Meep! Great! Here is the spyreport for you!", Amount=1, Delete(3607), Create(3232), SetQuestValue(282,2) +Topic=1 -> "No cheese - no report." +"report", QuestValue(282)=2 -> "I already gave you the report. I'm not going to write another one!" +} diff --git a/app/SabrehavenServer/data/npc/ray.npc b/app/SabrehavenServer/data/npc/ray.npc new file mode 100644 index 0000000..4be1dc1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ray.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ray.npc: Datenbank für den Postbeamten Ray + +Name = "Ray" +Outfit = (128,10-96-106-115-0) +Home = [32621,32747,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you back soon." + +"bye" -> "I hope to see you back soon.", Idle +"farewell" -> * + +"kevin" -> "Ah, yes we stay in touch via mail of course." +"postner" -> * +"postmasters","guild" -> "The guild is far away, but the mail keeps us close to each other somehow." +"join" -> "The guild is always looking for competent recruits. You can submit your application to our headquarters." +"headquarters" -> "It can be found easily. It's on the road from Thais to Kazordoon and Ab'Dendriel." + + + +"job" -> "I am responsible for this post office. If you have questions about the mail system or the depots, just ask me." +"name" -> "My name is Ray." +"ray" -> "Yes, that's me." +"time" -> "Now it's %T." +"king" -> "The king lives far away in the lovely city of Thais, but even he can be reached by our mailing system." +"tibianus" -> * +"army" -> "We got not the best men of the Thaian army to guard this colony. Given the number of difficulties this colony faces, this is quite a problem." +"ferumbras" -> "I hope this colony is too remote and meaningless to him to care for a visit." +"excalibug" -> "There are rumours about some hidden stone tablets mentioning that weapon. Adventurers claim to have seen those tablets in the ancient lizard city." +"news" -> "There are so many news that I cannot retell them all. Talk to the colonists and keep your ears open." +"thais" -> "All cities are covered by our mailing system." +"carlin" -> * +"apes" -> "They are a pest. A quite dangerous pest as far as I can tell." +"lizard" -> "The lizards give me shivers. They are so alien, even more than the minotaurs or orcs we know from the surroundings of Thais." +"dworcs" -> "Those bloodthirsty headhunters live in the south. I heard only horrible stories about them and I believe they are not exaggerated." +"jungle" -> "The jungle is a dangerous place. Many got lost there and never returned." + + +@"gen-post.ndb" + +} diff --git a/app/SabrehavenServer/data/npc/raymondstriker.npc b/app/SabrehavenServer/data/npc/raymondstriker.npc new file mode 100644 index 0000000..6b95469 --- /dev/null +++ b/app/SabrehavenServer/data/npc/raymondstriker.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Raymond Striker" +Outfit = (151,58-77-60-114-1) +Home = [32350,32588,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted. Is there anything I can do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "May there always be wind in your sails, %N.", Idle +"farewell" -> * + +"name" -> "Raymond Striker, at your service." +"job" -> "I am one of the free captains of the Shattered Isles, who selected me as their spokesperson." +"captains" -> "We don't consider ourselves as pirates, but as freedom fighters. But there are indeed pirates out there. They are true bandits, cutthroats and murderers. They are the ones responsible for the horrible acts that are accredited to us." +"ferumbras" -> "It is rumoured that he has established some base on one of the isles in the past." +"Liberty","Bay" -> "One day the city will bear this name justly and with pride." +"Thais" -> "Thais is not the source of the evil that has befallen our isles. It might end up as a victim itself whenever Venore feels powerful enough." +"Venore" -> "The scheming trade barons of Venore know nothing but profit and power. For that, they lie and murder and would readily sell their souls." +"Carlin" -> "We have loose trade relations with Carlin." +"Eleonore",QuestValue(17502)>12 -> "She is my only and true love. It hurts me deeply that the mermaid's magic let me forget her. I feel so guilty and can only hope that Eleonore can forgive my weakness." +"King" -> "I doubt the king gains as much from the occupation of the isles as the Venoreans do." + +#shattered +"mission",QuestValue(17502)=6 -> "Don't ask about silly missions. All I can think about is this lovely mermaid.", Topic=1 +"Eleonore",QuestValue(17502)=6 -> "Eleonore ... Yes, I remember her... vaguely. She is a pretty girl ... but still only a girl and now I am in love with a beautiful and passionate woman. A true mermaid even.", Topic=1 +Topic=1,"mermaid" -> "The mermaid is the most beautiful creature I have ever met. She is so wonderful. It was some kind of magic as we first met. A look in her eyes and I suddenly knew there would be never again another woman in my life but her.", SetQuestValue(17502,7) +"mermaid",QuestValue(17502)>6,QuestValue(17502)<12 -> "The mermaid is the most beautiful creature I have ever met. She is so wonderful. It was some kind of magic as we first met. A look in her eyes and I suddenly knew there would be never again another woman in my life but her." +"mermaid",QuestValue(17502)>12 -> "I am deeply ashamed that I lacked the willpower to resist her spell. Thank you for your help in that matter. Now my head is once more free to think about our mission." +"marina",QuestValue(17502)>12 -> * + +# Meriana_Quest +"mission",QuestValue(17502)>12,QuestValue(17520)=0 -> "Ask around in the settlement where you can help out. If you have proven your worth I might have some missions for you.", SetQuestValue(17520,1) +"mission",QuestValue(17520)=1,QuestValue(17521)=4,QuestValue(17522)=3,QuestValue(17523)=2,QuestValue(17524)=6 -> "Indeed, I could use some help. The evil pirates of Nargor have convinced an alchemist from Edron to supply them with a substance called Fafnar's Fire ...", + "It can burn even on water and is a threat to us all. I need you to travel to Edron and pretend to the alchemist Sandra that you are the one whom the other pirates sent to get the fire ...", + "When she asks for a payment, tell her 'Your continued existence is payment enough'. That should enrage any member of the Edron academy enough to refuse any further deals with the pirates.", SetQuestValue(17520,2) +"mission",QuestValue(17520)=1 -> "Ask around in the settlement where you can help out. If you have proven your worth I might have some missions for you." +"mission",QuestValue(17520)=2 -> "I am still waiting news from you about Fafnar's Fire from alchemist Sandra." +"mission",QuestValue(17520)=3 -> "I think that means 'mission accomplished'. Hehe. I guess that will put an end to their efforts to buy any alchemical substance from Edron.", SetQuestValue(17520,4) +"mission",QuestValue(17520)=4 -> "The mission on which I will send you is vital to our cause. It is a sabotage mission. Nargor is guarded by several heavy catapults. ...", + "I need you to sabotage the most dangerous of those catapults which can be found right in their harbour, aiming at ships passing by the entrance. ...", + "Get a fire bug - you can buy them in Liberty Bay - and set this catapult on fire. ...", + "Make sure to use the bug on the left part of the catapult where its lever is. That is where it's most vulnerable. ...", + "If you see a short explosion, you will know that it worked. I will tell Sebastian to bring you to Nargor, but beware. ...", + "Of course, he can't drop you off directly in the pirate's base. However, we have discovered a secret way into the Howling Grotto. ...", + "Try to make your way through the caves of Nargor to reach their harbour. This is where you will find the catapult in question.", SetQuestValue(17520,5) +"mission",QuestValue(17520)=5 -> "Sail with Sebastian to Nargor and make your way to the pirate's harbour. Once arrived there, find the catapult and use a fire bug on the left part. ...", + "A small explosion will show you that you have succeeded. Afterwards, report to me about your mission. Good luck!" + +"mission",QuestValue(17520)=6 -> "You did it! Excellent!", SetQuestValue(17520,7) +"mission",QuestValue(17520)=7,QuestValue(17525)<2 -> "I have heard that Duncan has a mission for you this time." +"mission",QuestValue(17520)=7 -> "If you manage to accomplish this vital mission you will prove yourself to be a worthy member of our community. I might even grant you your own ship and pirate clothing! ...", + "So listen to the first step of my plan. I want you to infiltrate their base. Try to enter their tavern, which means that you have to get past the guard. ...", + "You will probably have to deceive him somehow, so that he thinks you are one of them. ...", + "In the tavern, the pirates feel safe and plan their next strikes. Study ALL of their maps and plans lying around ...", + "Afterwards, return here and report to me about your mission.", SetQuestValue(17520,8) + +"mission",QuestValue(17520)=8,QuestValue(17528)=1,QuestValue(17529)=1,QuestValue(17530)=1 -> "Well done, my friend. That will help us a lot. Of course there are other things to be done though. ...", + "I learned that Klaus, the owner of the tavern, wants me dead. He is offering any of those pirates a mission to kill me....", + "If we could convince him that you fulfilled that mission, the pirates will have the party of their lives. This would be our chance for a sneak attack to damage their boats and steal their plunder! ...", + "Obtain this mission from him and learn what he needs as a proof. Then return to me and report to me about your mission so we can formulate an appropriate plan.", SetQuestValue(17520,9) +"mission",QuestValue(17520)=8 -> "I am waiting for the information about the pirate plans my friend." + +"mission",QuestValue(17520)=9 -> "Still planning to go to Klaus right?" +"mission",QuestValue(17520)=9,QuestValue(17531)=1 -> Type=6105, Amount=1, "My pillow?? They know me all too well... I've owned it since my childhood. However. Here, take it and convince him that I am dead.", Create(Type), SetQuestValue(17520,10) +"mission",QuestValue(17520)=10 -> "Pillow is already yours. Just go to Klaus and convince him that I am dead." +"mission",QuestValue(17520)=10,QuestValue(17531)=2 -> Type=2994, Amount=1, "Incredible! You did what no other did even dare to think about! You are indeed a true hero to our cause ...", + "Sadly I have no ship that lacks a captain, else you would of course be our first choice. I am still true to my word as best as I am able. ...", + "So take this as your very own ship. Oh, and remind me about the pirate outfit sometime.", Create(Type), SetQuestValue(17520,11) +"mission" -> "Sorry, I don't have any missions for you." + +"outfit",QuestValue(17520)=11 -> "Ah, right! The pirate outfit! Here you go, now you are truly one of us.", AddOutfit(155), AddOutfit(151), SetQuestValue(17520,12), EffectOpp(13) +"outfit",QuestValue(17520)=12 -> "The pirate of the first choice!" +"addon",QuestValue(17520)=12 -> * +} diff --git a/app/SabrehavenServer/data/npc/razan.npc b/app/SabrehavenServer/data/npc/razan.npc new file mode 100644 index 0000000..2931d88 --- /dev/null +++ b/app/SabrehavenServer/data/npc/razan.npc @@ -0,0 +1,166 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# razan.npc: Datenbank fuer den Waffenmeister Razan + +Name = "Razan" +Outfit = (146,95-19-10-58-3) +Home = [33239,32409,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Be patient, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"job" -> "I am the weaponmaster of Caliph Kazzan the great." +"name" -> "Razan ... Razan Ibn Rublai." +"time" -> "You are talking of what you are wasting right now?" +"caliph" -> "We owe caliph Kazzan our loyality and gratitude, thrice praised be his name." +"kazzan" -> * +"shalmar" -> "He is competent. That's fine enough for a mage." +"djinn" -> "Some people in Darashia rely to much on the services of these creatures. I wonder if they keep the path in mind." +"path" -> "The path of enlightenment, leading to ascension as thaught to us by Daraman." +"Daraman" -> "Better talk to Kasmir about that." +"enlightenment" -> * +"ascension" -> * +"ferumbras" -> "Maybe a worthy oponent, but probably only another of these spellcasting cowards." +"army" -> "This information is confidential." +"guards" -> * +"kasmir" -> "You will find him in the Muhayin, the sacred tower of meditation." +"excalibug" -> "The skill should make a fighter strong, not the weapon." +"news" -> "I don't care for rumours but for facts." +"weaponmaster" -> "I mastered the arts of close combat and distance fight alike. I teach both, paladins and knights in their ways." +"tibia" -> "The world is a dangerous place for body and for soul." +"knight" -> "The way of the warrior is not that different from the way to ascension." +"paladin" -> * +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Rely more on your skills, though." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +Paladin,"spell" -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell" -> "Sorry, I only teach spells to knights and paladins." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level would you like to learn a spell?", Topic=2 +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 + +Topic=3,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=3,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=3 +Topic=3,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 +Topic=3 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=3 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=4 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=4 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=4 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "Return when you have enough gold." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." + +"addon",QuestValue(17557)=5,male -> "Only oriental weaponmasters may wear it. Just like you." +"outfit",QuestValue(17557)=5,male -> * +"addon" -> "My turban? Eh no, you can't have it. Only oriental weaponmasters may wear it after having completed a difficult task." +"outfit" -> * + +"mission",female -> "I don't have any tasks available for you my lady." +"task",female -> * + +"mission",QuestValue(17557)=0,male -> "You mean, you would like to prove that you deserve to wear such a turban?", Topic=5 +"task",QuestValue(17557)=0,male -> * +Topic=5,"yes" -> "Alright, then listen to the following requirements. We are currently in dire need of ape fur since the Caliph has requested a new bathroom carpet. ...", + "Thus, please bring me 100 pieces of ape fur. Secondly, it came to our ears that the explorer society has discovered a new undersea race of fishmen. ...", + "Their fins are said to allow humans to walk on water! Please bring us 100 of these fish fin. ...", + "Third, if the plan of walking on water should fail, we need enchanted chicken wings to prevent the testers from drowning. Please bring me two. ...", + "Last but not least, just drop by with 100 pieces of blue cloth and I will happily show you how to make a turban. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=6 +Topic=5 -> "Maybe next time." +Topic=6,"yes" -> "Excellent! Come back to me once you have collected 100 pieces of ape fur.", SetQuestValue(17557,1), SetQuestValue(17594,1) +Topic=6 -> "Maybe next time." + +"ape","fur",QuestValue(17557)=1,male -> Type=5883, Amount=100, "Have you really managed to fulfil the task and brought me 100 pieces of ape fur?", Topic=7 +"mission",QuestValue(17557)=1,male -> * +"task",QuestValue(17557)=1,male -> * +Topic=7,"yes",Count(Type)>=Amount -> "Ahhh, this softness! I'm impressed, %N. You're on the best way to earn that turban. Now, please retrieve 100 fish fins.", Delete(Type), SetQuestValue(17557,2) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe next time." + +"fish","fin",QuestValue(17557)=2,male -> Type=5895, Amount=100, "Were you able to discover the undersea race and retrieved 100 fish fins?", Topic=8 +"mission",QuestValue(17557)=2,male -> * +"task",QuestValue(17557)=2,male -> * +Topic=8,"yes",Count(Type)>=Amount -> "I never thought you'd make it, %N. Now we only need two enchanted chicken wings to start our waterwalking test!", Delete(Type), SetQuestValue(17557,3) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe next time." + +"enchanted","chicken","wing",QuestValue(17557)=3,male -> Type=5891, Amount=2, "Were you able to get hold of two enchanted chicken wings?", Topic=9 +"mission",QuestValue(17557)=3,male -> * +"task",QuestValue(17557)=3,male -> * +Topic=9,"yes",Count(Type)>=Amount -> "Great, thank you very much. Just bring me 100 pieces of blue cloth now and I will happily show you how to make a turban.", Delete(Type), SetQuestValue(17557,4) +Topic=9,"yes" -> "You don't have that many." +Topic=9 -> "Maybe next time." + +"blue","cloth",QuestValue(17557)=4,male -> Type=5912, Amount=100, "Ah, have you brought the 100 pieces of blue cloth?", Topic=10 +"mission",QuestValue(17557)=4,male -> * +"task",QuestValue(17557)=4,male -> * +Topic=10,"yes",Count(Type)>=Amount -> "Ah! Congratulations - even if you are not a true weaponmaster, you surely deserve to wear this turban. Here, I'll tie it for you.", Delete(Type), SetQuestValue(17557,5), AddOutfitAddon(146,2), AddOutfitAddon(150,2), EffectOpp(13) +Topic=10,"yes" -> "You don't have that many." +Topic=10 -> "Maybe next time." + +"mission",QuestValue(17557)=5 -> "Sorry but I don't have any tasks for you." +"task",QuestValue(17557)=5 -> * +} diff --git a/app/SabrehavenServer/data/npc/redlilly.npc b/app/SabrehavenServer/data/npc/redlilly.npc new file mode 100644 index 0000000..003247f --- /dev/null +++ b/app/SabrehavenServer/data/npc/redlilly.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Red Lilly" +Outfit = (136,77-21-27-30-0) +Home = [32283,32821,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello sweetie. If you need general equipment, stuff like that, let me know." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am talking to a customer. Please stand in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye sweetie.", Idle +"farewell" -> * +"job" -> "I am selling equipment of all kinds. Do you need anything?" +"name" -> "I am Red Lilly." +"time" -> "It is exactly %T. Maybe you want to buy a watch?" +"food" -> "Sorry, I don't sell food." + +"equipment" -> "I sell shovels, picks, scythes, fishing rods, sixpacks of worms, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, footballs, and watches. I also sell means of illumination." +"offer" -> * +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"illumination" -> "I sell torches, candlesticks, candelabra, and oil." + +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"bag" -> Type=5950, Amount=1, Price=4, "Do you want to buy a beach bag for %P gold?", Topic=1 +"backpack" -> Type=5949, Amount=1, Price=20, "Do you want to buy a beach backpack for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=3, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=3, "Do you want to buy a candlestick for %P gold?", Topic=1 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 + +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=5950, Amount=%1, Price=4*%1, "Do you want to buy %A beach bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=5949, Amount=%1, Price=20*%1, "Do you want to buy %A beach backpacks for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=3*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=3*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"candelab" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 + +"worm" -> "I sell worms only in six-packs for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/riddler.npc b/app/SabrehavenServer/data/npc/riddler.npc new file mode 100644 index 0000000..486fa0b --- /dev/null +++ b/app/SabrehavenServer/data/npc/riddler.npc @@ -0,0 +1,94 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# riddler.npc: Datenbank für den Rätselgeist Riddler + +#ACHTUNG TOPIC 13 und 14 mittendrin benutzt, da nachträglich!! +#WICHTIG: Riddler MUSSS bei rätseln nach EINEM GANZEN WORT fragen, sonst geht cheat + +Name = "Riddler" +Outfit = (48,0-0-0-0-0) +Home = [32479,31902,2] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "%N! HEHEHEHE! Another fool! Excellent!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, you'll die next!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "COWARD! CHICKEN! HEHEHEHE!" + +"bye" -> "HEHEHE! I knew you don't have the stomach.", Idle +"name" -> "I am known as the riddler. That is all you need to know." +"job" -> "I am the guardian of the paradox tower." +"time" -> "It is the age of the talon." +"tower" -> "This tower, of course, silly one. It holds my master's treasure." +"paradox" -> * +"master" -> "His name is none of your business." +"guard" -> "I am guarding the treasures of the tower. Only those who pass the test of the three sigils may pass." +"treasure" -> * +"test" -> "Death awaits those who fail the test of the three seals! Do you really want me to test you?", Topic=1 +"sigil" -> * +"key" -> "The key of this tower! You will never find it! A malicious plant spirit is guarding it!" +"door" -> * + +Topic=1,"yes",QuestValue(212)=0 -> "FOOL! Now you're doomed! But well ... So be it! Let's start out with the Seal of Knowledge and the first question: What name did the necromant king choose for himself?",SetQuestValue(212,1), Topic=2 +Topic=1,"yes",QuestValue(212)=1 -> "So you think you're smart! But well ... So be it! Let's start out with the Seal of Knowledge and the first question: What do I have in my pocket?", Topic=14 +Topic=1,"no" -> "HEHEHE! I knew you wouldn't have the stomach.", Idle + +Topic=14,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=2,"goshnar",! -> "HOHO! You have learned your lesson well. Question number two then: Who or what is the feared Hugo?", Topic=3 +Topic=2,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=3,QuestValue(211)=4,"demonbunny",! -> "HOHO! Right again. All right. The final question of the first seal: Who was the first warrior to follow the path of the Mooh'Tah?", Topic=4 +Topic=3,QuestValue(211)<>4,"demonbunny",! -> "Hmmm, so you think cheating will get you through that test? Then your final question of the first seal is: What is the meaning of life?", Topic=13 +Topic=3,QuestValue(211)=4,"demonrabbit",! -> "HOHO! Right again. All right. The final question of the first seal: Who was the first warrior to follow the path of the Mooh'Tah?", Topic=4 +Topic=3,QuestValue(211)<>4,"demonrabbit",! -> "Hmmm, so you think cheating will get you through that test? Then your final question of the first seal is: What is the meaning of life?", Topic=13 +Topic=3,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) +Topic=13,! -> "WRONG! Next time get your own answers. To hell with thee, cheater!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=4,"Tha'kull",! -> "HOHO! Lucky you. You have passed the first seal! So ... would you like to continue with the Seal of the Mind?", Topic=5 +Topic=4,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=5,"yes" -> "As you wish, foolish one! Here is my first question: Its lighter then a feather but no living creature can hold it for ten minutes?", Topic=6 +Topic=5 -> "HEHEHE! I knew you don't have the stomach.",SetQuestValue(212,0), Idle + +Topic=6,"breath",! -> "That was an easy one. Let's try the second: If you name it, you break it.", Topic=7 +Topic=6,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=7,"silence",! -> "Hm. I bet you think you're smart. All right. How about this: What does everybody want to become but nobody to be?", Topic=8 +Topic=7,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=8,"old",! -> "ARGH! You did it again! Well all right. Do you wish to break the Seal of Madness?", Topic=9 +Topic=8,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=9,"yes" -> "GOOD! So I will get you at last. Answer this: What is your favourite colour?", Topic=10 +Topic=9 -> "HEHEHE! I knew you don't have the stomach.",SetQuestValue(212,0), Idle + +Topic=10,"red",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,1), Topic=11 +Topic=10,"blue",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,2), Topic=11 +Topic=10,"black",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,3), Topic=11 +Topic=10,"white",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,4), Topic=11 +Topic=10,"orange",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,5), Topic=11 +Topic=10,"green",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,6), Topic=11 +Topic=10,"yellow",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,7), Topic=11 +Topic=10,"brown",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,8), Topic=11 +Topic=10,"violet",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,9), Topic=11 +Topic=10,"pink",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,10), Topic=11 +Topic=10,"silver",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,11), Topic=11 +Topic=10,"gold",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,12), Topic=11 +Topic=10,"grey",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,13), Topic=11 + +Topic=10,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=11,"nothing",! -> "NO! NO! NO! That can't be true. You're not only mad, you are a complete idiot! Ah well. Here is the last question: What is 1 plus 1?", Topic=12 +Topic=11,"none",! -> * +Topic=11,! -> "SORRY I AM NOT ALLOWD TO HELP HEHEHE!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=12,QuestValue(1)=1,"49$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=2,"94$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=3,"13$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=4,"1$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) +} diff --git a/app/SabrehavenServer/data/npc/robin.npc b/app/SabrehavenServer/data/npc/robin.npc new file mode 100644 index 0000000..3af77f4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/robin.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# robin.npc: Datenbank für den Jäger Robin + +Name = "Robin" +Outfit = (129,77-118-118-115-0) +Home = [32287,32252,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, a visitor. Greetings %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Learn some patience %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"job" -> "I am the Chief Huntsman of Thais." +"name" -> "I am Robin. Some call me Rob, others Woody." +"time" -> "Time... it's running that fast when you are as old as me." +"king" -> "I am his Master of the Hunt as I was his father's Master of the Hunt." +"tibianus" -> * +"quentin" -> "My buddy Quentin is getting old, too. Things were different in our youth." +"lynda" -> "So young and so beautiful! She makes even an old man as me... uhm... feel a bit younger again." +"harkath" -> "Another one of a few friends of my youth who's still left." +"army" -> "These kids call themselves an army... In the old times we had a REAL army, I tell ya..." +"general" -> * +"ferumbras" -> "A misguided follower of evil." +"sam" -> "I have not much use for heavy armor." +"gorn" -> "Sells a lot of useful stuff that guy. I rember the days when we were so poor that we could not afford anything the former owner offered." +"frodo" -> "Ah, I love that hut. I liked it as it was Iwan's hut, I loved it as it was Pridence's hut, and I think I will never stop to love this place." +"elane" -> "A master, or better mistress, of the bow. But with her big feet she just chases all game away." +"muriel" -> "These mages still give me shivers. I remember the first time this Ferumbras guy showed his ugly face here." +"sorcerer" -> * +"gregor" -> "Can you imagine this youngster handles a guild? Ah, come on." +"marvik" -> "Druids have their ways with nature, but they would rather cuddle a bear than hunting it." +"druid" -> * +"bozo" -> "Such guys don't live long. The grandfather of our king had a new jester every season." +"baxter" -> "I hardly know him." +"oswald" -> "Oh, what a charming young man. He's often here asking me about my youth and the people I met in my life." +"sherry" -> "The farmers are fine fellows." +"donald" -> * +"mcronald" -> * +"crunor" -> "Crunor gives and takes. That is his way. As long as we don't hunt more then we need we are at balance with Crunor." +"lugri" -> "Can you imagine his father was such a fine guy? A shame what his son has become." +"excalibug" -> "I don't like swords in general." +"news" -> "News? In the woods I learn nothing of importance to the world." +} diff --git a/app/SabrehavenServer/data/npc/roderick.npc b/app/SabrehavenServer/data/npc/roderick.npc new file mode 100644 index 0000000..3a4bff1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/roderick.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# roderick.npc: Datenbank für den thaischen Botschafter Roderick of Thais (Elfenstadt) + +Name = "Roderick" +Outfit = (130,38-129-129-19-0) +Home = [32672,31699,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N!", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am ambassador of our beloved king, Tibianus III." +"name" -> "I am Roderick of Thais, commoner." +"time" -> "Ask someone else." +"elves" -> "Though there are differences, I am sure we can live in peace and harmony with that noble race." +"dwarfs" -> "The dwarfs are verry dilligent and crafty people. Our contracts with kazordoon asure the best for both of our races." +"kazordoon" -> * +"humans" -> "Though there are differences to other races, I am sure we can live in peace and harmony with them." +"troll" -> "I heared about them working in the local mines. I am not sure if i like the concept of having such creatures within the walls of a city." +"carlin" -> "We are watching their relations with Ab'Denriel closely." +"venore" -> "The tradesmen of venore show great interest in trade cotracts with the elves." +"cenath" -> "I look forward to improve our relations with them." +"kuridai" -> * +"deraisim" -> * +"abdaisim" -> "Unfortunately I've had no contact with them yet." +"teshial" -> "They hardly seem more then an elven myth." +"ferumbras" -> "A threat to all free races." +"crunor" -> "I am not familiar enough with the different faithes to discuss them properly." +"excalibug" -> "A nice myth but nothing more." +"news" -> "We don't hear much at this place." +"magic" -> "I am impressed by the magic the elves are able to wield. Many of them can cast and even teach spells." +"druid" -> "The elven magic is somewhat similar to that of the druids." +"sorcerer" -> "Perhaps Thaian sorcerers can teach the elves their magic in exchange for knowledge of that noble race." +"tibianus" -> "Our beloved ruler seeks friendship and peace with the elves of Ab'Dendriel." +"olrik" -> "He is my servant and responsible for the mail. I wish he would not spent so much time with elven ladies and work harder." +} diff --git a/app/SabrehavenServer/data/npc/rodney.npc b/app/SabrehavenServer/data/npc/rodney.npc new file mode 100644 index 0000000..8ed30b1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rodney.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rodney.npc: Datenbank für den Lebensmittelhändler Rodney + +Name = "Rodney" +Outfit = (128,95-100-116-76-0) +Home = [32971,32041,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"name" -> "My name is Rodney." +"job" -> "I sell some local fruits." +"time" -> "It is %T right now." +"king" -> "King Tibianus! May the gods bless him!" +"tibianus" -> * +"army" -> "I am glad that the Thaian garrison is here to protect us." +"ferumbras" -> "Sounds like the name of a foreign soup to me." +"excalibug" -> "What is that? Is it tasty?" +"thais" -> "The Thaian protectorate serves our beloved Venore well." +"tibia" -> "I have seen only little of it." +"carlin" -> "We have little contact with far Carlin, but I heared some renegade Amazons are terrorizing the area." +"edron" -> "Our climate is quite rough, so we can only grow wheat here, but no fruits." +"news" -> "Rumour goes that some amazons, who where banished from Carlin, took refuge near the swamps." +"rumour" -> * +"rumor" -> * +"amazon" -> * + +"buy" -> "I can offer some of the local fruits." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have apples, cherries, grapes, and pears. What would you like?" + +"grape" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 +"pear" -> Type=3584, Amount=1, Price=4, "Do you want to buy a pear for %P gold?", Topic=1 + +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"cherry" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"cherries" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 +%1,1<%1,"pear" -> Type=3584, Amount=%1, Price=4*%1, "Do you want to buy %A pears for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I'm sorry, you are out of funds." +Topic=1 -> "Too bad." +} diff --git a/app/SabrehavenServer/data/npc/rokyn.npc b/app/SabrehavenServer/data/npc/rokyn.npc new file mode 100644 index 0000000..cea5a81 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rokyn.npc @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rokyn.npc: Datenbank für den Bankangestellten Rokyn + +Name = "Rokyn" +Outfit = (160,58-87-57-95-0) +Home = [33021,32053,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, Hiho, %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, %N gimme a minute, ok?", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"job" -> "I can change money for you." +"name" -> "I am Rokyn Pursesniffer, son of Fire, proud member of the Molten Rock fellowship." +"time" -> "It is exactly %T right now." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/romella.npc b/app/SabrehavenServer/data/npc/romella.npc new file mode 100644 index 0000000..567cc96 --- /dev/null +++ b/app/SabrehavenServer/data/npc/romella.npc @@ -0,0 +1,105 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# romella.npc: Datenbank fuer die Waffenhändlerin Romella + +Name = "Romella" +Outfit = (139,79-39-77-115-0) +Home = [32913,32117,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "I welcome thee, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just one more moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye and please bring more gold next time . I mean, it would be nice to see you again." + +"bye" -> "Goodbye and please bring more gold next time . I mean, it would be nice to see you again.", Idle +"farewell" -> * +"job" -> "I am Romella, and I will be serving you today." +"shop" -> * +"name" -> * +"time" -> "It is %T." +"king" -> "The only royal thing we feel here is the royal tax." +"tibianus" -> * +"army" -> "Our warehouse is the main supplier of the local garrison." +"ferumbras" -> "Make sure to buy some extra weapons before facing that one." +"excalibug" -> "I heard the amazons are after it." +"news" -> "It says the amazons are looking for a certain magical weapon in this area." +"amazon" -> "I wonder how they finance themselves. I bet they are secretly trading in some strange stuff." +"help" -> "The weapons we sell are all help you need." +"monster" -> "Just buy enough weapons and you don't have to fear them." +"swamp" -> "Don't go exploring without weapons. Especially you'll need a machete." +"thanks" -> "You are welcome." +"thank","you" -> * + +"offer" -> "I sell several weapons." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, sabres, and machetes. What's your choice?" + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/rose.npc b/app/SabrehavenServer/data/npc/rose.npc new file mode 100644 index 0000000..a5c4a3c --- /dev/null +++ b/app/SabrehavenServer/data/npc/rose.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rose.npc: Blumenverkäuferin Rose in Venore + +Name = "Rose" +Outfit = (136,79-77-112-116-0) +Home = [32971,32034,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Crunor's Finest Warehouse, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Rose, nice to meet you, %N." +"rose" -> "That's me. I am not for sale. " +"job" -> "Here you may buy some of the most beautiful flowers." +"time" -> "Sorry, I have no watch on me." +"news" -> "You mean my specials, don't you?" + +"offer" -> "I am selling beautiful flowers here." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-flowers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/ross.npc b/app/SabrehavenServer/data/npc/ross.npc new file mode 100644 index 0000000..428a1a7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ross.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Ross" +Outfit = (128,75-37-116-76-0) +Home = [32245,32713,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi %N. Can I do something for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/rowenna.npc b/app/SabrehavenServer/data/npc/rowenna.npc new file mode 100644 index 0000000..6982f0a --- /dev/null +++ b/app/SabrehavenServer/data/npc/rowenna.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rowenna.npc: Datenbank für die Waffenhändlerin Rowenna + +Name = "Rowenna" +Outfit = (139,132-38-76-38-0) +Home = [32324,31794,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the finest weaponshop in the land, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "Learn patience, male!", Queue +BUSY,"hi$",male,! -> * +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * +"job" -> "I am blacksmith and shopowner. If you need weapons or armor you are at the right place." +"shop" -> * +"name" -> "My name is Rowenna." +"time" -> "Right now it's %T." +"help" -> "I sell and buy weapons. Some of the finest in the land, indeed." +"monster" -> "Are we talking about the fured or scaly ones or just about males? Ha, ha, ha!." +"dungeon" -> "In our fair city is no place for dungeons. I heared rumours that the crypts in the east are haunted." +"sewer" -> "Our city has a sewersystem, of course! But we leave it to the males to take care of it." +"thanks" -> "You are welcome." +"thank","you" -> "You are welcome." +"ghostlands" -> "Only the mad would travel there ... the few sane people who went there returned mad. I am not comfortable with enemys from beyond the grave, you know?" + +"buy" -> "What do you need? I sell only weapons. For armor, ask Cornelia." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are light and heavy weapons." +"weapon" -> "I have light and heavy weapons. What are you looking for?" + +"light" -> "I have clubs, daggers, spears, swords, maces, rapiers, morning stars, and sabres. What's your choice?" +"heavy" -> "I have the best two handed swords in Tibia. I also sell battle hammers, battle axes, and the famous carlin swords. What's your choice?" +"armor" -> "I sell only weapons. For armor, ask Cornelia in the other shop." +"shield" -> * +"helmet" -> * +"trousers" -> * +"legs" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"carlin","sword" -> Type=3283, Amount=1, Price=473, "Do you want to buy one of the excellent carlin swords for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "Do you want to buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "Do you want to buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "Do you want to buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=473*%1, "Do you want to buy %A of the excellent carlin swords for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=8, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "Do you want to sell a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Do you want to sell a club? Hmm, I give you %P gold for this garbage, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "Do you want to sell a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "Do you want to sell a war hammer for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=8*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "Do you want to sell %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Do you want to sell %A clubs? Hmm, I give you %P gold for this garbage, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "Do you want to sell %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "Do you want to sell %A war hammers for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/rudolph.npc b/app/SabrehavenServer/data/npc/rudolph.npc new file mode 100644 index 0000000..12c1a90 --- /dev/null +++ b/app/SabrehavenServer/data/npc/rudolph.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rudolph.npc: Datenbank für den Schneider Rudolph + +Name = "Rudolph" +Outfit = (128,41-29-78-76-0) +Home = [33212,31812,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, a customer. Hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Oh, so wait a little, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh, good bye." + +"bye" -> "Oh, good bye.", Idle +"name" -> "I'm Rudolph, you know." +"job" -> "Oh, I am a tailor, can't you see?" +"time" -> "Oh, now it's %T." +"king" -> "Oh, the king. What a well dressed man he is." +"tibianus" -> * +"army" -> "Oh, such handsome guys and such ugly uniforms." +"ferumbras" -> "Oh, dear." +"excalibug" -> "Oh, that thing must be dangerous. One could hurt himself quite badly with it I guess." +"thais" -> "Oh, what a lovely city it was once." +"tibia" -> "Oh, there is not much sense for fashion in this world." +"carlin" -> "Oh, these women ... oh, go away." +"edron" -> "Oh, what a lovely city it is." +"news" -> "Oh, we tailors learn much, but don't talk about it. It's the tailors' code of honor, you know." +"rumors" -> * + +"offer" -> "Oh, I have wonderful clothes and shoes." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"clothes" -> "Oh, I have wonderful jackets, capes, tunics, leather legs, and scarfs." +"shoes" -> "Oh, I have wonderful leather boots and sandals." + +"jacket" -> Type=3561, Amount=1, Price=12, "Oh, do you want to buy one of my wonderful jackets for %P gold?", Topic=1 +"tunic" -> Type=3563, Amount=1, Price=10, "Oh, do you want to buy one of my wonderful green tunics for %P gold?", Topic=1 +"cape" -> Type=3565, Amount=1, Price=9, "Oh, do you want to buy one of my wonderful capes for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Oh, do you want to buy one of my wonderful leather legs for %P gold?", Topic=1 +"scarf" -> Type=3572, Amount=1, Price=15, "Oh, do you want to buy one of my wonderful silky scarfs for %P gold?", Topic=1 +"sandals" -> Type=3551, Amount=1, Price=2000, "Oh, do you want to buy one of my wonderful sandals for %P gold?", Topic=1 +"leather","boot" -> Type=3552, Amount=1, Price=2, "Oh, do you want to buy one of my wonderful leather boots for %P gold?", Topic=1 + +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=12*%1, "Oh, do you want to buy %A of my wonderful jackets for %P gold?", Topic=1 +%1,1<%1,"tunic" -> Type=3563, Amount=%1, Price=10*%1, "Oh, do you want to buy %A of my wonderful green tunics for %P gold?", Topic=1 +%1,1<%1,"cape" -> Type=3565, Amount=%1, Price=9*%1, "Oh, do you want to buy %A of my wonderful capes for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Oh, do you want to buy %A of my wonderful leather legs for %P gold?", Topic=1 +%1,1<%1,"scar" -> Type=3572, Amount=%1, Price=15*%1, "Oh, do you want to buy %A of my wonderful silky scarves for %P gold?", Topic=1 +%1,1<%1,"sandals" -> Type=3551, Amount=%1, Price=2000*%1, "Oh, do you want to buy %A of my wonderful sandals for %P gold?", Topic=1 +%1,1<%1,"leather","boot" -> Type=3552, Amount=%1, Price=2*%1, "Oh, do you want to buy %A of my wonderful leather boots for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Oh, here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, you do not have enough money." +Topic=1 -> "Oh, but next time." +} diff --git a/app/SabrehavenServer/data/npc/sam.npc b/app/SabrehavenServer/data/npc/sam.npc new file mode 100644 index 0000000..9f2f4d5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sam.npc @@ -0,0 +1,212 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sam.npc: Datenbank fuer den Schmied Sam + +Name = "Sam" +Outfit = (131,57-112-48-95-0) +Home = [32360,32199,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$","sam",! -> "Hi %N. Can I do something for you?" +ADDRESS,"hi$","sam",! -> * +ADDRESS,"hello$",! -> "Welcome to my shop, adventurer %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again." + +"bye" -> "Good bye and come again.", Idle +"farewell" -> * +"job" -> "I am the blacksmith. If you need weapons or armor - just ask me." +"shop" -> * +"name" -> "My name is Samuel, but you can call me Sam." +"time" -> "It is %T." +"king" -> "The king supports Tibia's economy a lot." +"tibianus" -> * +"quentin" -> "He is a monk of some kind!" +"lynda" -> "Uhm! ---blush---" +"harkath" -> "A warrior who is a joy for Banor." +"general" -> * +"army" -> "I supply the army with weapons and armor." +"ferumbras" -> "A threat for mankind! Buy weapons to be ready to face him." +"sam" -> "I was named after my grandfather." +"gorn" -> "He can tell a tale or two about his adventures with baxter in their younger days." +"frodo" -> "I don't like crowded places like his bar." +"elane" -> "Oh, I hardly know her." +"muriel" -> "Sorcerers seldom need my skills." +"gregor" -> "His guild relies heavily on my wares." +"marvik" -> "I never visited his ... cave or whatever it's called." +"bozo" -> "He is funny now and then." +"baxter" -> "A fine warrior." +"oswald" -> "Oswald isn't one of the most liked people in this city." +"sherry" -> "The McRonalds are the local farmers, aren't they?" +"donald" -> * +"mcronald" -> * +"lugri" -> "I just know some rumours that he is a follower of evil." +"excalibug" -> "It is rumoured to be a weapon beyond mortal craftsmanship." +"news" -> "I know nothing of interest." +"help" -> "I sell and buy weapons, armor, helmets, and shields. So you are able to slash the monsters." +"monster" -> "Yeah, these awful beasts. They live in the forests near the city and in the sewers and dungeons." +"dungeon" -> "Below our city are the sewers and I heard about a passage to the deeper dungeons." +"sewer" -> * +"passage" -> "Don't ask me. I have never been there." +"thanks" -> "You are welcome." +"thank","you" -> * + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain and brass armors. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell a spear for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"sell","magic","plate","armor" -> "WOW! Do you really want to sell me a MAGIC plate armor?", Topic=3 +Topic=3,"yes" -> Type=3366, Amount=1, Price=6400,"Oh, unbelievable! I would pay %P gold for this wonderful piece of armor. Are you still interested?", Topic=4 +Topic=3 -> "Hmmm, what a pity! I am looking for such an armor since I live in Thais." +Topic=4,"yes",Count(Type)>=Amount -> "Finally it is mine! Here is your money. Can I be of any further help?", Delete(Type), CreateMoney +Topic=4,"yes" -> "Argl! You do not have one! Trying to tease me? Get lost or I call the guards!",Idle +Topic=4 -> "Maybe my offer is too low? Unfortunately I can not bring up more money, I am just a smith." + +"backpack",QuestValue(289)>0 -> "Yes, you brought back my old backpack. Thank you again." +"backpack",QuestValue(289)<1 -> Type=3244, Amount=1, "What? Are you telling me you found my old adventurer's backpack that I lost years ago??",Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Thank you verry much! This brings back good old memories! Please, as a reward, travel to kazordoon and ask my old friend Kroox to provide you a special dwarven armor. ...", + "I will mail him about you immediately. Just tell him, his old buddy sam is sending you.", Delete(Type),SetQuestValue(289,1) + +Topic=5,"yes",Count(Type) "No, you don't have my old backpack. What a pity." +Topic=5,"no" -> "What a pity." + +"addon",ExpiringQuestValue(17544)>0 -> "Oh, Gregor sent you? I see. It will be my pleasure to adorn your helmet. Please give me some time to finish it." +"adorned","helmet",ExpiringQuestValue(17544)>0 -> * + +"addon",ExpiringQuestValue(17544)<0,QuestValue(17542)=5 -> "Just in time, %N. Your helmet is finished, I hope you like it.", SetQuestValue(17542,6), AddOutfitAddon(139,2), AddOutfitAddon(131,2), EffectOpp(13) +"adorned","helmet",ExpiringQuestValue(17544)<0,QuestValue(17542)=5 -> * + +"addon",QuestValue(17542)=6 -> "Sorry, Gregor told me to adorn only one helmet for you and you have already received one." +"adorned","helmet",QuestValue(17542)=6 -> * + +"task",QuestValue(17650)=0,knight -> Amount=17651, "Young knight, I see you need decent weapon but those are too expensive, right. Hmm... I can't give you for free. ...", + "However, if you could kill 50 orcs to prove your trustworthy willingness I will reward you the weapon of your choose. Deal?", Topic=120 + +"task",QuestValue(17651)=50,QuestValue(17650)=1,knight -> "Well done, %N. Which type of weapon do you use the most: axe, sword or club?", Topic=121 +Topic=121,"axe" -> "Very well. I have some spare barbarian axe for you!", SetQuestValue(17650,2), SetQuestValue(17652,0), Type=3317, Amount=1, Create(Type) +Topic=121,"sword" -> "Very well. I have some spare serpent sword for you!", SetQuestValue(17650,2), SetQuestValue(17652,0), Type=3297, Amount=1, Create(Type) +Topic=121,"club" -> "Very well. I have some spare clerical mace for you!", SetQuestValue(17650,2), SetQuestValue(17652,0), Type=3311, Amount=1, Create(Type) +Topic=121 -> "If you can't decide right now you can come later." + +"task",QuestValue(17652)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young knight. Come back once you are done.", SetQuestValue(17652,Amount), SetQuestValue(Amount,0), SetQuestValue(17650,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." + +} diff --git a/app/SabrehavenServer/data/npc/samir.npc b/app/SabrehavenServer/data/npc/samir.npc new file mode 100644 index 0000000..7e7bdd7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/samir.npc @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Samir" +Outfit = (153,38-40-39-114-3) +Home = [33027,32417,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a moment!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"life" -> "You would not want to lead such a life, believe me. And I simply refuse to take anyone as my apprentice anymore." +"job" -> "I don't have a job anymore. Long time ago, I used to be an assassin... but that is a dark chapter of my past. Nowadays I'm a simple hermit." +"hermit" -> "That is the life I chose now. My sins can't be made undone, but my life in solitude is my self-imposed punishment." +"assassin" -> "It is true - I once made a living from... well, putting people away who made others feel uncomfortable. It is not an easy life." +"apprentice" -> "No way. I am old and weary. If you really want to throw your life away and become an assassin, go bother Atrad, Vescu or Erayo and try not to get killed." +"Atrad" -> "The red death. As far as I know, he hides somewhere on the Forbidden Islands. Usually he prefers hot places, which fits his temper." +"Vescu" -> "The green death. I think the last time I heard about him, he roamed Tiquanda. In his spare time he prefers jungle surroundings, which matches his confused mind." +"Erayo" -> "The blue death. If he isn't on a killing spree, he loves places with a lot of water, such as the Laguna Islands. He is a quiet one, but don't underestimate him." + +} diff --git a/app/SabrehavenServer/data/npc/sandra.npc b/app/SabrehavenServer/data/npc/sandra.npc new file mode 100644 index 0000000..22054e9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sandra.npc @@ -0,0 +1,118 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sandra.npc: Datenbank für die Trankhändlerin Sandra + +Name = "Sandra" +Outfit = (140,115-95-127-76-1) +Home = [33258,31840,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and please come back soon." + +"bye" -> "Good bye and please come back soon.", Idle +"name" -> "I am lady Sandra Astralian." +"job" -> "I sell potions and magic fluids." +"time" -> "Buy a watch." +"king" -> "I was guest at his castle on my visits to Thais." +"tibianus" -> * +"army" -> "Do I look as if I'd fraternize with such people?" +"ferumbras" -> "A disgusting person indeed." +"excalibug" -> "I am not interested in tales only kids belive in." +"thais" -> "A city full of disgusting people with ill manners." +"tibia" -> "The world is a place of barbarianism." +"carlin" -> "I plan to visit this city one day." +"edron" -> "Isn't it a wonderful town?" +"news" -> "Nothing I would talk to you about." +"rumors" -> * + +"offer" -> "I'm selling life and mana fluids and several potions." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"fluid" -> * +"potion" -> "I'm selling potions of slime, blood, urine, oil, and distilled water." + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +"slime" -> Type=2874, Data=6, Amount=1, Price=12, "Do you want to buy a potion of slime for %P gold?", Topic=2 +"blood" -> Type=2874, Data=5, Amount=1, Price=15, "Do you want to buy a potion of blood for %P gold?", Topic=2 +"urine" -> Type=2874, Data=8, Amount=1, Price=10, "Do you want to buy a potion of urine for %P gold?", Topic=2 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy a potion of oil for %P gold?", Topic=2 +"water" -> Type=2874, Data=1, Amount=1, Price=8, "Do you want to buy a potion of distilled water for %P gold?", Topic=2 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=11 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=11 +"bp","mana","fluid" -> * + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=11 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=11 +%1,1<%1,"bp","mana","fluid" -> * + +"deposit",QuestValue(17545)=1 -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial",QuestValue(17545)=1 -> * +"flask",QuestValue(17545)=1 -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +"mission",QuestValue(17520)=2 -> "Pssht, not that loud. So they have sent you to get... the stuff?", Topic=4 +"fafnar","fire",QuestValue(17520)=2 -> * +Topic=4,"yes" -> "Finally. You have no idea how difficult it is to keep something secret here. And you brought me all the crystal coins I demanded?", Topic=5 +Topic=4 -> "How do you even know about the stuff then..." +Topic=5,"Your","continued","existence","is","payment","enough" -> "What?? How dare you?! I am a sorcerer of the most reknown academy on the face of this world. Do you think some lousy pirates could scare me? Get lost! Now! I will have no further dealings with the likes of you!", SetQuestValue(17520,3) +Topic=5 -> "I see no deal with you then." + +"deposit",QuestValue(17545)=0 -> "The Edron academy has introduced a bonus system. Each time you deposit 100 vials without claiming the money for it, you will receive a lottery ticket. ...", + "Some of these lottery tickets will grant you a special potion belt accessory, if you bring the ticket to me. ...", + "If you join the bonus system now, I will ask you each time you are bringing back 100 or more vials to me whether you claim your deposit or rather want a lottery ticket. ...", + "Of course, you can leave or join the bonus system at any time by just asking me for the 'bonus'. ...", + "Would you like to join the bonus system now?", Topic=6 +"vial",QuestValue(17545)=0 -> * +"flask",QuestValue(17545)=0 -> * +Topic=6,"yes" -> "Great! I've signed you up for our bonus system. From now on, you will have the chance to win the potion belt addon!", SetQuestValue(17545,2) +Topic=6,"no" -> "Alright. I removed your name from our list. If you want to join again and get the chance to win a potion belt addon, just ask me for the 'bonus'.", SetQuestValue(17545,1) +Topic=6 -> "Maybe another time." + +"deposit",QuestValue(17545)=2 -> "Would you like to get a lottery ticket instead of the deposit for your vials?", Data=0, Topic=7 +"vial",QuestValue(17545)=2 -> * +"flask",QuestValue(17545)=2 -> * +Topic=7,"yes",Count(2874)>=100 -> "Ok! Here take this lottery ticket.", DeleteAmount(2874, 100), Type=5957, Amount=1, Create(Type) +Topic=7,"yes" -> "You don't have 100 empty vials." +Topic=7 -> "Maybe another time." + +"bonus",QuestValue(17545)=2 -> "Would you like to leave our bonus system?", Topic=8 +Topic=8,"yes" -> "Alright. I removed your name from our list. If you want to join again and get the chance to win a potion belt addon, just ask me for the 'bonus'.", SetQuestValue(17545,1) +Topic=8 -> "Alright. Your name is still in our bonus system list." + +"bonus",QuestValue(17545)=1 -> "Would you like to join our bonus system?", Topic=9 +Topic=9,"yes" -> "Alright. I added your name from in to the bonus system list. Wish you best of luck on depositing vials!", SetQuestValue(17545,2) +Topic=9 -> "Maybe another time." + +"winning","lottery","ticket",QuestValue(17546)=0 -> Type=5958, Amount=1, "Are you here to claim a prize?", Topic=10 +"prize",QuestValue(17546)=0 -> * +Topic=10,"yes",Count(Type)>=Amount -> "Congratulations! Here, from now on you can wear our lovely potion belt as accessory.", Delete(Type), SetQuestValue(17546,1), SetQuestValue(17594,1), AddOutfitAddon(138,1), AddOutfitAddon(133,1), EffectOpp(13) +Topic=10,"yes" -> "Where is yours winning lottery ticket then?" +Topic=10 -> "Maybe another time." + +"winning","lottery","ticket",QuestValue(17546)=1 -> "I am sorry, but since you already have our potion belt we have no further prizes left for you. Maybe try to gift the winning lottery ticket for someone who really want to claim the accessory." +"prize",QuestValue(17546)=1 -> * + +Topic=11,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=11,"yes" -> "Come back, when you have enough money." +Topic=11 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/santaclaus.npc b/app/SabrehavenServer/data/npc/santaclaus.npc new file mode 100644 index 0000000..a5edbbb --- /dev/null +++ b/app/SabrehavenServer/data/npc/santaclaus.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# 17608 - any task in progress + +Name = "Santa Claus" +Outfit = (160,0-112-93-95-0) +Home = [32316,31937,8] +Radius = 3 + +Behaviour = { +@"gen-xmas.ndb" +} diff --git a/app/SabrehavenServer/data/npc/sarina.npc b/app/SabrehavenServer/data/npc/sarina.npc new file mode 100644 index 0000000..41889e0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sarina.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sarina.npc: Datenbank für die Händlerin Sarina + +Name = "Sarina" +Outfit = (136,41-72-95-96-0) +Home = [32334,31808,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am talking to a customer. Please stand in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment of all kinds. Do you need anything?" +"name" -> "I am Sarina. I am selling everything the adventurer needs." +"time" -> "It is exactly %T. Maybe you want to buy a watch?" +"food" -> "Sorry, I don't sell food." +"ghostlands" -> "Since the druids sealed that placed with their magic, rarely anyone was there. Perhaps whatever haunted that place is long gone, who knows." + +"equipment" -> "I sell shovels, picks, scythes, fishing rods, sixpacks of worms, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, footballs, and watches. I also sell means of illumination." +"offer" -> * +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"illumination" -> "I sell torches, candlesticks, candelabra, and oil." + +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"bag" -> Type=2860, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2868, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=3, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=3, "Do you want to buy a candlestick for %P gold?", Topic=1 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 + +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2860, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2868, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=3*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=3*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"candelab" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 + +"worm" -> "I sell worms only in six-packs for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/scott.npc b/app/SabrehavenServer/data/npc/scott.npc new file mode 100644 index 0000000..eeedbbb --- /dev/null +++ b/app/SabrehavenServer/data/npc/scott.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# scott.npc: Datenbank für den Wirt Scott auf Senja + +Name = "Scott" +Outfit = (131,75-38-77-96-0) +Home = [32138,31659,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little inn, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you again." + +"bye" -> "I hope to see you again.", Idle +"farewell" -> * +"job" -> "I'm the keeper of the inn. You can buy food here." +"name" -> "My name is Scott." +"time" -> "It is exactly %T." + +"tibia" -> "Oh, I'm happy to live in this world full of thrilling things." +"thais" -> "It's the capital in the southwest of Tibia." +"carlin" -> "Sometimes I travel to Carlin and visit the market." +"queen" -> "She is a strong and wise leader. We owe protection from evil monsters to her." +"senja" -> "It's a peaceful island. Cold and lonesome but I like it." +"mage" -> "It is said that there are some secrets to discover around the mage's castle." +"castle" -> * + +"do","you","sell" -> "You can get bread, cheese, ham, or meat." +"do","you","have" -> * +"offer" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/sebastianmeriana.npc b/app/SabrehavenServer/data/npc/sebastianmeriana.npc new file mode 100644 index 0000000..aed8f18 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sebastianmeriana.npc @@ -0,0 +1,41 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Sebastian" +Outfit = (151,95-53-109-115-3) +Home = [32349,32624,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ho, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","nargor",Premium,QuestValue(17520)>3,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32025,32812,7), EffectOpp(11) +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) +ADDRESS,"bring","me","to","nargor",Premium,QuestValue(17520)>3,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32025,32812,7), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye, %N.", Idle +"farewell" -> * +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Where do you want to go? I can sail you to Liberty Bay and Nargor." +"passage" -> * +"meriana" -> "Yes. We are in Meriana." + +"liberty","bay" -> Price=50, "Do you seek a passage to Liberty Bay for %P?", Topic=1 +"nargor",QuestValue(17520)<4 -> "Sorry, but you have to get permission from the Raymond Striker if you wan't to sail to Nargor." +"nargor" -> Price=50, "Do you seek a passage to Nargor for %P?", Topic=2 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,32812,7), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/sebastiannargor.npc b/app/SabrehavenServer/data/npc/sebastiannargor.npc new file mode 100644 index 0000000..3650130 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sebastiannargor.npc @@ -0,0 +1,40 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Sebastian" +Outfit = (151,95-53-109-115-3) +Home = [32027,32814,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ho, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","liberty","bay",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) +BUSY,"bring","me","to","meriana",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32346,32625,7), EffectOpp(11) +ADDRESS,"bring","me","to","liberty","bay",Premium,CountMoney>=100,! -> Price=100, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) +ADDRESS,"bring","me","to","meriana",Premium,CountMoney>=50,! -> Price=50, "Set the sails %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(32346,32625,7), EffectOpp(11) + +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye, %N.", Idle +"farewell" -> * +"job" -> "I am the captain of this ship." +"captain" -> * +"sail" -> "Where do you want to go? I can sail you to Liberty Bay and Nargor." +"passage" -> * +"nargor" -> "Yes. We are in Nargor." + +"liberty","bay" -> Price=100, "Do you seek a passage to Liberty Bay for %P?", Topic=1 +"meriana" -> Price=50, "Do you seek a passage to Meriana for %P?", Topic=2 + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32316,32702,7), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32346,32625,7), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/app/SabrehavenServer/data/npc/seymour.npc b/app/SabrehavenServer/data/npc/seymour.npc new file mode 100644 index 0000000..78a950a --- /dev/null +++ b/app/SabrehavenServer/data/npc/seymour.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# seymour.npc: Datenbank fuer den Schulleiter Seymour + +Name = "Seymour" +Outfit = (128,115-69-87-116-0) +Home = [32103,32195,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello",! -> "Hello, %N. What do you need?" +ADDRESS,"hi",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "%N, I am already talking to somebody else! Please wait until it is your turn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye! And remember: No running up and down in the academy!" + +"bye" -> "Good bye! And remember: No running up and down in the academy!", Idle +"farewell" -> * +"how","are","you" -> "Well, the king doesn't send troops anymore, the academy is dreadfully low on money, and the end of the world is pretty nigh. Apart from that I am reasonably fine, I suppose." +"sell" -> "I sell the Key to Adventure for 5 gold! If you are interested, tell me that you want to buy the key." +"job" -> "I am the master of this fine academy." +"academy" -> "Our academy has a library, a training center in the cellars and the oracle upstairs." +"library" -> "Go and read our books. Ignorance may mean death, so be careful." +"train" -> "You can try some basic things down there, but don't challenge the monsters in our arena if you are inexperienced." +"center" -> * +"cellar" -> * +"oracle" -> "You will find the oracle upstairs. Talk to the oracle as soon as you have made level 8. Choose a vocation and a new home town, and you will be sent off to the continent." +"vocation" -> * + +"key" -> Type=2969, Data=4600, Amount=1, Price=5, "Do you want to buy the Key to Adventure for %P gold coins?", Topic=1 +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Only nonsense on your mind, eh?" + +"rookgaard" -> "Here on Rookgaard we have some people, a temple, some shops, a farm and an academy." +"name" -> "My name is Seymour, but to you I am 'Sir' Seymour." +"seymour" -> * +"sir" -> "At least you know how to address a man of my importance." +"time" -> "It is %T, so you are late. Hurry!" +"help" -> "I can assist you with my advice." +"hint" -> * +"people" -> "Well, there's me, Cipfried, Willie, Obi, Amber, Dallheim, Al Dee, Norma, and Hyacinth." +"advice" -> "Read the blackboard for some hints and visit the training center in the cellar." +"monster" -> "You can learn about Tibia's monsters in our library." +"dungeon",level<3 -> "There are some dungeons on this isle, but almost all of them are too dangerous for you at the moment." +"dungeon",level>2 -> "There are some dungeons on this isle. You should be strong enough to explore them now, but make sure to take a rope with you." +"sewer" -> "Our sewers are overrun with rats. If you own some equipment you could go down a sewer grate and fight the vermin." +"god" -> "You can learn much about Tibia's gods in our library." +"gamemaster" -> "If you have serious problems with the game or with other people who are harassing you, contact a counsellor or a gamemaster using CTRL+R." +"counsellor" -> * +"king" -> "Hail to King Tibianus! Long live our king! Not that he cares for an old veteran who is stuck on this godforsaken island..." +"obi" -> "A cousin of Thais' smith Sam. He has a shop here where you can buy most stuff an adventurer needs." +"cipfried" -> "A humble monk with healing powers, and a pupil of the great Quentin himself." +"amber" -> "A traveller from the main land. I wonder what brought her here, since no one comes here of his own free will." +"willie" -> "Willie is a fine farmer, although he has short temper." +"hyacinth" -> "A mysterious druid who lives somewhere in the wilderness. He sells precious life fluids." +"dallheim" -> "Oh good Dallheim! What a fighter he is! Without him we would be doomed." +"al","dee" -> "He is a shop owner in the northwestern part of the village." +"quentin" -> "He is responsible for the temple in Thais." +"life","fluid" -> "A rare magic potion that restores health." +"fuck",male -> "For this remark I will wash your mouth with soap, young man!", EffectOpp(8) +"fuck",female -> "For this remark I will wash your mouth with soap, young lady!", EffectOpp(8) +"bug" -> "Nasty little creatures, but once you have a suitable weapon and perhaps a shield they will be no match for you." +"weapon" -> "You need fine weapons to fight the tougher beasts. Unfortunately only the most basic weapons and armor are available here. You will have to fight some monsters to get a better weapon." +"magic" -> "The only magic-user on this isle is old Hyacinth." +"tibia" -> "Oh, how I miss the crowded streets of Thais. I know one day I will get promoted and get a job at the castle... I must get out of here! The faster the better! It is people like you who are driving me mad." +"castle" -> "The castle of Thais is the greatest achievement in Tibian history." + +"mission",level<4 -> "You are pretty inexperienced. I think killing rats is a suitable challenge for you. For each fresh rat I will give you two shiny coins of gold." +"quest",level<4 -> * +"mission",level>3 -> "Well I would like to send our king a little present, but I do not have a suitable box. If you find a nice box, please bring it to me." +"quest",level>3 -> * + +"rat" -> Type=3994, Amount=1, Price=2, "Have you brought a dead rat to me to pick up your reward?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "Have you brought %A dead rats to me to pick up your reward?", Topic=2 +Topic=2,"yes",Count(Type)>=Amount -> "Thank you! Here is your reward.", Delete(Type), CreateMoney +Topic=2,"yes" -> "HEY! You don't have one! Stop playing tricks on fooling me or I will give you some extra work!" +Topic=2,"yes",Amount>1 -> "HEY! You do not have so many!" +Topic=2 -> "Go and find some rats to kill!" + +"box" -> Type=2856, Amount=1, "Do you have a suitable present box for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "THANK YOU! Here is a helmet that will serve you well.", Delete(Type), Create(3374) +Topic=3,"yes" -> "HEY! You don't have one! Stop playing tricks on me or I will give some extra work!" +Topic=3 -> * +} diff --git a/app/SabrehavenServer/data/npc/shalmar.npc b/app/SabrehavenServer/data/npc/shalmar.npc new file mode 100644 index 0000000..dedd899 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shalmar.npc @@ -0,0 +1,218 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shalmar.npc: Datenbank fuer den Magier Shalmar + +Name = "Shalmar" +Outfit = (130,95-8-65-0-0) +Home = [33221,32408,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Shalmar Ibn Djinbar, the caliph's magician and astrologer." +"job" -> "I teach magic spells to the worthy." +"time" -> "It's %T right now." +"caliph" -> "The caliph has the strong soul needed to guide his people." +"kazzan" -> * +"ferumbras" -> "His weakness is evident by the rotting of his soul." +"excalibug" -> "A strong mind and a pure soul has no need for such items." +"thais" -> "It's a city of souls who failed to see the need of ascension." +"tibia" -> "The world is filled with wonderous places and items." +"carlin" -> "I heared it's a city of druids." +"ascension" -> "Talk to Kasmir about that issue. It's not my place to pose as a teacher since I am a student, too." +"news" -> "News are distractions. Nothing of importance happens outside your own soul." +"rumour" -> * +"rumor" -> * +"sorcerer" -> "The way of the magician is not that different from the way to ascension." +"druid" -> * + +"offer" -> "I'm teaching spells to sorcerers and druids. I also used to sell magic goods, but my assistant Asima in the next room does that now for me." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I don't sell this anymore, it sort of kept on confusing me to do that much work. Please talk to my assistant Asima in the next room to purchase magic goods." +"life","fluid" -> * +"mana","fluid" -> * +"blank","rune" -> * +"spellbook" -> * + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers and Druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Daraman's blessings.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=4 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=4 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=4 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=4 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=4 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=4 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=4 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=4 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=4 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=4 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=4 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=4 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=4 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=4 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=4 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=4 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=3,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=3 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=4 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=4 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=4 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=4 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=4 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=4 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=4 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=4 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=4 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=4 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=4 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=4 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=4 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=4 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=4 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=4 + + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"27$" -> "For level 27 I have 'Firebomb'.", Topic=3 +Topic=3,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=3 +Topic=3,"31$" -> "For level 31 I have 'Explosion'.", Topic=3 +Topic=3,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 +Topic=3,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=3 +Topic=3,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=3 +Topic=3,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=3 + +Topic=3 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=3 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "You need more money." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." + + +} diff --git a/app/SabrehavenServer/data/npc/shanar.npc b/app/SabrehavenServer/data/npc/shanar.npc new file mode 100644 index 0000000..34879e5 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shanar.npc @@ -0,0 +1,216 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shanar.npc: Datenbank für den Schmied Shanar (Elfenstadt) + +Name = "Shanar" +Outfit = (144,0-93-105-76-0) +Home = [32657,31655,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I sell weapons, shields, and armor, and teach protective spells." +"name" -> "I am Shanar Ethkal." +"time" -> "I don't care." + +"carlin" -> "Carlin is quite close and we have some trade now and then." +"thais" -> "Thais is a town of humans far away." +"venore" -> "Those human merchants try to sell their low quality weapons and armor here to ruin my trade." +"roderick" -> "He is some human who lives in a stone house in the south of the town." +"olrik" -> "This human is sneaking around in the tow far too often." + +"elves" -> "That's our race, so what?" +"dwarfs" -> "Ugly and dirty." +"humans" -> "Loud and noisy." +"troll" -> "I own only a few." +"cenath" -> "Idiots." +"kuridai" -> "That's my caste." +"deraisim" -> "Squirrels." +"abdaisim" -> "They don't live here." +"teshial" -> "Don't know much about them" +"ferumbras" -> "A danger to all." +"crunor" -> "I don't care about gods." +"excalibug" -> "Perhaps more than a myth." +"news" -> "Nothing I want to talk about." +"magic" -> "I teach some spells of protection." +"druid" -> "Druids are great healers." +"sorcerer" -> "They understand so few..." + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell a coat for %P gold?", Topic=2 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell a jacket for %P gold?", Topic=2 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell a knight's armor for %P gold?", Topic=2 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500,"Do you want to sell a golden armor for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "Do you want to sell a devil's helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "Do you want to sell a warrior's helmet for %P gold?", Topic=2 +"sell","leather","legs" -> Type=3559, Amount=1, Price=1, "Do you want to sell leather legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "Do you want to sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "Do you want to sell knight's legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell %A coats for %P gold?", Topic=2 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell %A jackets for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell %A knight's armors for %P gold?", Topic=2 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1,"Do you want to sell %A golden armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "Do you want to sell %A devil's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "Do you want to sell %A warrior's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=1*%1, "Do you want to sell %A leather legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "Do you want to sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "Do you want to sell %A knight's legs for %P gold?", Topic=2 + +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=31, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","guardians","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell a guardian's shield for %P gold?", Topic=2 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell a dragon shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=31*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell %A guardian's shields for %P gold?", Topic=2 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=2 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "Do you want to sell a longsword for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000,"Do you want to sell a fire sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "Do you want to sell %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1,"Do you want to sell %A fire swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"staff" -> Type=3289, Amount=1, Price=40, "Do you want to buy it for %P gold?", Topic=1 +"longsword" -> Type=3285, Amount=1, Price=160, "Do you want to buy it for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=40*%1, "Do you want to buy them for %P gold?", Topic=1 +%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=160*%1, "Do you want to buy them for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2 -> "Ok, then not." + +"spell" -> "I teach 'Poison Field', 'Fire Field', 'Energy Field', 'Poison Wall', 'Fire wall', and 'Energy Wall'." + +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +"poison","field" -> "I'm sorry, but this spell is only for druids." +"fire","field" -> * +"energy","field" -> * +"poison","wall" -> * +"fire","wall" -> * +"energy","wall" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to improve to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you may cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." + +"weapon" -> "I have spears, swords, rapiers, daggers, longswords, machetes, staffs, and sabres. Interested?" +"helmet" -> "I am selling leather helmets and chain helmets. Anything you'd like to buy?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * +"spellbook" -> "I have none here." +} diff --git a/app/SabrehavenServer/data/npc/shauna.npc b/app/SabrehavenServer/data/npc/shauna.npc new file mode 100644 index 0000000..52836d0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shauna.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shauna.npc: Datenbank für den Sheriff von Carlin Shauna + +Name = "Shauna" +Outfit = (139,78-95-38-58-0) +Home = [32384,31778,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$",! -> "Howdy!" +ADDRESS,"salutations$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"news" -> "No news are good news." +"queen" -> "HAIL TO QUEEN ELOISE!" +"leader" -> * +"job" -> "What do you think? I am the sheriff of Carlin." +"how","are","you"-> "Just fine." + +"sell" -> "Would you like to buy the general key to the town?", Topic=1 +Topic=1,"yes" -> "Yeah, I bet you'd like to do that! HO, HO, HO!" + +"army" -> "If they make trouble, I'll put them behind bars like all others." +"guard" -> * +"general" -> "The Bonecrusher family is ideally suited for military jobs." +"bonecrusher" -> * +"enemies" -> "If you have a crime to report and clues, then do it, but dont waste my time." +"enemy" -> * +"criminal" -> * +"murderer" -> * +"castle" -> "The castle is one of the safest places in Carlin." +"subject" -> "Our people are fine and peaceful." +"tbi$" -> "I bet they spy on us... not my business, however." +"todd$" -> "I scared this bigmouth so much that he left the town by night. HO, HO, HO!" +"city" -> "The city is is a peacful place, and it's up to me to keep it this way." +"hain$" -> "He is the guy responsible to keep the sewers working. Someone has to do such kind of jobs. I can't handle all the garbage of the city myself." +"rowenna$" -> "Rowenna is one of our local smiths. When you look for weapons, look for Rowenna." +"weapon" -> * +"Cornelia" -> "Cornelia is one of our local smiths. When you look for armor, look for Rowenna." +"armor" -> * +"legola" -> "She has the sharpest eye in the region, I'd say." +"padreia" -> "Her peacefulness is sometimes near stupidity." +"god" -> "I worship Banor of course." +"banor" -> "For me, he's the god of justice." +"zathroth" -> "His cult is forbidden in our town." +"brog" -> "Wouldn't wonder if some males worship him secretly. HO, HO, HO!" +"monster" -> "I deal more with the human mosters, you know? HO, HO, HO!" +"excalibug" -> "Would certainly make a good butterknife. HO, HO, HO!" +"rebellion" -> "The only thing that rebels here now and then is the stomach of a male after trying to make illegal alcohol. HO, HO, HO!" +"alcohol" -> "For obvious reasons it's forbidden in our city." + +"waterpipe" -> "Oh, there's a waterpipe in one of my cells? ...", + "I guess my last prisoner forgot it there." +"pipe" -> * +"prisoner" -> "My last prisoner? Hmm. ...", + "I think he was some guy from Darama. Can't remember his name. ...", + "He was here just for one night, because he got drunk and annoyed our citizens. ...", + "Obviously he wasn't pleased with this place, because he headed for Thais the next day. ...", + "Something tells me that he won't stay out of trouble for too long." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/app/SabrehavenServer/data/npc/sherry.npc b/app/SabrehavenServer/data/npc/sherry.npc new file mode 100644 index 0000000..2467e96 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sherry.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sherry.npc: Datenbank für die Bäuerin Sherry McRonald + +Name = "Sherry McRonald" +Outfit = (136,78-94-38-58-0) +Home = [32390,32241,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Welcome to our humble farm." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "What a strange person." + +"bye" -> "Grace our home with another visit soon.", Idle +"farewell" -> * +"job" -> "I and my husband run this farm." +"husband" -> "My husband Donald is busy on the fields almost all night and day." +"donald" -> * +"farm" -> "It is a hard work, but the city needs us." +"name" -> "I am Sherry McRonald." +"time" -> "Sorry, I don't have a watch." +"weather" -> "The weather is the best friend and the worst enemy of a farmer." +"field" -> "The druids helped us by placing a blessing on our fields." +"city" -> "The city needs our crops." +"crops" -> "It's hard to harvest it, carry it to the mill in the north and make flour. If you can bake some bread I will buy it for 2 gold." +"mill" -> "The miller is a lazy fellow and afraid of his own mill, because he thinks it is spooked." +"spooked" -> "I don't know for sure. The miller claims that his mill is threatened by some monsters sometimes." +"king" -> "King Tibianus granted us this farm to earn a living." +"tibianus" -> * +"frodo" -> "He is a friend of my husband." +"oswald" -> "This lazy fellow has nothing better to do than to spread rumours." +"bloodblade" -> "He is an impressive warrior as far as I can tell." +"muriel" -> "We a mere peasants and don't know much about the guild leaders." +"elane" -> * +"gregor" -> * +"marvik" -> * +"gorn" -> "He doesn't talk much to us." +"sam" -> "He is too busy to care much about farmers like us." +"quentin" -> "What a nice person he is." +"lynda" -> "She is sooo charming. I can't believe she is not married yet! Have you met her?", Topic=2 +"spider" -> "Spiders infested the sewers beneath our farm. We need some help to exterminate them. My husband pays a reward for killed spiders." +"monster" -> * +"help" -> * + +"buy" -> "I can offer you cheese, cherries, and melons." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have cheese, cherries, pumpkins and melons." + +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + + +"sell","bread" -> "I will pay 2 gold for every bread, is that ok?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",male -> "She really should find a husband with ease! You should ask her for a date." +Topic=2,"yes",female -> "She really should find a husband with ease! If you know a bachelor, introduce him to her." +Topic=2 -> "Oh, if you say so." + +Topic=3,"yes",Count(3600)>0 -> Amount=Count(3600), Price=Amount*2, "Here you are ... %P gold.", Delete(3600), CreateMoney +Topic=3,"yes" -> "Sorry, you don't have any bread." +Topic=3 -> "Maybe another time." +} diff --git a/app/SabrehavenServer/data/npc/shiantis.npc b/app/SabrehavenServer/data/npc/shiantis.npc new file mode 100644 index 0000000..c6c75a7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shiantis.npc @@ -0,0 +1,103 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shiantis.npc: Datenbank für die Händlerin Shiantis in Venore + +Name = "Shiantis" +Outfit = (136,0-36-13-76-0) +Home = [32890,32086,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N. What is your need today?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Excuse me, %N, I am already talking to another customer. Wait just a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling several kinds of equipment and decoration. What is your need?" +"name" -> "I am Shiantis." +"time" -> "I won't tell you for free, but maybe you want to buy a watch?" +"king" -> "I would love to see the royal taxes lowered." +"tibianus" -> * +"army" -> "I think it's needed for protection. We pay enough taxes for this." +"ferumbras" -> "Ferumbras dolls were not the saleshit we expected. Sold all stock to a strange guy who bought a bunch of needles, too." +"excalibug" -> "Sorry, we run out of stock. I expect another load of wodden excalibug simulacra to arrive next week." +"news" -> "I heard the merchants will petition the king to lower the taxes." +"tax" -> * + +"offer" -> "As you can see, our inventory is large, just have a look." +"goods" -> "At this booth we sell containers, decoration, illumination, paperware, footballs, and watches." +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * +"containers" -> "In that department we offer bags, backpacks, and present boxes." +"illumination" -> "In that department we offer torches, candlesticks, candelabra, oil and coal basins." +"paperware" -> "In that department we offer scrolls, documents, parchments, and books." +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" +"magic" -> "You will have to visit that spooky magic market for that stuff." +"fluid" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you wanna buy oil for %P gold?", Topic=2 +"coal","basin" -> Type=2806, Amount=1, Price=25, "Do you want to buy a coal basin for %P gold?", Topic=3 +"bag" -> Type=2859, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2867, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"greeting","card",ClientVersion>=790 -> Type=6386, Amount=1, Price=30, "Do you want to buy a greeting card for %P gold?", Topic=1 +"valentine","card",ClientVersion>=790 -> Type=6538, Amount=1, Price=30, "Do you want to buy a valentine's card for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 +%1,1<%1,"coal","basin" -> Type=3510, Amount=%1, Price=25*%1, "Do you want to buy %A coal basins for %P gold?", Topic=3 +%1,1<%1,"bag" -> Type=2859, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2867, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"greeting","card",ClientVersion>=790 -> Type=6386, Amount=%1, Price=30*%1, "Do you want to buy %A greeting cards for %P gold?", Topic=1 +%1,1<%1,"valentine","card",ClientVersion>=790 -> Type=6538, Amount=%1, Price=30*%1, "Do you want to buy %A valentine's cards for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "That's not funny!" +Topic=2 -> "Then not." + +Topic=3,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=3,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "Hmm, but I'm sure, it would fit nicely into your house." + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." + +@"gen-t-furniture-decoration-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/shiriel.npc b/app/SabrehavenServer/data/npc/shiriel.npc new file mode 100644 index 0000000..c263a39 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shiriel.npc @@ -0,0 +1,93 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shiriel.npc: Datenbank für die Magiehändlerin Shiriel (Elfenstadt) + +Name = "Shiriel" +Outfit = (144,2-103-0-95-0) +Home = [32670,31657,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I sell mystic runes, spellbooks, wands, rods and fluids of life or mana." +"name" -> "I am Shiriel Sharaziel." +"time" -> "Time was mastered by my people long ago." + +"elves" -> "Our noble race has knowledge of secrets beyond your comprehension." +"dwarfs" -> "Not worth to bother about." +"humans" -> "Cursed with a short livespan and not worth to be remembered." +"troll" -> "We should eradicate them all." +"cenath" -> "We are the teachers to the other castes." +"kuridai" -> "Their knowledge is limited." +"deraisim" -> "They lack the patience that suits a race with our lifespan." +"abdaisim" -> "I think they are lost forever." +"teshial" -> "They were not prepared for what they encountered in their quest for knowledge. WE will be prepared." +"ferumbras" -> "A humanbreed abnomination." +"crunor" -> "I have no time for superstition." +"excalibug" -> "I would love to analyse it one day." +"news" -> "News are secrets and you are not worthy of my secrets." +"magic" -> "I could teach you some spells ... but I won't." + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial, ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/shirith.npc b/app/SabrehavenServer/data/npc/shirith.npc new file mode 100644 index 0000000..69a6182 --- /dev/null +++ b/app/SabrehavenServer/data/npc/shirith.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shirith.npc: Datenbank für den Minenleiter Shirith (Elfenstadt) + +Name = "Shirith" +Outfit = (144,59-97-58-76-0) +Home = [32646,31655,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the overseer of the mines." +"name" -> "I am called Shirith Blooddancer." +"time" -> "It is %T." + +"carlin" -> "I think those humans are trespassing elven teritory far too often." +"thais" -> "Thais is far away as all humans should be." +"venore" -> "If it comes to trade, I can respect those merchants. As long as they leave as soon as they finished buisness, that is." +"roderick" -> "We don't need him or any other ambassador here." +"olrik" -> "As a post officer he has some use ... as a troll has some use for mining." + +"elves" -> "We are a superior race, indeed." +"dwarfs" -> "They could be of ... some use." +"human" -> "Humans are more annoying than our trolls." +"troll" -> "We give these useless creatures a reason to live by serving us." +"cenath" -> "They think they are better then us." +"kuridai" -> "We keep this society running. Without our tools and work our case would be a lost one." +"deraisim" -> "They could do more for us if they would try more hard." +"abdaisim" -> "Let them go, we don't need them." +"teshial" -> "Who needs dreamers in these days?" +"ferumbras" -> "He should be destroyed." +"mines" -> "We hardly get the ore we need. The worthless trolls are lazy workers. I keep them locked up the whole time." +"locked" -> "I keep the keys to the mines." +"excalibug" -> "Nonsense." +"news" -> "Trolls are boring, I have no news to tell." + +"key" -> Type=2969, Data=3033, Price=50, "I would sell you a key for 50 gold, ok?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You do not have enough gold." +Topic=1 -> "Ok, then not." +} diff --git a/app/SabrehavenServer/data/npc/sigurd.npc b/app/SabrehavenServer/data/npc/sigurd.npc new file mode 100644 index 0000000..c613ce3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sigurd.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sigurd.npc: Datenbank für den Händler Sigurd + +Name = "Sigurd" +Outfit = (69,0-0-0-0-0) +Home = [32626,31923,5] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the magic store, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You next %N, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * + +"name" -> "I am Sigurd Fireworker, brother to Etzel Fireworker, son of fire, of the Molten Rocks." +"job" -> "I help my brother handling his little magic store so he can focus on studying spells." +"time" -> "It's %T right now." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=5 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=5 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=4 +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=5 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=5 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=4 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy %A spellbooks for %P gold?", Topic=4 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=6 +"vial" -> * +"flask" -> * + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=6,"yes" -> "You don't have any empty vials." +Topic=6 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +} diff --git a/app/SabrehavenServer/data/npc/simon.npc b/app/SabrehavenServer/data/npc/simon.npc new file mode 100644 index 0000000..aae9605 --- /dev/null +++ b/app/SabrehavenServer/data/npc/simon.npc @@ -0,0 +1,132 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# simon.npc: Datenbank für den Bettler Simon auf der Insel Fibula + +Name = "Simon the Beggar" +Outfit = (153,116-123-32-40-3) +Home = [32186,32468,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. I am a poor man. Please help me." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Have a nice day." + +"bye" -> "Have a nice day.", Idle +"farewell" -> * +"job" -> "I have no job. I am a beggar." +"beggar" -> "I have no gold and no job, so I am a beggar." +"gold" -> "I need gold. I love gold. I need help." +"name" -> "My name is Simon. I am a very poor man." +"simon" -> "I am Simon. The poorest human all over the continent." + +"help" -> Price=100, "I need gold. Can you spend me %P gold pieces?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> DeleteMoney, Price=500, "Thank you very much. Can you spend me %P more gold pieces? I will give you a nice hint.", Topic=2 +Topic=1,"yes" -> "You've not enough money for me." +Topic=1 -> "Hmm, maybe next time." +Topic=2,"yes",CountMoney>=Price -> DeleteMoney, Price=200, "That's great! I have stolen something from Dermot. You can buy it for %P gold. Do you want to buy it?", Topic=3 +Topic=2,"yes" -> "Sorry, that's not enough." +Topic=2 -> "It was your decision." +Topic=3,"yes",CountMoney>=Price -> DeleteMoney, "Now you own the hot key.", Data=3940, Create(2968) +Topic=3,"yes" -> "Pah! I said 200 gold. You don't have so much." +Topic=3 -> "Ok. No problem. I'll find another buyer." + +"dermot" -> "The magistrate of the village. I heard he is selling something for the Fibula Dungeon." +"village" -> "To the north is the village Fibula. A very small village." +"key" -> "Key? There are a lot of keys. Please change the topic." +"dungeon" -> "I heard a lot about the Fibula Dungeon. But I never was there." +"fibula" -> "I hate Fibula. Too many wolves are here." +"timur" -> "I hate Timur. He is too expensive. But sometimes I find maces and hatchets. Timur is buying these items." +"wolf" -> "Please kill them ... ALL." +"flute" -> "Har, har. The stupid Dermot lost his flute. I know that some minotaurs have it in their treasure room." +"minotaurs"-> "Very rich monsters. But they are too strong for me. However, there are even stronger monsters." +"minos" -> * +"treasure" -> "I know there are two rooms. And I know you can pass only the first door. The second door can't be opened." +"giant","spider" -> "I know that terrible monster. It killed the fishers on the isle to the north." +"monster" -> "The strongest monster I know is the giant spider." +"jetty" -> "I hate this jetty. I have never seen a ship here." +"ship" -> "There is a large sea-monster outside. I think there is no gritty captain to sail in this quarter." +"tibia" -> "Hehe, do you have a shovel? I can sell you a shovel if you want to return to Tibia." + +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=4 + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + +"addon",QuestValue(17563)<6 -> "My beard should not bother you my friend." +"outfit",QuestValue(17563)<6 -> * +"beard",QuestValue(17563)<6 -> * + +"addon",QuestValue(17563)=6 -> "Haha, that beard is - well, not fake, but there's a trick behind it. I noticed people tend to be more generous towards a poor gramps. Want to know my trick?", Topic=5 +"outfit",QuestValue(17563)=6 -> * +"beard",QuestValue(17563)=6 -> * +Topic=5,"yes",male -> "I can mix a secret potion which will increase your facial hair growth enormeously. I call it 'Instabeard'. However, it requires certain ingredients. ...", + "For the small fee of 20000 gold pieces I will help you mix this potion. Just bring me 100 pieces of ape fur, which are necessary to create this potion. ...", + "Do we have a deal?", Topic=6 +Topic=5,"yes",female -> "I can mix a secret potion which increases facial hair growth enormeously. I call it 'Instabeard'. However, I fear it works only for men. ...", + "Even if it worked on girls, I'd rather not be responsible for you ruining your pretty face. I have an idea though. If you help me brew one of these potions, I sell something nice to you. ...", + "I still have a pretty gypsy dress and a pearl necklace somewhere, which you could wear instead of this ragged skirt. For the small fee of 20000 gold pieces, it'd be yours. ...", + "You only have to bring me 100 pieces of ape fur, so I can brew the potion. Do we have a deal?", Topic=6 +Topic=5 -> "Maybe another time." +Topic=6,"yes" -> "Great! Come back to me once you have the 100 pieces of ape fur and I'll do my part of the deal.", SetQuestValue(17563,7) +Topic=6 -> "Maybe another time." + +"ape","fur",QuestValue(17563)=7 -> Type=5883, Amount=100, Price=20000, "Have you brought me the 100 pieces of ape fur and 20000 gold pieces?", Topic=7 +"20000",QuestValue(17563)=7 -> * +"mission",QuestValue(17563)=7 -> * +"task",QuestValue(17563)=7 -> * +"outfit",QuestValue(17563)=7 -> * +"addon",QuestValue(17563)=7 -> * +Topic=7,"yes",Count(Type)>=Amount,CountMoney>=Price,male -> "Ahh! Very good. I will start mixing the potion immediately. Come back later. Bye bye.", Delete(Type), DeleteMoney, SetQuestValue(17563,8), Idle +Topic=7,"yes",Count(Type)>=Amount,CountMoney>=Price,female -> "Ahh! Very good. I will start mixing the potion immediately and sell it to some poor foo- eh, man. Goodbye.", Delete(Type), DeleteMoney, SetQuestValue(17563,8), Idle +Topic=7,"yes" -> "You do not have all the required items." +Topic=7 -> "Maybe another time." + +"addon",QuestValue(17563)=8,male -> "Hmm, I'm not done yet with your potion. But here, let me sprinkle a few drops of my own potion on your face... there you go. Now you just have to wait.", SetQuestValue(17563,9), SetExpiringQuestValue(17565, 432000000), Idle +"outfit",QuestValue(17563)=8,male -> * +"beard",QuestValue(17563)=8,male -> * + +"addon",ExpiringQuestValue(17565)>0 -> "Hmm, hmmm, I think you already have a little more hair than the last time I saw you. Just be patient and don't shave. I'm gonna check the progress for you." +"outfit",ExpiringQuestValue(17565)>0 -> * +"beard",ExpiringQuestValue(17565)>0 -> * + +"outfit",ExpiringQuestValue(17565)<0,QuestValue(17563)=9 -> "Aha! I can see it! Now that you've waited patiently without shaving, your beard is perfect! All thanks to my, err, potion. Yes. Goodbye!", AddOutfitAddon(157,2), AddOutfitAddon(153,2), SetQuestValue(17563,10), EffectOpp(13), Idle + +"gypsy","dress",QuestValue(17563)=8,female -> "Oh, I'm sorry... I almost forgot! Okay, okay... here is your promised dress. I'm sure it will look so much better on you than on me- I mean, my, err, sister.", AddOutfitAddon(157,2), AddOutfitAddon(153,2), SetQuestValue(17563,10), EffectOpp(13), Idle +"outfit",QuestValue(17563)=8,female -> * +"addon",QuestValue(17563)=8,female -> * + +"staff",QuestValue(17563)<10 -> "No, no. It's mine, get your own one!" + +"outfit",QuestValue(17563)=10 -> "No, no. Our deal is finished, no complaining now, I don't have time all day. And no, you can't have my staff.", SetQuestValue(17563,11) +"addon",QuestValue(17563)=10 -> * + +"staff",QuestValue(17563)=11 -> "I said, no! Or well - I have a suggestion to make. Will you listen?", Topic=8 +"addon",QuestValue(17563)=11 -> * +"outfit",QuestValue(17563)=11 -> * +"mission",QuestValue(17563)=11 -> * +"task",QuestValue(17563)=11 -> * +Topic=8,"yes" -> "When I was wandering around in Tibia, I lost my favourite staff somewhere in the northern ruins in Edron. ...", + "Uh, don't ask me what I was doing there... sort of a pilgrimage. Well anyway, if you could bring that staff back to me, I promise I'll give you my current one. ...", + "What do you say?", Topic=9 +Topic=8 -> "Maybe another time." +Topic=9,"yes" -> "Good! Come back to me once you have retrieved my staff. Good luck.", SetQuestValue(17563,12), Idle +Topic=9 -> "Maybe another time." + +"staff",QuestValue(17563)=12 -> Type=6107, Amount=1, "Did you bring my favourite staff??", Topic=10 +"mission",QuestValue(17563)=12 -> * +"task",QuestValue(17563)=12 -> * +"outfit",QuestValue(17563)=12 -> * +"addon",QuestValue(17563)=12 -> * +Topic=10,"yes",Count(Type)>=Amount -> "Yes!! That's it! I'm so glad! Here, you can have my other one. Thanks!", Delete(Type), AddOutfitAddon(157,1), AddOutfitAddon(153,1), SetQuestValue(17563,13), EffectOpp(13), Idle +Topic=10,"yes" -> "You don't have it." +Topic=10 -> "Maybe another time." + +"mission",QuestValue(17563)=13 -> "Nah, nothing new." +"task",QuestValue(17563)=13 -> * + +} diff --git a/app/SabrehavenServer/data/npc/skjaar.npc b/app/SabrehavenServer/data/npc/skjaar.npc new file mode 100644 index 0000000..f688a09 --- /dev/null +++ b/app/SabrehavenServer/data/npc/skjaar.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# skjaar.npc: Datenbank für Skjaar, Wächter der Krypta im Berg und Meistermagier + +Name = "Skjaar" +Outfit = (9,0-0-0-0-0) +Home = [32450,32038,8] +Radius = 2 + +Behaviour = { +BUSY,"hello$",Level<15,! -> "I don't talk to little children!!", Idle +BUSY,"hi$",Level<15,! -> * +ADDRESS,"hello$",Druid,! -> "Hail, friend of nature! How may I help you?" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",Knight,! -> "Another creature who believes thinks physical strength is more important than wisdom! Why are you disturbing me?" +ADDRESS,"hi$",Knight,! -> * +ADDRESS,"hello$",Sorcerer,! -> "It's good to see somebody who has chosen the path of wisdom. What do you want?" +ADDRESS,"hi$",Sorcerer,! -> * +ADDRESS,"hello$",Paladin,! -> "Neither strong enough to be a knight nor wise enough to be a real mage. You like it easy, don't you? Why are you disturbing me?" +ADDRESS,"hi$",Paladin,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence, unworthy creature!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Run away, unworthy %N!" + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "Once I was the master of all mages, but now I only protect this crypt." +"name" -> "I am Skjaar the Mage, master of all spells." +"door" -> "This door seals a crypt." +"crypt" -> "Here lies my master. Only his closest followers may enter." +"help" -> "I'm not here to help anybody. I only protect my master's crypt." +"mountain" -> "Hundreds of years my master's castle stood on the top of this mountain. Now there is a volcano." +"volcano" -> "I can still feel the magical energy in the volcano." +"castle" -> "The castle was destroyed when my master tried to summon a nameless creature. All that is left is this volcano." +"time" -> "To those who have lived for a thousand years time holds no more terror." +"master" -> "If you are one of his followers, you need not ask about him, for you will know. And if you aren't, you are not worthy anyway!" +"key" -> "I will give the key to the crypt only to the closest followers of my master. Would you like me to test you?", Topic=1 + +"idiot" -> "Take this for your words!", HP=1, EffectOpp(14), Idle +"fuck" -> "Take this for your words!", HP=1, EffectOpp(14), Idle +"asshole" -> "Take this for your words!", HP=1, EffectOpp(14), Idle + +Topic=1,"yes" -> Price=1000, "Before we start I must ask you for a small donation of 1000 gold coins. Are you willing to pay 1000 gold coins for the test?", Topic=2 +Topic=1,"no" -> "Then leave, unworthy worm!", Idle +Topic=1 -> "You're not worthy if you cannot make up your mind. Leave!", Idle + +Topic=2,"yes",CountMoney>=Price -> "All right then. Here comes the first question. What was the name of Dago's favourite pet?", DeleteMoney, Topic=3 +Topic=2,"yes",CountMoney "You don't even have the money to make a donation? Then go!", Idle +Topic=2,"no" -> "You're not worthy then. Now leave!", Idle +Topic=2 -> "You're not worthy if you cannot make up your mind. Leave!", Idle + +Topic=3,"redips",! -> "Perhaps you knew him after all. Tell me - how many fingers did he have when he died?", Topic=4 +Topic=3,! -> "You are wrong. Get lost!", Idle + +Topic=4,"7",! -> "Also true. But can you also tell me the colour of the deamons in which master specialized?", Topic=5 +Topic=4,"seven",! -> "Also true. But can you also tell me the colour of the deamons in which my master specialized?", Topic=5 +Topic=4,! -> "You are wrong. Get lost!", Idle + +Topic=5,"black",! -> "It seems you are worthy after all. Do you want the key to the crypt?", Topic=6 +Topic=5,! -> "You are wrong. Get lost!", Idle + +Topic=6,"yes" -> Type=2970, Data=3142, Amount=1, "Here you are.", Create(Type) +Topic=6 -> "It is always a wise decision to leave the dead alone." +} diff --git a/app/SabrehavenServer/data/npc/smiley.npc b/app/SabrehavenServer/data/npc/smiley.npc new file mode 100644 index 0000000..bcdea07 --- /dev/null +++ b/app/SabrehavenServer/data/npc/smiley.npc @@ -0,0 +1,129 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# smiley.npc: Datenbank für den Magiehändler Smiley + +Name = "Smiley" +Outfit = (37,0-0-0-0-0) +Home = [32979,32087,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "... Greeeeeetiiiingssss..." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "... Wait... %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "... Good... Bye" + +"bye" -> "... Good... Bye", Idle +"farewell" -> * +"job" -> "... Selling Spells" +"name" -> "... Smiley" +"time" -> "... Time?... Not important... anymore." +"king" -> "..." +"tibianus" -> * +"vladruc" -> "... Maaaaassssterrrrr" +"urghain" -> * +"ferumbras" -> "... un...important" +"market" -> "... You buy?" +"excalibug" -> "... only sell spells..." +"news" -> "... more spells..." + +"sorcerer" -> "... Ask Chatterbone?" +"druid" -> "... You... buy spells?" +"power" -> * +"spellbook" -> "... You buy book... store spells... other counter..." +"rune" -> "... Runes... mighty stones... other counter..." +Druid,"spell" -> "... Spells... rune spells... instant spells... what you want? ... Or for which level?", Topic=2 +"spell" -> "... Only druids..." + +druid,"rod",QuestValue(333)<1 -> "Oooh... present from meee... take it... goooood start for youuuung druuuids...",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "... Attack rune spells ... healing rune spells ... support rune spells ... summon rune spells. Which...?" +Topic=2,"instant","spell" -> "... Healing spells ... supply spells ... support spells ... summon spells. Which...?" +Topic=2,"level" -> "Which level...?", Topic=2 +Topic=2,"bye" -> "... Good... Bye", Idle + +Druid,"level" -> "... Spell... which level...?", Topic=2 +Druid,"rune","spell" -> "... Attack rune spells ... healing rune spells ... support rune spells ... summon rune spells. Which...?" +Druid,"instant","spell" -> "... Healing spells ... supply spells ... support spells ... summon spells. Which...?" + +Druid,"attack","rune","spell" -> "... Missile rune spells ... explosive rune spells ... field rune spells ... wall rune spells ... bomb rune spells." +Druid,"healing","rune","spell" -> "In this category ... 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category ... 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category ... 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category ... 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category ... 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category ... 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category ... 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category ... 'Firebomb'." + +Druid,"healing","spell" -> "In this category ... 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category ... 'Food'." +Druid,"support","spell" -> "In this category ... 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category ... 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "... You want 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "... You want 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "... You want 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "... You want 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "... You want 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "... You want 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "... You want 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "... You want 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "... You want 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "... You want 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "... You want 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "... You want 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "... You want 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "... You want 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "... You want 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "... You want 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "... You want 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "... You want 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "... You want 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "... You want 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "... You want 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "... You want 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "... You want 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "... You want 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "... You want 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "... You want 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "... You want 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "... You want 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "... You want 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "... You want 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "... For level 8 ... 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "... For level 9 ... 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "... For level 10 ... 'Antidote'.", Topic=2 +Topic=2,"11$" -> "... For level 11 ... 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "... For level 13 ... 'Great Light'.", Topic=2 +Topic=2,"14$" -> "... For level 14 ... 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "... For level 15 ... 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "... For level 16 ... 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "... For level 17 ... 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "... For level 18 ... 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "... For level 20 ... 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "... For level 23 ... 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "... For level 24 ... 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "... For level 25 ... 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "... For level 27 ... 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "... For level 29 ... 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "... For level 31 ... 'Explosion'.", Topic=2 +Topic=2,"33$" -> "... For level 33 ... 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "... For level 35 ... 'Invisible'.", Topic=2 +Topic=2,"41$" -> "... For level 41 ... 'Energy Wall'.", Topic=2 + +Topic=2 -> "... Only spells for level 8 to 11 ... 13 to 18 ... 20 ... 23 to 25 ... 27 ... 29 ... 31 ... 33 ... 35 and 41...", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "... You already know..." +Topic=3,"yes",Level Amount=SpellLevel(String), "... not level %A..." +Topic=3,"yes",CountMoney "... More money." +Topic=3,"yes" -> "... Here...", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "... Then not." + +} diff --git a/app/SabrehavenServer/data/npc/snakeeye.npc b/app/SabrehavenServer/data/npc/snakeeye.npc new file mode 100644 index 0000000..36b266e --- /dev/null +++ b/app/SabrehavenServer/data/npc/snakeeye.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# snakeeye.npc: Datenbank für den Wirt im Kriminellencamp + +Name = "Snake Eye" +Outfit = (73,0-0-0-0-0) +Home = [32657,32190,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi %N. Come in and have a drink.", Data=3303 +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hang on a second, %N. I'm talking!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,female,! -> "Get lost, stinky dragon." +VANISH,! -> "Bye" + +"bye",female -> "Get lost, stinky dragon.", Idle +"bye",male -> "Bye.", Idle +"job" -> "Well, I'm the boss of this tavern." +"name" -> "I'm Snake Eye." +"eye" -> "Well, I had a dispute with a snake once. And the snake won. Bit my left eye out. Therefore, Snake Eye." +"snake" -> * +"boss" -> "Yeah. I'm the boss. So don't bother me." +"tavern" -> "It's a great tavern. No closing time. No problems with kings or other rulers. Best place in Tibia." +"tibia" -> "There's already too much order in Tibia. We don't need kings or whatever." +"king" -> "We don't need one." +"ruler" -> * +"tibianus" -> "We don't need him." +"time" -> "Go and get a watch." + +"god" -> "The Gods of Tibia! What a crap! It's all superstition!" +"crap" -> "Crap. Crap! CRAP! It's all CRAP!" +"superstition" -> "Believe me! There are no gods." +"durin" -> "He's the worst. The so called god of the dwarfs. I don't believe it. It's all crap." +"steve" -> "Never heard of him." +"guido" -> * +"stephan" -> * +"cip" -> "Cip sux!" + +"thais" -> "In the beginning, it was a nice encampment. Now it's an overcrowded, polluted city. I hate it!" +"carlin" -> "I've never been there. Don't know anything about it." +"kazordoon" -> "Kazordoon is alright. Except the dwarfs. I don't like them. But the mountains are a good place. Been there once." +"ab'dendriel" -> "I've never been there. I don't like the elves anyway." +"edron" -> "That's a place for wealthy toffs!" + +"wild","warrior" -> "There are a lot of wild warriors around. They built this camp." +"camp" -> "Well, the real wild warriors don't live here. They hide in the woods." +"hide" -> "Well. I know of a small camp to the south." +"south" -> "It's abandoned. But I bet that something is hidden there!" +"hidden" -> "Go and find out yourself. You can tell me if you find something." +"copper","key",Count(2970)>0 -> "Hmmm. A copper key. You should ask H.L. about it." +"key", Count(2970)>0 -> * +"h.l." -> "He is a wild warrior. Nobody knows his real name. We just call im H.L. You can find him in the small armory shop." +"hl" -> * +"wood" -> "It's the best place to live. By the way, there's an old wild warrior building to the southwest. It might be interesting for you." +"building" -> "Go and ask H.L. about it." + +"buy" -> "Do you want to eat or drink?" +"eat" -> "Ok, I have fish, meat, and bread. What do you want?" +"drink" -> "I can offer you beer, wine, and water. Water is for free." + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy fish for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=6, "Do you want to buy meat for %P gold?", Topic=1 +"bread" -> Type=3602, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=5, "A beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=6, "Wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=0, "Water is for free. Do you want some?", Topic=1 + +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fish for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=6*%1, "Do you want to buy %A pieces of meat for %P gold?", Topic=1 +%1,1<%1,"bread" -> Type=3602, Amount=%1, Price=4*%1, "Do you want to buy a %A breads for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=5*%1, "%A beers for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=6*%1, "%A Wines for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "What? You don't have the money. You crook. Get lost.", Idle +Topic=1 -> "OK, then not." +} diff --git a/app/SabrehavenServer/data/npc/soullost.npc b/app/SabrehavenServer/data/npc/soullost.npc new file mode 100644 index 0000000..f6df872 --- /dev/null +++ b/app/SabrehavenServer/data/npc/soullost.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Lost Soul" +Outfit = (48,0-0-0-0-0) +Home = [32209,31924,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/soultainted.npc b/app/SabrehavenServer/data/npc/soultainted.npc new file mode 100644 index 0000000..4b6dccd --- /dev/null +++ b/app/SabrehavenServer/data/npc/soultainted.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Tainted Soul" +Outfit = (48,0-0-0-0-0) +Home = [32216,31927,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/soultortured.npc b/app/SabrehavenServer/data/npc/soultortured.npc new file mode 100644 index 0000000..383940b --- /dev/null +++ b/app/SabrehavenServer/data/npc/soultortured.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Tortured Soul" +Outfit = (48,0-0-0-0-0) +Home = [32207,31928,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/spooky.npc b/app/SabrehavenServer/data/npc/spooky.npc new file mode 100644 index 0000000..e91871a --- /dev/null +++ b/app/SabrehavenServer/data/npc/spooky.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sooky.npc: Datenbank für die Gespensterfrau + +Name = "A Ghostly Woman" +Outfit = (136,0-0-0-0-0) +Home = [32191,31811,5] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/app/SabrehavenServer/data/npc/stranger.npc b/app/SabrehavenServer/data/npc/stranger.npc new file mode 100644 index 0000000..9c6afee --- /dev/null +++ b/app/SabrehavenServer/data/npc/stranger.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# stranger.npc: Datenbank für den säumigen schuldner david brassacres + +Name = "A Strange Fellow" +Outfit = (128,76-43-38-76-0) +Home = [32910,32077,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Uh? What do you want?!" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just wait a minute!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Still I am better in vanishing!" + + +"bye" -> "Good riddance.", Idle +"farewell" -> * + +"name" -> "My name is not of your concern." +"job" -> "That's only my business, not yours." +QuestValue(229)>1,"david" -> "Yes, yes... Its me .. you exposed me! Stop nagging me with that." +QuestValue(229)>1,"brassacres" -> * +"david" -> "I never heard that name and now get lost." +"brassacres" -> * + +QuestValue(229)=2,"bill" -> Type=3216, Amount=1,"A bill? Oh boy so you are delivering another bill to poor me?",Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Ok, ok, I'll take it. I guess I have no other choice anyways. And now leave me alone in my misery please.",Delete(Type),SetQuestValue(229,3) +Topic=5,"yes",Count(Type) "Ha Ha! You have none!! Naanaanaanaaanaaaa!",Idle +Topic=5 -> "Hoooraaaay! Uhm... I mean, thats fine..." + + +"bill" -> "Thats not my concern, you are probably looking for someone else and now get lost!",Idle + +Topic=1, "hat" -> "Stop bugging me about that hat, do you listen?", Topic=2 + +Topic=2, "hat" -> "Hey! Don't touch that hat! Leave it alone!!! Don't do this!!!!", Topic=3 +Topic=3, "hat" -> "Noooooo! Argh, ok, ok, I guess I can't deny it anymore, I am David Brassacres, the magnificent, so what do you want?",Summon("Rabbit"),Summon("Rabbit"),Summon("Rabbit"),Summon("Rabbit"),SetQuestValue(229,2) +QuestValue(229)=1,"hat" -> "What? My hat?? Theres... nothing special about it!", Topic=1 + +"hat" -> "Get lost!",Idle +} diff --git a/app/SabrehavenServer/data/npc/stutch.npc b/app/SabrehavenServer/data/npc/stutch.npc new file mode 100644 index 0000000..4e54507 --- /dev/null +++ b/app/SabrehavenServer/data/npc/stutch.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# stutch.npc: Datenbank für den Wachmann Stutch + +Name = "Stutch" +Outfit = (131,79-79-79-79-0) +Home = [32310,32170,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","king",! -> "HAIL TO THE KING!" +ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","tibianus",! -> "HAIL TO THE TIBIANUS!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","king",! -> "Wait for your audience!" +BUSY,"hail$","king",! -> "Wait for your audience!" +BUSY,"salutations$","king",! -> "Wait for your audience!" +BUSY,"hi$","king",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/app/SabrehavenServer/data/npc/suzy.npc b/app/SabrehavenServer/data/npc/suzy.npc new file mode 100644 index 0000000..cafcd2d --- /dev/null +++ b/app/SabrehavenServer/data/npc/suzy.npc @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# suzy.npc: Datenbank für die Bankangestellte Suzy + +Name = "Suzy" +Outfit = (136,78-10-96-95-0) +Home = [32320,32258,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I work in this bank. I can change money for you." +"name" -> "I am Suzy." +"time" -> "It is exactly %T." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/sylvester.npc b/app/SabrehavenServer/data/npc/sylvester.npc new file mode 100644 index 0000000..29848e4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/sylvester.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sylvester.npc: Datenbank für eine Stadtwache in Venore + +Name = "Sylvester" +Outfit = (131,113-113-113-115-0) +Home = [32897,32145,4] +Radius = 3 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/talesia.npc b/app/SabrehavenServer/data/npc/talesia.npc new file mode 100644 index 0000000..8355b1b --- /dev/null +++ b/app/SabrehavenServer/data/npc/talesia.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Talesia.npc: Datenbank für die Besitzerin des Grünzeugmarktes Talesia + +Name = "Talesia" +Outfit = (138,114-78-77-116-0) +Home = [32979,32035,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutation, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Come back later, I am talking." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Fare thee well." + +"bye" -> "Fare thee well.", Idle +"name" -> "I am Talesia De'Mir, owner of Crunor's Finest Warehouse." +"job" -> "I am a the owner of Crunor's Finest Warehouse of course." +"time" -> "It's %T right now." +"king" -> "We pay this man enough to live here undisturbed of major interventions." +"tibianus" -> * +"army" -> "At least they are useful, but we pay enough taxes to supply the entire Thaian army." +"ferumbras" -> "I hope he is aware that his enemies live elsewhere." +"excalibug" -> "I am contend with my familysword meloncutter." +"thais" -> "I hope they live well from our taxes..." +"tibia" -> "The world is a treasure chest for those of knowledge and skill." +"warehouse" -> "My warehouse is only one of many. We merchants hold this city together and lead it to prosperity." +"carlin" -> "They are a bit problematic as business partners, but their independence from Thais is... interesting." +"news" -> "Even bad news can be good news, if you play your cards well." +"tax" -> "Venore is the major tax payer in the whole realm. So we more than deserve the privileges the king granted us." +"crunor" -> "A god worth to worship. At least he gives something useful back to the faithful." +"privilege" -> "We are alowed to trade with anyone, Thaian subject or not, have no Thaian noble as governor, and own the exclusive gambling license." +"gambling" -> "I don't care much about it, though others profit greatly." +} diff --git a/app/SabrehavenServer/data/npc/talphion.npc b/app/SabrehavenServer/data/npc/talphion.npc new file mode 100644 index 0000000..79a4d1e --- /dev/null +++ b/app/SabrehavenServer/data/npc/talphion.npc @@ -0,0 +1,102 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# talphion.npc: Datenbank fuer den Technomancer Talphion (Zwergenstadt) + +Name = "Talphion" +Outfit = (160,11-86-87-106-0) +Home = [32563,31894,12] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "HIHOOOO %N! " +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "NOT NOW! TALKING! STAND IN LINE!.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! CAREFUL WHERE YOU STEP!" + +"bye" -> "YEAH, GO AWAY!", Idle +"farewell" -> * +"job" -> "WHERE SHOULD I HOP?", topic=1 +topic=1,"job" -> "OH, JOB? I AM THE CHIEF TECHNOMANCER!" +"name" -> "I HAVE NO TIME FOR A GAME!", topic=2 +topic=2,"name" -> "I AM TALPHION SPARKBENDER, SON OF THE MACHINE, FROM THE SAVAGE AXES." +"hall","ancients" -> "JUST A BUNCH OF BONES." +"tibia" -> "CAN'T TELL MUCH ABOUT IT. SELDOM GET OUT HERE, I AM A BUSY DWARF." +"kazordoon" -> "WHAT?", topic=3 +topic=3,"kazordoon" -> "WHOS DOOMED?", topic=4 +topic=4,"kazordoon" -> "OH, THE CITY? NICE, ISN'T IT?" + +"big","old" -> "THIS IS THE NAME OF THIS MOUNTAIN!" +"elves" -> "NO. I DON'T NEED ANY SHELVES!" +"humans" -> "A PROMISING RACE, SOME OF THEM ACTUALLY ADMIRE MECHANICS." +"orcs" -> "LET THEM COME, I AM WORKING ON A LITTLE SURPRISE FOR THEM! " +"minotaurs" -> * +"pyromancer" -> "OLD FOOLS, TO MUCH CONCERNED ABOUT TRADITION." +"geomancer" -> * +"technomancer" -> "WE ARE THE FUTURE. WE WILL BECOME A MAJOR POWER IN DWARFEN SOCIETY SOON! THEY WILL SEE, THEY WILL ALL SEE! " +"god" -> "GODS, WHO NEEDS GODS, WHEN WE CAN BUILD THE CORRECT MACHINE FOR EVERY OCCASION?" +"fire" -> "NICE RESOURCE FOR OUR MACHINES, BUT NO NEED TO MAKE A BIG DEAL ABOUT IT, JAWOLL!" +"flame" -> * +"earth" -> "SORRY, BUT JUST DUST AND MUD TO ME." +"durin" -> "I AM SURE HE WOULD BE SMART ENOUGH TO SEE THE CHANCES WE PROVIDE FOR DWARFENHOOD." +"life" -> "WHAT HIVE?" +"plant" -> "HEY! HOW DID YOU LEARN ABOUT OUR SECRET PLANT?" +"citizen" -> "YOU CAN BECOME CITIZEN IN THE HALL OF ANCIENTS." +"kroox" -> "WE COULD TEACH HIM MUCH IF HE LISTENED." +"jimbin" -> "HIS BREWERY SAVED OUR DAY MORE THEN ONCE IN MANY WAYS." +"maryza" -> "LOVELY, BUT PREDJUDICED AS MOST DWARFS ARE." +"bezil" -> "BEZIL AND NEZIL ARE RUNNING A SHOP." +"nezil" -> * +"uzgod" -> "WE COULD MAKE FOR HIM MACHINES TO DO HIS WORK IN HALF THE TIME I BET." +"etzel" -> "WHO NEEDS MAGIC? PAH!" +"duria" -> "KNIGHTS DO NOT HAVE THE BRAIN TO EVEN UNDERSTAND WHAT WE ARE OFFERING THEM." +"offering" -> "YES, THE MOST SOPHISTICATED ITEMS THEY BUY ARE CROSSBOWS." + +"crossbow" -> Type=3349, Amount=1, Price=1150, "DO YOU WANT TO BUY A CROSSBOW FOR %P GOLD?", Topic=5 +"bolt" -> Type=3446, Amount=1, Price=5, "DO YOU WANT TO BUY A BOLT FOR %P GOLD?", Topic=5 + +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=5*%1, "DO YOU WANT TO BUY %A BOLTS FOR %P GOLD?", Topic=5 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=1150*%1, "DO YOU WANT TO BUY %A CROSSBOWS FOR %P GOLD?", Topic=5 + +Topic=5,"yes",CountMoney>=Price -> "HERE YOU ARE.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "NOT ENOUGH MONEY. SORRY." +Topic=5 -> "PERHAPS NEXT TIME." + +"emperor" -> "AT LEAST HES SMART ENOUGH TO LEAVE US ALONE, SO THERES HOPE FOR HIM." +"kruzak" -> * +"motos" -> "STUPID IDIOT, WITH SOME MORE RESOURCES I COULD BUILD FOR HIM WARMACHINES BEYOND HIS WILDEST DREAMS! " +"general" -> * +"army" -> "ONE DAY OUR MACHINES WILL CHANGE THE ARMY STRUCTURES DRASTICALLY, JAWOLL!" +"ferumbras" -> "I BET I COULD BUILD A MACHINE TO SHRED HIM INTO PIECES!" +"excalibug" -> "OLD FASHIONED BUTTERKNIFE! IF THEY LET ME, I WOULD CREATE WEAPONS THAT LEVEL ENTIRE CITIES!" +"news" -> "ASK JIMBIN ABOUT HIS BREWS, NOT ME!" +"monster" -> "I COULDN'T CARE LESS ABOUT THEM." +"help" -> "WHOM YOU ARE CALLING A WHELP, YOU &$(&*#!", idle +"quest" -> "BRING ME THE SCREWDRIVER OF KURIK AND I WILL REWARD YOU WITH A STEAMPOWERED SPIKESWORD!" +"task" -> * +"what","do" -> * +"gold" -> "DONATIONS ARE ALWAYS WELCOME!" +"money" -> * +"equipment" -> "YOU ARE TOO STUPID FOR MOST OF OUR STUFF, BUT I COULD SELL YOU SOME CROSSBOWS." +"fight" -> "NO, DONT SWITCH OUT THE LIGHT." + +Topic=6,"dress","pattern" -> "A PRESS LANTERN? NEVER HEARD ABOUT IT!",Topic=7 +Topic=7,"dress","pattern" -> "CHESS? I DONT PLAY CHESS!",Topic=8 +Topic=8,"dress","pattern" -> "A PATTERN IN THIS MESS?? HEY DON'T INSULT MY MACHINEHALL!",Topic=9 + +Topic=9,"dress","pattern" -> "AH YES! I WORKED ON THE DRESS PATTERN FOR THOSE UNIFORMS. STAINLESS TROUSERS, STEAM DRIVEN BOOTS! ANOTHER MARVEL TO BEHOLD! I'LL SEND A COPY TO KEVIN IMEDIATELY!",SetQuestValue(233,4) +Topic=9,"uniform" -> * + +"technical","details" -> "TECH DETAILS ABOUT WHAT???" +"dress","pattern",QuestValue(233)=3 -> "DRESS FLATTEN? WHO WANTS ME TO FLATTEN A DRESS?",Topic=6 + +"dress","pattern",QuestValue(233)<>3 -> "DRESS FLATTEN? WHO WANTS ME TO FLATTEN A DRESS?" +"uniform" -> "NO, HERE IS NO UNICORN!" + +"heal",Burning>0 -> "YOU ARE BURNING! THAT'S FUN, HOW DO YOU DO THAT?" +"heal",Poison>0 -> "YOU ARE POISONED! HAVE YOU DRUNK THE STUFF IN A GREEN BOTTLE? THAT'S SUPERGLUE, NOT SUPPER-GLUE, STUPID!" +"heal" -> "I AM AN ENGINEER, NOT A DOCTOR!" +"time" -> "ONE DAY I WILL CREATE A CLOCK FOR THE COLOSSUS" +"colossus" -> "NICE PIECE OF WORK. WOULD BE MORE FUN IF IT COULD MOVE AROUND... WE HAVE PLANS..." +} diff --git a/app/SabrehavenServer/data/npc/tandros.npc b/app/SabrehavenServer/data/npc/tandros.npc new file mode 100644 index 0000000..6c9e92e --- /dev/null +++ b/app/SabrehavenServer/data/npc/tandros.npc @@ -0,0 +1,107 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tandros.npc: Datenbank für den magierhändler Tandros + +Name = "Tandros" +Outfit = (132,78-79-113-95-0) +Home = [32621,32739,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, student of the arcane." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Show some patience please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am selling items of magic power such as runes, wands, rods, life fluids and mana fluids." +"name" -> "I am Tandros the magnificent." +"time" -> "It is a crime against the order of things to try measuring time. The very thought of squeezing the majesty of centuries and centuries into a puny mechanical device is blasphemy." +"king" -> "Our king is a worldly one. But if you buy enough of my fluids and runes you might become the king of magic one day." +"venore" -> "Technically I am an employee of those trade barons of Venore but of course no one can control my magnificent mind." +"thais" -> "It is so crowded and people there are always busy. I dare to say that this is a city that has lost its magic at some point." +"carlin" -> "I heard there are many druids that are quite influential. They should know how to keep the magic of a place alive. I am looking forward to travel there one day." +"edron" -> "Edron is rumoured to be a place of ancient mysteries and powerful magic." +"jungle" -> "The magic is out there somewhere." + +"tibia" -> "The world is full of magic that is waiting to be used ... perhaps by you! Take the first step by buying my wares to gather even more magic power for yourself." + +"kazordoon" -> "Dwarves have little love for magic. That makes them quite suspicious, doesn't it?" +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Elves are such marvelous, mythic creatures. They are full of magic." +"elves" -> * +"elfs" -> * +"darama" -> "Although our people, spoken in cosmological terms, have setteled here just recently, there is already much history hidden here. ...", + "Not only mysteries and magical secrets but also many treasures are here to be explored by that person that is equipped with enough runes and fluids to master all dangers." +"darashia" -> "An unremarkable little town, but riding there by carpet is pure magic." +"ankrahmun" -> "A city that breathes evil and dark magic. Stay away or be at least well prepared if you intend to visit the city of the dead." +"ferumbras" -> "He might be evil, but his powers are unimaginable! To stand a chance against evil overlords like him, you have to buy loads of my runes and fluids." +"excalibug" -> "A weapon of unparalleled magic. Don't listen to people that tell you that this is only a myth. It might be a dream but remember, dreams can come true." +"apes" -> "They are attacking travellers and even our settlement now and then. What can be a better way for you to survive than by preparing yourself well and to buy enough fluids and runes?" +"lizard" -> "The lizards live far away on the other side of the dangerous jungle. If you want to go there to learn more about their secrets, I strongly advise you to supply yourself with runes and fluids." +"dworcs" -> "The dworcs are fierce enemies and the poison they use is lethal. If you don't have some fluids and runes with you, you are easy prey to them." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on every vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/tesha.npc b/app/SabrehavenServer/data/npc/tesha.npc new file mode 100644 index 0000000..146148a --- /dev/null +++ b/app/SabrehavenServer/data/npc/tesha.npc @@ -0,0 +1,125 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tesha.npc: Datenbank für die pyramidenhändlerin Tesha + +Name = "Tesha" +Outfit = (140,77-32-81-93-0) +Home = [33135,32821,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell and buy gems and jewelry." +"name" -> "I am the mourned Tesha." +"time" -> "Time is yet another burden that lies heavy on our mortal bodies." +"temple" -> "The temple can offer us guidance and solace in our mortal existence." +"pharaoh" -> "He is the benevolent father of this nation. Blessed be our saviour." +"oldpharaoh" -> "This poor man could not comprehend his son's wisdom. Perhaps he has spelled his own eternal doom." +"scarab" -> "The priests say they are sacred beings, although ... I find them scary!" +"chosen" -> "I can only hope my humble work for our community and for the temple will make me worthy one day to be elevated to the rank of a chosen one. One to whom the path of ascension is opened up through undeath." + +"tibia" -> "The world is so huge, and I have seen so little. Perhaps if I am chosen one day I will travel and see it all." +"carlin" -> "Those citites are so far away. So far that the enlightened preachings of our divine pharaoh cannot reach those poor misguided souls." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves have a strong Akh. This makes them arrogant and deaf to the true creed." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Most elves lack the sincerity to strive for ascension. At least that's what the priests are telling us." +"elves" -> * +"elfes" -> * +"darama" -> "In the desert the lines of life and death are clearly drawn. Because of this it is easier for us, its children, to focus on them. In the jungle those lines are fuzzy and blurred, and people easily fall victim to temptation." +"darashia" -> "Those poor souls there might still be saved if only they listened." +"daraman" -> "He was a great man. If he had left his mortal existence behind he might have become one of the greatest prophets of the true faith, second only to the pharaoh himself." +"Ankrahmun" -> "This city is both a refuge and centre of learning for the believers of the true faith taught by his divine majesty the pharaoh." + +"pharaoh" -> "The pharaoh has such amazing patience with us puny mortals. He is truly a caring father of this nation." +"mortality" -> "Mortality can be overcome. It is a sickness, but it can be cured through undeath." +"false", "gods" -> "These greedy beings are trying to devour us all. May the pharaoh thwart their evil plans and free us from their reign of terror!" +"ascension" -> "Oh, I am not asking for much, you know. I mean, I really don't have to be a god or something. All I wish for is a bit of the wisdom that comes with ascension." +"Akh'rah","Uthun" -> "I don't really understand this concept, but from what I know it is the three components that make up every being." +"Akh" -> "Our body. The only physical part of the Akh'rah Uthun." + +"undead" -> "Undeath is the reward for a life of faith and service." +"undeath" -> * +"Rah" -> "The Rah is our essence. The spiritual bond that keep the other parts of the Akh'rah Uthun together." +"uthun" -> "The Uthun is all we learned in life." +"mourn" -> "The dead mourn our tempted existence, and we mourn ourselves." +"arena" -> "Look for it in the eastern part of the city." +"palace" -> "Isn't the palace magnificent to behold? It is so impressive!" + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels. I also change money." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"gem" -> "You can buy and sell small diamonds, small sapphires, small rubies, small emeralds, and small amethysts." +"pearl" -> "There are white and black pearls you can buy or sell." +"jewel" -> "Currently you can purchase gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/tezila.npc b/app/SabrehavenServer/data/npc/tezila.npc new file mode 100644 index 0000000..9625866 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tezila.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tezila.npc: Datenbank für die Juwelierin Tezila + +Name = "Tezila" +Outfit = (160,3-92-110-115-0) +Home = [32657,31922,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, %N. Come in and look. But don't touch the exhibits, jawoll!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy with a customer. Please have some patience.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a jeweller. Maybe you want to have a look at my wonderful offers. I also exchange money." +"name" -> "I am Tezila Gemcutter, daughter of Fire, from the Savage Axes." +"time" -> "It's %T." + +"offer" -> "I can sell you glittering gems, precious pearls or some ... uhm ... jolly jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "I have white and black pearls you can buy, but you can also sell me some." +"jewel" -> "You can purchase our fine dwarfish wares like gold converting rings, wedding rings, golden amulets, and ruby necklaces." +"talon" -> "I am suspicious of these magic gems. Better you ask some mages about this." +"tibianus","talon" -> "I am more interested in buying them instead of selling." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 +"crystal","ring" -> Type=3007, Amount=1, Price=250, "Do you want to buy a crystal ring to convert gold for %P gold?", Topic=1 +"gold","convert" -> * + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 +%1,1<%1,"crystal","ring" -> Type=3007, Amount=%1, Price=250*%1, "Do you want to buy %A crystal rings to convert gold for %P gold?", Topic=1 +%1,1<%1,"gold","convert" -> * + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 +"sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 +"sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 + +"sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 +"sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/app/SabrehavenServer/data/npc/thanita.npc b/app/SabrehavenServer/data/npc/thanita.npc new file mode 100644 index 0000000..0fdd96d --- /dev/null +++ b/app/SabrehavenServer/data/npc/thanita.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Thanita.npc: Datenbank für die Amazonenwächterin des Turms + +Name = "Thanita" +Outfit = (139,78-52-64-52-0) +Home = [32535,31773,1] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "How could you sneak up on me like this? I thought you were one of THEM! Well, since you are not, what brings you to this wilderness?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please wait.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take good care of yourself traveler!" + +"bye" -> "Take good care of yourself traveler. Would be a shame to lose such a courageous wanderer to those green monsters.", Idle +"farewell" -> * +"job" -> "I'm an amazon guard. It's my job to keep my eyes open and to keep enemies from passing by. My job here truely is one of the toughest. All because of these nerve-racking beasts." +"name" -> "My name is Thanita. Nice to meet you." + +"beasts" -> "These green, orcish raiders come in masses. Hundreds of them. They are worse than those goblins I have to deal with from time to time. ...", + "Some of them come on beastly warwolves, others shoot fireballs at you and some are just plain ugly. ...", + "They have never succeeded though in capturing this tower. So far I have always been able to put them to flight." +"orcs" -> * +"them" -> * +"raid" -> * + + +"mission" -> "Well, I cannot provide you with a mission, I have a mission to fulfill myself. ...", + "However, when the orcs attack, you are more than welcome to help me to defeat them. ...", + "I'll even let you have all the stuff they carry with them. Even if they looted it from the tower here." +"quest" -> * + + +"fight" -> "To get rid of them, you need to be quite good in different martial arts." +"defeat" -> * + +"femor", "hills" -> "Are you kidding me? This is femor hills. Probably one of the roughest areas in Tibia. There is nobody else could do my job." + +"tower" -> "This is a watchtower of the city of carlin. From here I can see pretty much all of the surrounding lands. Hardly anybody can startle me up here. I see all enemies long before they can see me." + +"enemies" -> "The enemies I fear most here are these nasty orcs." + +"amazon" -> "I see you have heard of amazons before. Well let me tell you, probably everything you heard is true. We are much stronger and tougher than people think. Also, we know how to fight and could teach many men how to handle a weapon. ...", + "Not that we would do such a foolish thing." + +"bye" -> "Take good care of yourself traveler. Would be a shame to lose such a courageous wanderer to those green monsters." +} diff --git a/app/SabrehavenServer/data/npc/theodoreloveless.npc b/app/SabrehavenServer/data/npc/theodoreloveless.npc new file mode 100644 index 0000000..d59b8d6 --- /dev/null +++ b/app/SabrehavenServer/data/npc/theodoreloveless.npc @@ -0,0 +1,42 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Theodore Loveless" +Outfit = (132,19-57-76-114-1) +Home = [32362,32787,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, dear visitor." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am honoured to humbly serve as the spokesman of the Venorean trading companies." +"Venore" -> "Venore has become an impressive little point of trade for wares from all over the world. The steady work pays off and brings us a modest state of luxury ...", + "We have learnt much from our big brother Thais which we admire and where we are still searching for guidance." +"Liberty Bay" -> "We put great hope in this settlement. The Venorean traders invested much money in the growth of that community here ...", + "We have to see that the little profit we make will be enough to cover our expenses in the remote future ...", + "But we are happy that our money could help to bring peace and to civilise the natives here ...", + "Thais finally brought order to the locals that were torn from fightings and hunger before our arrival." +"Thais" -> "Thais is such a lovely city. The bustling centre of civilisation. The world has to thank Thais for so many things." +"sugar" -> "Sugar is money. Sugar is business. It's as easy as that." +"Ferumbras" -> "I heard he has some relation to these isles. To learn more about it, you probably talk to the mage from Edron that resides in a tower here in the city." +"king" -> "The king has again proven his great wisdom. The way how he allowed to settle the affairs in Liberty Bays was more than smart ...", + "The trust he has put in the governor, the local military and the help from Venore begins slowly to pay off ...", + "People have work and something to do. The number of fights decreased to the brawls between drunks that are usual for seaports ...", + "As soon as we have the pirates eliminated, we can lower many of the restrictions we had to enforce for the safety of all." +"governor" -> "Governor Silverhand is such a fine person. He does so much for the city and receives so little in return. I hope we can cheer him up now and then. That's the least we can do for this great man that sacrifices himself for the good of his people." +"companies" -> "Ah, my young friend, you have no idea how many companies have some sort of interest in seeing this settlement prosper." +"pirates" -> "The pirates are all what is left from the warlords that once terrorised this region. Now that peace and the protection of Liberty Bay byour military moves in, their days are numbered ...", + "One day, hopefully in the near future, the sea will be just as safe as this isle." +"Isolde" -> "I must admit I am a bit suspicious of this young lady. As far as I know she was not overly fond of the Thaian nobility. Add her strong family ties to this rebel townCarlin and you begin to wonder what that woman is up to. All of a sudden she accepted a mission for the nobility that she did not acknowledge before? And this unholy influence on this poor young Tristan whom she seduced with her beauty. I must admit, I am concerned." +"Tristan" -> "What a fine young man. I just wonder what brought him here in the first place. Of course I don't believe into rumours that he left a sick wife and 3 childrensomewhere to begin a new life here. That is probably just made up by those that are jealous of him." +"Admiral Wyrmslicer" -> "Even the mentionable efforts of this great admiral could not stop the pirates yet. But he will not rest until the last one of this scum will hang for the glory of Thais." +"Eleonore" -> "Miss Eleonore is so charming, so beautiful. Her strong will and her independence poses surely a great challenge to every man who wants to marry her. But one look into her beautiful eyes and you will see that she is worth the effort. Perhaps one day I might be allowed to take that challenge ...", + "who knows." +} diff --git a/app/SabrehavenServer/data/npc/thomas.npc b/app/SabrehavenServer/data/npc/thomas.npc new file mode 100644 index 0000000..3392a60 --- /dev/null +++ b/app/SabrehavenServer/data/npc/thomas.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# thomas.npc: Datenbank für den Schreibwarenhändler Thomas + +Name = "Thomas" +Outfit = (128,116-11-101-76-0) +Home = [33256,31838,4] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Don't be that impatient, %N. Wait a moment!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"name" -> "Thomas." +"job" -> "Selling spellbooks, notebooks, scrolls, documents, parchments, inkwells, and the like." +"time" -> "No idea." +"king" -> "Know nothing interesting about him." +"tibianus" -> * +"army" -> "Ask in the castle." +"ferumbras" -> "Only heard of him." +"excalibug" -> "No idea." +"thais" -> "Never been there." +"tibia" -> "Like it here best." +"carlin" -> "Never been there." +"edron" -> "This town." +"news" -> "Nothing interesting." +"rumors" -> * + +"offer" -> "Selling scrolls, documents, parchments, spellbooks, books, and inkwells." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"inkwell" -> Type=3509, Amount=1, Price=8, "Do you want to buy an inkwell for %P gold?", Topic=1 +"greeting","card",ClientVersion>=790 -> Type=6386, Amount=1, Price=30, "Do you want to buy a greeting card for %P gold?", Topic=1 +"valentine","card",ClientVersion>=790 -> Type=6538, Amount=1, Price=30, "Do you want to buy a valentine's card for %P gold?", Topic=1 + +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"inkwell" -> Type=3509, Amount=%1, Price=8*%1, "Do you want to buy %A inkwells for %P gold?", Topic=1 +%1,1<%1,"greeting","card",ClientVersion>=790 -> Type=6386, Amount=%1, Price=30*%1, "Do you want to buy %A greeting cards for %P gold?", Topic=1 +%1,1<%1,"valentine","card",ClientVersion>=790 -> Type=6538, Amount=%1, Price=30*%1, "Do you want to buy %A valentine's cards for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/app/SabrehavenServer/data/npc/tibra.npc b/app/SabrehavenServer/data/npc/tibra.npc new file mode 100644 index 0000000..93f137b --- /dev/null +++ b/app/SabrehavenServer/data/npc/tibra.npc @@ -0,0 +1,122 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tibra.npc: Datenbank für die Priesterin Tibra + +Name = "Tibra" +Outfit = (138,60-92-90-95-0) +Home = [32318,31783,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","tibra",! -> "Welcome in the name of the gods, pilgrim %N!" +ADDRESS,"hi$","tibra",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +ADDRESS,male,"my$","heart$","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,female,"my$","heart$","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=9 + +BUSY,"hello$",! -> "%N, please be patient and wait a moment, my child.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods guard you!" + +"bye" -> "Good bye, %N. May the gods be with you to guard and guide you, my child!", Idle +"farewell" -> * +"job" -> "I am a priest of the great pantheon." +"news" -> "Sorry, worldly matters are of no concern to me." +"name" -> "My name is Tibra. Your soul tells me that you are %N" +"tibia" -> "The world of Tibia is the creation of the gods." +"how","are","you"-> "Thank you, I'm fine, the gods give me hope and comfort." +"sell" -> "The grace of the gods must be earned, it cannot be bought!" +"sin$" -> "Do you wish to confess your sins, my chilid?", Topic=3 +"sins$" -> * + +"marriage","ceremony" -> "So you want me to initate a marriage ceremony?", Topic=5 +Topic=5,"yes" -> "In the name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time, marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence, please! I hereby invoke the attention of the eternal Powers looking over our souls and lives. May the gods bless us!", EffectMe(2), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(1), idle +Topic=8,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(1), idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further life as married couple! Go now and celebrate your marriage!", EffectOpp(1), EffectMe(7), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", idle + +"god$" -> "The gods of good guard us and guide us, the gods of evil want to destroy us and steal our souls!", Topic=2 +"gods$" -> * +"life" -> "The teachings of Crunor tell us to honor life and not to harm it." +"citizen" -> "The things we priests know about the citizens are confidential." +"lugri" -> "Only a man can fall as low as he did. His soul rotted away already." +"queen" -> "Queen Eloise is wise to listen to the proposals of the druidic followers of Crunor." +"monster" -> "Remind: Not everything you call monster is evil to the core!" +"quest" -> "It is my mission to bring the teachings of the gods to everyone." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "It is MY mission to teach, it is YOUR mission to fight!" +"slay" -> * +"mission" -> * +"heal$",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"heal$",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"heal$" -> "You aren't looking that bad. Sorry, I need my powers for cases more severe than yours." +"help",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"help" -> "You aren't looking that bad. Sorry, I need my powers for cases more severe than yours." +"ferumbras" -> "The fallen one should be mourned, not feared." +"time" -> "Now, it is %T." +"excalibug" -> "The mythical blade was hidden in ancient times. Its said that powerful wards protect it." +"graveyard" -> "There's something strange in its neighbourhood. But whom we gonna call for help if not the gods?" +"crypt" -> * +"mausoleum" -> * + +Topic=2,"good" -> "The gods we call the good ones are Fardos, Uman, the Elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator. The great obsever. He is our caretaker." +"uman" -> "Uman is the positive aspect of magic. He brings us the secrets of the arcane arts." +"suon" -> "Suon is the lifebringing sun. He observes the creation with love." +"crunor" -> "Crunor, the great tree, is the father of all plantlife. He is a prominent god for many druids." +"nornur" -> "Nornur is the mysterious god of fate. Who knows if he is its creator or just a chronist?" +"bastesh" -> "Bastesh, the deep one, is the goddess of the sea and its creatures." +"kirok" -> "Kirok, the mad one, is the god of scientists and jesters." +"toth" -> "Toth, Lord of Death, is the keeper of the souls, the guardian of the afterlife." +"banor" -> "Banor, the heavenly warrior, is the patron of all fighters against evil. He is the gift of the gods to inspire humanity." +"tibiasula" -> "Tibiasula lost her life, but out of her essence the world was created." +Topic=2,"tibia" -> "Tibia is the essence of the elemental power of earth." +"sula" -> "Sula is the essence of the elemental power of water." +"air" -> "Air is one of the primal elemental forces, sometimes worshipped by tribal shamans." +"fire" -> "Fire is one of the primal elemental forces, sometimes worshipped by tribal shamans." + +Topic=2,"evil" -> "The gods we call the evil ones are Zathroth, Fafnar, Brog, Urgith, and the Archdemons!" +"zathroth" -> "Zathroth is the destructive aspect of magic. He is the deciver and the thief of souls." +"fafnar" -> "Fafnar is the scorching sun. She observes the creation with hate and jealousy." +"brog" -> "Brog, the raging one, is the great destroyer. The berserk of darkness." +"urgith" -> "The bonemaster Urgith is the lord of the undead and keeper of the damned souls." +"archdemons" -> "The demons are followers of Zathroth. The cruelest are known as the ruthless seven." +"ruthless","seven" -> "I dont want to talk about that subject!" + +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes",CountMoney "Don't be ashamed, but you lack the gold." + +Topic=3,"yes" -> "So tell me, what shadows your soul, my child.", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and pray for your soul." + +"stake",QuestValue(17576)=2,Count(5941)<=0 -> "I think you have forgotten to bring your stake, my child." +"stake",QuestValue(17576)=2 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Hope may fill your heart - doubt shall be banned'. Now, bring your stake to Maealil in the elven settlement for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,3) +Topic=10,"yes" -> "I think you have forgotten to bring your stake, my child." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=3 -> "You should visit Maealil in the elven settlement now, my child." +"stake",QuestValue(17576)>3 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That is a strange request, my child. Maybe Quentin knows more, he is one of the oldest monks after all." + +} diff --git a/app/SabrehavenServer/data/npc/tim.npc b/app/SabrehavenServer/data/npc/tim.npc new file mode 100644 index 0000000..e05fa75 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tim.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tim.npc: Datenbank für die Stadtwache am Osttor + +Name = "Tim, the guard" +Outfit = (131,19-19-19-19-0) +Home = [32428,32226,6] +Radius = 4 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/app/SabrehavenServer/data/npc/timur.npc b/app/SabrehavenServer/data/npc/timur.npc new file mode 100644 index 0000000..9c0fde0 --- /dev/null +++ b/app/SabrehavenServer/data/npc/timur.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# timur.npc: Datenbank für den Händler Timur auf der Insel Fibula + +Name = "Timur" +Outfit = (128,0-116-102-95-0) +Home = [32191,32443,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$","timur",! -> "Hello, %N. Come in and buy." +ADDRESS,"hi$","timur",! -> * +ADDRESS,"hello$",! -> "Welcome to my little shop, adventurer! First read my blackboards." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to a customer, %N. Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment. Do you want something?" +"name" -> "I am Timur. Sorry, I have not much equipment for sale. The business is running low." +"timur" -> * +"time" -> "I am sorry, I have no watch." +"fibula" -> "It's a very nice isle. But we don't have enough weapons to defeat the many wolves." +"wolf" -> "They are everywhere around the village." +"wolves" -> * +"helmet" -> "I can sell you a viking helmet in a very good quality." +"food" -> "If you are looking for food, buy a rod and go fishing." +"weapon" -> "At the moment I have no weapons to offer. Weapons are very rare on this isle, so I have to buy a few." +"bow" -> "We have too few bows on this isle for our hunters." +"crossbow" -> "We have too few crossbows on this isle for our hunters." +"fluid" -> "The magic shops have a monopole on fluids now... argl!" +"magic" -> * + +"equipment" -> "I sell torches, scrolls, documents, parchments, ropes, fishing rods, sixpacks of worms, arrows, bolts, and a nice helmet." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"ammo" -> "I have arrows and bolts in this shop." +"ammunition" -> * + +"torch" -> Type=2920, Amount=1, Price=3, "Do you want to buy a torch for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=10, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=65, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=170, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=3, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=4, "Do you want to buy a bolt for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=3*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=10*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=65*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=170*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=3*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=4*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"sell","bow" -> Type=3350, Amount=1, Price=130, "Do you want to sell a bow for %P gold?", Topic=2 +"sell","crossbow" -> Type=3349, Amount=1, Price=160, "Do you want to sell a crossbow for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 + +"sell",%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=130*%1, "Do you want to sell %A bows for %P gold?", Topic=2 +"sell",%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=160*%1, "Do you want to sell %A crossbows for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Nice to do business with you.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You've not enough money." +Topic=1 -> "Hmm, maybe next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Hey, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/todd.npc b/app/SabrehavenServer/data/npc/todd.npc new file mode 100644 index 0000000..b31953d --- /dev/null +++ b/app/SabrehavenServer/data/npc/todd.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# todd.npc: Datenbank für den betrügerischen schmuggler todd + +Name = "Todd" +Outfit = (128,115-0-67-114-0) +Home = [32363,32210,6] +Radius = 7 + +Behaviour = { +ADDRESS,"hi$",! -> "Uhm oh hello... not so loud please... my head..." +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please wait... in silence if possible .", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Silence at last." + + +"bye" -> "Yes, goodbye, just leave me alone.", Idle + +"how","are","you"-> "Oh, this headache, one of the beers frodo served me must have been foul." +"job" -> "I am... a traveller." +"karl" -> "Uhm, never heared about him... and you can't proof otherwise.", Idle +"name" -> "My Name? I am To... ahm... hum... My name is Hugo." +"Hugo" -> "Yes, thats my name of course." +"smuggler" -> "I am a honest person and don't like to be insulted!", Idle +"carlin" -> "I never was there. Now leave me alone.", Idle +"resistance" -> "Resistance is futile... uhm... I wonder where I picked that saying up. Oh my head..." +"head" -> "Uhhh Ohhhh one of the beers yesterday must have been bad." + +"thais" -> "I love that city." +"william" -> "Thats a common name, perhaps I met a William, not sure about that." +"money" -> "I don't know anything about money, missing or not." +"todd" -> "Uh .. I... I met a Todd on the road. He told me he was traveling to Venore, look there for your Todd." + +} diff --git a/app/SabrehavenServer/data/npc/tokel.npc b/app/SabrehavenServer/data/npc/tokel.npc new file mode 100644 index 0000000..e2b1b53 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tokel.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tokel.npc: Datenbank für den Bauern Tokel in Greenshore + +Name = "Tokel" +Outfit = (128,78-96-30-114-0) +Home = [32256,32056,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. Please wait a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"how","are","you" -> "I am fine, thank you." +"sell" -> "I sell some food." +"job" -> "I am a farmer, and proud of it." +"name" -> "My name is Tokel." +"time" -> "Oh, now that you mention it: I have much left to do, please excuse me.", Idle +"help" -> "Sorry, I have no idea how to help you." +"monster" -> "It's relatively peaceful here." +"dungeon" -> "Here are no dungeons as far as I know." +"god" -> "I pray to Crunor to bless our harvests." +"king" -> "I wish I'd be as rich as him." +"greenshore" -> "The soil is a bit dry and there are a lot of stones. It's very hard to work this soil." +"magic" -> "I know nothing but about such stuff." +"weapon" -> * +"spell" -> * +"tibia" -> "I have not seen much of it yet. I am thinking about moving to Edron soon." +"thais" -> "The city is too lousy and crowded for my taste." +"edron" -> "They say life is easy there, the soil is rich, the city save. One day I might move there." + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"buy" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/app/SabrehavenServer/data/npc/tom.npc b/app/SabrehavenServer/data/npc/tom.npc new file mode 100644 index 0000000..896c6e7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tom.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tom.npc: Datenbank fuer den Gerber Tom + +Name = "Tom" +Outfit = (144,113-115-58-115-1) +Home = [32085,32199,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "I'm Tom the Tanner. How can I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N. I'll call you in a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Doh?" + +"bye",male -> "Good hunting, son.", Idle +"farewell",male -> * +"bye",female -> "Good hunting, child.", Idle +"farewell",female -> * +"how","are","you" -> "Much to do, these days." +"job" -> "I'm the local tanner. I buy fresh animal corpses, tan them, and convert them into fine leather clothes ...","I'm only selling to major customers. But I'm buying fresh corpses of rats, rabbits and wolves from you." +"name" -> "My name is Tom the tanner." + +"tanner" -> "That's my job. I buy fresh animal corpses, tan them, and convert them into fine leather clothes." +"corpse" -> "I'm buying fresh corpses of rats, rabbits and wolves. What do you want to sell?" +"animal" -> * +"monster" -> * +"wares" -> * +"major" -> "Yes. Obi, Norma and good old Al. Go ask them for leather clothes." +"customer" -> * + +"time" -> "Sorry, I haven't been outside for a while, so I don't know." +"help" -> "Help? I will give you a few gold coins if you have some dead animals for me." + +"troll" -> "Troll leather stinks. Can't use it." +"orc" -> "I don't buy Orcs. Their skin is too scratchy." +"human" -> "Are you crazy?!", Idle + +"rat",Questvalue(224)=0 -> Type=3994, Amount=1, Price=2, "I'll give you %P gold for a dead rat. Do you accept?", Topic=2 +"rabbit",Questvalue(224)=0 -> Type=4173, Amount=1, Price=2, "I'll give you %P gold for a dead rabbit. Do you accept?", Topic=2 +"rat" -> Type=3994, Amount=1, Price=2, "I'll give you %P gold for a dead rat. Do you accept?", Topic=1 +"rabbit" -> Type=4173, Amount=1, Price=2, "I'll give you %P gold for a dead rabbit. Do you accept?", Topic=1 +"wolf" -> Type=4007, Amount=1, Price=5, "Do you want to sell a dead wolf for %P gold?", Topic=1 +"minotaur","leather" -> Type=5878, Amount=1, Price=12, "Do you want to sell a minotaur leather for %P gold?", Topic=1 +"wolf","paw" -> Type=5897, Amount=1, Price=7, "Do you want to sell a wolf paw for %P gold?", Topic=1 +"bear","paw" -> Type=5896, Amount=1, Price=10, "Do you want to sell a bear paw for %P gold?", Topic=1 + +%1,1<%1,"rat",Questvalue(224)=0 -> Type=3994, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rats. Do you accept?", Topic=2 +%1,1<%1,"rabbit",Questvalue(224)=0 -> Type=4173, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rabbits. Do you accept?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rats. Do you accept?", Topic=1 +%1,1<%1,"rabbit" -> Type=4173, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rabbits. Do you accept?", Topic=1 +%1,1<%1,"wolf" -> Type=4007, Amount=%1, Price=5*%1, "Do you want to sell %A dead wolves for %P gold?", Topic=1 +%1,1<%1,"wolves" -> Type=4007, Amount=%1, Price=5*%1, "Do you want to sell %A dead wolves for %P gold?", Topic=1 +%1,1<%1,"minotaur","leather" -> Type=5878, Amount=%1, Price=12*%1, "Do you want to sell %A minotaur leathers for %P gold?", Topic=1 +%1,1<%1,"wolf","paw" -> Type=5897, Amount=%1, Price=7*%1, "Do you want to sell %A wolf paws for %P gold?", Topic=1 +%1,1<%1,"bear","paw" -> Type=5896, Amount=%1, Price=10*%1, "Do you want to sell %A bear paws for %P gold?", Topic=1 + +Topic=1,"yes",Count(Type)>=Amount -> "Ok. Corpse for me, gold for you.", Delete(Type), CreateMoney +Topic=1,"yes" -> "Sorry, you do not have a fresh one." +Topic=1,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=1 -> "Maybe another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Deal. By the way: If you'd like to hunt something bigger, check the cellar of the stables to the north. Some adventurer used to store his loot under a loose board beneath a barrel. He might have forgotten something when he left the isle.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have a fresh one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe another time." + +"sell" -> "Sorry. I'm only selling to major customers. But I'm buying fresh corpses of rats, rabbits and wolves from you." + + +"addon",QuestValue(18504)=0,premium -> "Would you like to wear bear paws like I do? No problem, just bring me 50 bear paws and 50 wolf paws and I'll fit them on.", SetQuestValue(18504,1) +"paws",QuestValue(18504)=0,premium -> * +"addon",QuestValue(18504)=0 -> "Addons can be wear only by premium players." +"paws",QuestValue(18504)=0 -> * + +"addon",QuestValue(18504)=1 -> "Have you brought 50 bear paws and 50 wolf paws?", Topic=3 +"paws",QuestValue(18504)=1 -> * +Topic=3,"yes",Count(5897)>=50,Count(5896)>=50 -> "Excellent! Like promised, here are your bear paws.", DeleteAmount(5897,50), DeleteAmount(5896,50), SetQuestValue(18504,2), AddOutfitAddon(148,1), AddOutfitAddon(144,1), EffectOpp(13) +Topic=3,"yes" -> "You don't have required ingredients." +Topic=3 -> "Maybe another time." + +"addon",QuestValue(18504)=2 -> "I see that you like your new bear paws!" +"paws",QuestValue(18504)=2 -> * + +} diff --git a/app/SabrehavenServer/data/npc/topsy.npc b/app/SabrehavenServer/data/npc/topsy.npc new file mode 100644 index 0000000..e5dd54f --- /dev/null +++ b/app/SabrehavenServer/data/npc/topsy.npc @@ -0,0 +1,118 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# topsy.npc: Datenbank für shopkeeper Topsy (magic) + +Name = "Topsy" +Outfit = (139,78-52-64-115-0) +Home = [32415,32170,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Hello, dear %N. How can I help you?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "My apologies, but I really must talk to this customer first. Sugar, I am busy with a customer at the moment. Please have some patience.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Do come again!" + +"bye" -> "Good bye. Do come again!", Idle +"farewell" -> * +"how","are","you" -> "I'm just wonderful - thank you for asking." +"job" -> "I sell runes, life and mana fluids - your best friends in any dungeon!" +"name" -> "My name is Topsy." +"time" -> "Time waits for no one! Not even you, sweetheart, so please do hurry up." +"help" -> "I'd love to help, but I have a business to run - I am busy busy busy!." +"monster" -> "Better buy and charge a lot of runes before facing any monster." +"dungeon" -> "Dungeons - dank cold places if you ask me. They lead to rusty armour, severe colds and death ... on the other hand you use a lot of runes there ... so just think about the treasures you'll surely find there." +"sewer" -> "The Thais sewerage system is a model of modern rat breeding and for some reason is very popular with young adventurers such as yourself." +"boss" -> "I had one once. He should have bought better armour. Actually - he's upstairs." +"thank","you" -> "Oh, such a sweetie ... and so polite. I thought politeness was out of fashion, these days." +"god" -> "Gods - if we didn't have them, we would have invented them." +"king" -> " Here we go again ... Hail to King Tibianus! ... Don't make me do that again!" +"sam" -> "Ah, such a sweetie. A simple man, with simple tastes and a simple mind." +"benjamin" -> "Bless him, he stood, he fought, and then left his sanity on the battlefield." +"gorn" -> "Ah yes ... Gorn ... the used-cart salesman of scrolls." +"quentin" -> "I can't tell much about that old monk." +"bozo" -> "He wanted to be the court jester but got upset when people laughed at him" +"rumour" -> "I'm all ears", Topic=4 +"gossip" -> * +"news" -> * +"Gamon" -> "I think he is a spy ... so I smile at him the whole day. He won't get anything out of me!" +"carlin" -> "I went there on holiday once. Just goes to show that women are much better at running a place than men. King Tibianus could learn a thing or two from Queen Eloise." +"weapon" -> "Wrong shop - go to my sister, silly!" +"magic" -> "Magic will only protect you - a rune and some magic fluids." +"power" -> "There is a power struggle between Venore and Thais." +"rebellion" -> "There is talk of a rebellion in Venore to gain independence from the Oppressor - I mean - King of Thais - it can only help business." +"quest" -> "I sell magic stuff, my dear. If you want a quest, you've come to the wrong shop." +"spells" -> "You never know when you run out of mana. All the more reason to buy some good runes or fluids." +"muriel" -> "You should ask this guy Oswald about him ... or other pointless rumors." +"elane" -> "Some call her the preying mantis - apparently she has killed over a dozen husbands already." +"venore" -> "A marvellous city! Modern! Prosperous! Thais could learn a thing or two from Venore" +"marvik" -> "Who knows what the old man is up to in his hideout when no one is watching?" +"gregor" -> "Ah Knight ... can't expect much from those guys." +"lugri" -> "I only heared rumours about him, isn't he a hermit somewhere in the north?" +"excalibug" -> "If you want to find out about excalibug you should ask the more sinister characters in Thais not a respectable woman like myself!" +"chester" -> "I have never seen him at all. I only heared hes kind of the townsguards chief or such." +"ardua" -> "What a strange woman. She lingers in our shop now and then ... I wonder what shes up to." +"partos" -> "I heared he was a thief. Good thing he was caught." +"gamel" -> "He hung around with that partos a lot. I wouldn't be suprised if he's a thief too. He is not allowed to enter our markethall." +"quest" -> "Sure. Just ask my sister Turvy." +Topic=4 -> "Really? Tststs." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/torence.npc b/app/SabrehavenServer/data/npc/torence.npc new file mode 100644 index 0000000..2bbf4ea --- /dev/null +++ b/app/SabrehavenServer/data/npc/torence.npc @@ -0,0 +1,22 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Torence" +Outfit = (128,79-37-116-76-0) +Home = [32181,32829,7] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait please, %N!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +} diff --git a/app/SabrehavenServer/data/npc/tothdral.npc b/app/SabrehavenServer/data/npc/tothdral.npc new file mode 100644 index 0000000..0bb4771 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tothdral.npc @@ -0,0 +1,152 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tothdral.npc: Datenbank für den pyramidenmagier tothdral + +Name = "Tothdral" +Outfit = (65,0-0-0-0-0) +Home = [33148,32867,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "I knew this day will come. True Gods are here soon!",Idle +ADDRESS,"hi$",! -> "I knew this day will come. True Gods are here soon!",Idle +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Piligrim in flesh. False Gods were nothing but minor servants!",Idle +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Your incivility will not be tolerated much longer." + +"bye" -> "Strive for enlightenment, mourned mortal.", Idle +"farewell" -> * +"job" -> "I am the foremost astrologer and supreme magus of this city." +"name" -> "My name is Tothdral. They call me 'The Seeker Beyond the Grave'." +"time" -> "Time is your problem. It is no longer mine." +"temple" -> "Our temple is a centre of spirituality and learning. The temple and the serpentine tower work hand in hand for the good of all people, whether they are alive or undead." +"pharaoh" -> "The immortal pharaoh is our god and our example. He alone holds the secrets that will save us all from the greedy grasp of the false gods." +"oldpharaoh" -> "He has been given a chance to ascend. I am sure he will feel nothing but thankfulness for this divine son, our revered pharaoh." +"scarab" -> "If you know how to listen to them they will reveal ancient secrets to you." +"tibia" -> "This world is only a shadow of the worlds that have been. That was long ago, before the true gods fought each other in the godwars and the false gods rose to claim their heritage." +"carlin" -> "Those cities that bow to the false gods will fall prey to their treacherous greed sooner or later." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves should have learned their lessons, but these boneheaded fools still don't see there is only one way to escape the false gods' grasp." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are nothing but idle riff-raff. They embrace the vain amusements of physical pleasure. Eternal damnation will be their lot." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is mostly free from the servants of the false gods. Those who live here may hope to become worthy one day of the first steps towards ascension." +"darashia" -> "Their foolishness is great, but perhaps they still can be saved. If only they listened and accepted the next step to ascension." +"next","step" -> "Undeath alone will rid you of the temptations that blur your vision of the true path." +"daraman" -> "He was so close. ... and still he failed to draw the right conclusions." +"ankrahmun" -> "This city is as old as the sands that surround it, and it is built on previous settlements that date back even further in time. Perhaps only the wise scarabs know the full story of this place." +"pharaoh" -> "The pharaoh was the first to take the ultimate step. He braved death and claimed the godhood that was rightfully his." +"mortality" -> "Mortality is your curse. When you are worthy the burden of mortality will be taken from your shoulders." +"false", "gods" -> "The self-styled gods were nothing but minor servants of the true gods. They steal the soul of any mortal foolish enough to believe in them. They plan to use the stolen souls to ascend to true godhood." +"godswar" -> "This war brought about the end of the true universe. That which is left now is but a shadow of former glories, a bleak remainder of what once was. The true gods perished and their essence was dispersed throughout the remaining universe." +"great","suffering" -> "The great suffering is a phase of steady decline that will end eventually in void and emptiness unless some divine power such as our pharaoh will reverse it. Mend your ways and follow him! Perhaps you will be chosen to join him in his noble struggle." +"ascension" -> "The essence of the true gods is omnipresent in the universe. We all share this divine heritage, for every single one of us carries the divine spark inside him. This is the reason we all have a chance to ascend to godhood, too." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the trinity of existence, the three that are one. The Akh, the shell, the Rah, the source of power, and the Uthun, our consciousness, form this union." +"steal","souls" -> "The false gods harvest the souls of the dead to secure their stolen powers and status." +"Akh" -> "The Akh is a tool. As long as it is alive it is a burden and source of weakness, but if you ascend to undeath it becomes a useful tool that can be used to work towards greater ends." +"undead" -> "Undeath is an improvement. It is the gateway to goals that are nobler than eating, drinking or other fulfilments of trivial physical needs.." +"undeath" -> * +"Rah" -> "The Rah is what the ignorant might call the soul. But it's more than that. It is the divine spark in all of us, the source of energy that keeps us alive." +"uthun" -> "The Uthun is the part of the trinity that is easiest to form. It consists of our recollections of the past and of our thoughts. It is that which determines who we are in this world and it gives us guidance throughout our existence." +"mourn" -> "The dead mourn the living because they are weak and excluded from ascension." +"arena" -> "The arena is a suitable distraction for the Uthun of the mortals. It might even serve as a place for them to prove their worth. If they pass the test they may be freed of their mortal shells." +"palace" -> "The residence of the pharaoh should be worshipped just as the pharaoh is worshipped. Don't enter until you have business there." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Strive for enlightenment, mourned mortal.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." + +} diff --git a/app/SabrehavenServer/data/npc/trimegis.npc b/app/SabrehavenServer/data/npc/trimegis.npc new file mode 100644 index 0000000..fd68918 --- /dev/null +++ b/app/SabrehavenServer/data/npc/trimegis.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# trimegis.npc: Datenbank fuer den Hofmagier Trimegis in Thais + +Name = "Trimegis" +Outfit = (130,57-109-94-0-0) +Home = [32307,32185,5] +Radius = 1 + +Behaviour = { +ADDRESS,Sorcerer,male,"hello$",! -> " Greetings, brother %N." +ADDRESS,Sorcerer,male,"hi$",! -> * +ADDRESS,Sorcerer,female,"hello$",! -> " Greetings, sister %N." +ADDRESS,Sorcerer,female,"hi$",! -> * +ADDRESS,"hello$",! -> " Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"job" -> "I am the kings new courtmage and advisor in arcane matters." +"courtmage" -> "The last courtmage was killed by Ferumbras in one of his attacks." +"name" -> "I am commonly known as Trimegis." +"time" -> "Time does not matter in the end." +"king" -> "Our king frequently relies on my divinations and spells of protection." +"tibianus" -> * +"muriel" -> "He's quite good in magical theories, but lacks practice in the field." +"quentin" -> "Mixing up magic with religion can't do any good." +"lynda" -> "Gods are not as reliable as something you mastered on your own." +"harkath" -> "The king listens to the advice of this swordsman far too often." +"general" -> * +"army" -> "In the long run, it would pay off to focus all resources on a magicians corps, but the king is not convinced of that. Not yet." +"ferumbras" -> "He failed in his quest for power since he ultimately forfeited greater powers for a quick but limited powerboost by enslaving himself to some dark entities." +"sam" -> "A man as mundane as a rock." +"xodet" -> "He made the best he could of his limited abilities." +"frodo" -> "A bar is fine to distract the mundanes from doing something foolish." +"elane" -> "Paladins are another example that diversing one's resources between goods, mundane weapons, and magic does not make a good mixture." +"gregor" -> "Limited in his vision as all knights are." +"marvik" -> "Since intelligence can't be substituted by passion, all druids are nothing but hedgemages." +"bozo" -> "At least one mundane who knows his proper place." +"baxter" -> "Brawns but no brain." +"oswald" -> "A truly disgusting fellow." +"sherry" -> "I have certainly no business with such persons." +"donald" -> * +"mcronald" -> * +"lugri" -> "Another bogeyman. Who's afraid of someone who is that 'powerful' that he hides in some dirthole?" +"lungelen" -> "She has the 'know how', but sadly does not really know how to use it efficitenly." +"excalibug" -> "The only weapon I need is my magic." +"news" -> "I don't care about mundane gossip." +"pits","inferno" -> "Some dumb holes for adventurers seeking trouble." +"nightmare","pit"-> * +"wisdom" -> "Wisdom is only an excuse for the lack of consequence." +"sorcerer" -> "Many call themselves a sorcerer, but only a few truly understand what that means." +"power" -> "Power comes to those who have the intelligence to claim it." +"rune" -> "I have no need for runes anymore. Runes are tools for beginners." +"spell" -> "My spells are my personal secret." +} diff --git a/app/SabrehavenServer/data/npc/trisha.npc b/app/SabrehavenServer/data/npc/trisha.npc new file mode 100644 index 0000000..a47f726 --- /dev/null +++ b/app/SabrehavenServer/data/npc/trisha.npc @@ -0,0 +1,116 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# trisha.npc: Datenbank fuer die Ritterin Trisha + +Name = "Trisha" +Outfit = (142,94-67-38-95-1) +Home = [32348,31748,7] +Radius = 3 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Welcome back, knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,"hello$",! -> "Salutations, %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Be careful on your journeys." + +"bye",male -> "Don't hurt yourself with that weapon, little one.", Idle +"farewell",male -> * +"bye",female -> "Take care, sister.", Idle +"farewell",female -> * +"job" -> "I am the high knight of Carlin. I trained the the greatest heroines and even some males." +"name" -> "I am Trisha Ironfist." +"time" -> "It is time for a fight!" +"hero" -> "Heroes are knights and knights are heroes, of course." +"knight" -> "Knights are the true heroes of Tibia. Fame can only be earned by hand to hand combat. Brave women can join us, and we even accept suitable males now and then." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Knights, paladins, sorcerers, and druids." +"spellbook" -> "In a spellbook, all your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit the magicians shop in the south of Carlin." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Be careful on your journeys." + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"addon",QuestValue(17558)=7 -> "You are the true hero to wear the same spiky shoulder pad like I do." +"outfit",QuestValue(17558)=7 -> * +"addon" -> "Are you talking about my spiky shoulder pad? You can't buy one of these. They have to be earned." +"outfit" -> * + +"mission",QuestValue(17558)=0 -> "I'm not sure if you are enough of a hero to earn them. You could try, though. What do you think?", Topic=5 +"task",QuestValue(17558)=0 -> * +"earn",QuestValue(17558)=0 -> * +Topic=5,"yes" -> "Okay, who knows, maybe you have a chance. A really small one though. Listen up: ...", + "First, you have to prove your guts by bringing me 100 hardened bones. ...", + "Next, if you actually managed to collect that many, please complete a small task for our guild and bring us 100 turtle shells. ...", + "It is said that excellent shields can be created from these. ...", + "Alright, um, afterwards show me that you have fighting spirit. Any true hero needs plenty of that. ...", + "The last task is the hardest. You will need to bring me a claw from a mighty dragon king. ...", + "Did you understand everything I told you and are willing to handle this task?", Topic=6 +Topic=5 -> "Maybe next time." +Topic=6,"yes" -> "Excellent! Come back to me once you have collected 100 hardened bones.", SetQuestValue(17558,1), SetQuestValue(17594,1) +Topic=6 -> "Maybe next time." + +"hardened","bone",QuestValue(17558)=1 -> Type=5925, Amount=100, "How are you faring with your mission? Have you collected all 100 hardened bones?", Topic=7 +"mission",QuestValue(17558)=1 -> * +"task",QuestValue(17558)=1 -> * +Topic=7,"yes",Count(Type)>=Amount -> "I'm surprised. That's pretty good for a man. Now, bring us the 100 turtle shells.", Delete(Type), SetQuestValue(17558,2) +Topic=7,"yes" -> "You don't have that many." +Topic=7 -> "Maybe next time." + +"turtle","shell",QuestValue(17558)=2 -> Type=5899, Amount=100, "Did you get us 100 turtle shells so we can make new shields?", Topic=8 +"mission",QuestValue(17558)=2 -> * +"task",QuestValue(17558)=2 -> * +Topic=8,"yes",Count(Type)>=Amount,male -> "Well done - for a man. These shells are enough to build many strong new shields. Thank you! Now - show me fighting spirit.", Delete(Type), SetQuestValue(17558,3) +Topic=8,"yes",Count(Type)>=Amount,female -> "Well done, sister. These shells are enough to build many strong new shields. Thank you! Now - show me fighting spirit.", Delete(Type), SetQuestValue(17558,3) +Topic=8,"yes" -> "You don't have that many." +Topic=8 -> "Maybe next time." + +"fighting","spirit",QuestValue(17558)=3 -> Type=5884, Amount=1, "So, can you show me your fighting spirit?", Topic=9 +"mission",QuestValue(17558)=3 -> * +"task",QuestValue(17558)=3 -> * +Topic=9,"yes",Count(Type)>=Amount,male -> "Correct - pretty smart for a man. But the hardest task is yet to come: the claw from a lord among the dragon lords.", Delete(Type), SetQuestValue(17558,4) +Topic=9,"yes",Count(Type)>=Amount,female -> "Correct - you are smart sister. But the hardest task is yet to come: the claw from a lord among the dragon lords.", Delete(Type), SetQuestValue(17558,4) +Topic=9,"yes" -> "You don't have it." +Topic=9 -> "Maybe next time." + +"dragon","claw",QuestValue(17558)=4 -> Type=5919, Amount=1, "Have you actually managed to obtain the dragon claw I asked for?", Topic=10 +"mission",QuestValue(17558)=4 -> * +"task",QuestValue(17558)=4 -> * +Topic=10,"yes",Count(Type)>=Amount,male -> "You did it! I have seldom seen a man as courageous as you. I really have to say that you deserve to wear a spike. Go ask Cornelia to adorn your armour.", Delete(Type), SetQuestValue(17558,5) +Topic=10,"yes",Count(Type)>=Amount,female -> "You did it! I really have to say that you deserve to wear a spike. Go ask Cornelia to adorn your armour.", Delete(Type), SetQuestValue(17558,5) +Topic=10,"yes" -> "You don't have it." +Topic=10 -> "Maybe next time." + +"mission",QuestValue(17558)=5 -> "Go ask Cornelia to adorn your armour." +"task",QuestValue(17558)=5 -> * +"earn",QuestValue(17558)=5 -> * + +"mission",QuestValue(17558)=7 -> "Sorry but I don't have any tasks for you." +"task",QuestValue(17558)=7 -> * +} diff --git a/app/SabrehavenServer/data/npc/tristan.npc b/app/SabrehavenServer/data/npc/tristan.npc new file mode 100644 index 0000000..54b891d --- /dev/null +++ b/app/SabrehavenServer/data/npc/tristan.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Tristan" +Outfit = (131,79-11-28-20-1) +Home = [32390,32829,2] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "May enlightenment be your path.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/tulf.npc b/app/SabrehavenServer/data/npc/tulf.npc new file mode 100644 index 0000000..4a9db1b --- /dev/null +++ b/app/SabrehavenServer/data/npc/tulf.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tulf.npc: Datenbank für den Wachoffizier Tulf + +Name = "Tulf" +Outfit = (71,0-0-0-0-0) +Home = [32586,31928,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho! " +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence youngster!" +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, bye." + +"bye" -> "Yeah, bye.", Idle +"job" -> "I am the captain of the emperor's personal bodyguards." +"shop" -> * +"name" -> "My name is Tulf Beardweaver, son of Earth, from the Dragoneaters." +"time" -> "It's %T right now." +"help" -> "Sorry, I am on duty!" +"dwarfs" -> "If you go to a dwarfs' city, do as the dwarfs do." +"monster" -> "I doubt anyone can make it through our lines of defense." +"dungeon" -> "I despise the habit to challenge the prisoners of Dwarcatra or provoke the boys in the mines." +"mines" -> "The mines aren't meant for foreigners. The miners there have enough troubles." +"bodyguard" -> "We keep up law and order here, though the boys would rather need some practice like taking care for the trouble in the mines." +"trouble" -> "The mines are raided again and again by the bandits of the Horned Fox." +"horned","fox" -> "It's a renegade minotaur who has a hidden lair somewhere near our mines." +"lair" -> "The lair of the Horned Fox is surely well guarded and even better hidden." +} diff --git a/app/SabrehavenServer/data/npc/turvy.npc b/app/SabrehavenServer/data/npc/turvy.npc new file mode 100644 index 0000000..fbb1bd8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/turvy.npc @@ -0,0 +1,177 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Turvy.npc: Datenbank für shopkeeper Turvy (weaponry) + +Name = "Turvy" +Outfit = (139,78-52-64-115-0) +Home = [32414,32177,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Hello, dear %N. Can I be of any assistance?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "I'm so sorry, but I simply must talk to this customer first. Please wait a moment.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Do come again!" + +"bye" -> "Good bye. Do come again!", Idle +"farewell" -> * +"how","are","you" -> "I'm just fine and dandy, thank you for asking." +"job" -> "It is an absolute honour to provide weaponry and armor to the courageous adventurers of Thais, just so long as you have the gold to pay for it." +"name" -> "Some call me Turvy. Actually, everybody calls me Turvy." +"time" -> "It is nearly time for my afternoon nap, so please hurry!" +"help" -> "Help to self-help - that is my motto." +"monster" -> "There is a monster here? HERE?! Time to double the prices!" +"dungeon" -> "If you want to see dungeons go and insult the guards. On second thoughts - don't do that." +"sewer" -> "It is very effective, but attracts almost as many wannabe heroes as it does rats." +"assistant" -> "I am not a mere assistant! I have a job of great responsibility! But mostly I keep annoying personages away from my boss." +"annoying" -> "Oh gosh - I could tell you some stories. But I won't." +"thank","you" -> "So polite . . . bless you!" +"god" -> "The Gods of Tibia play games with the fate of Tibians - but they haven't bothered to read the instructions." +"king" -> "Ah, yes, yes, hail to King Tibianus! May he in his infinite wisdom reduce my taxes... and so on..." +"sam" -> "A simple shopkeeper, who was last in the queue when they were handing out intelligence." +"benjamin" -> "Ah, such a shame about poor Benjamin. Lost it a bit after receiving one too many blows to the head." +"gorn" -> "He does a good line in second-rate scrolls for first-rate prices." +"quentin" -> "You can't tech an old monk new tricks. He is stubborn to the extreme and overly concerned about Thais. He should care more about his gods and less about that king." +"bozo" -> "Bozo - such a tragic story. If only I could remember it." +"rumour" -> "You know a rumour? Well then - don't keep it to yourself.", Topic=3 +"gossip" -> * +"news" -> * +"weapon" -> "The word on the street is that Sam does not forge all his weapons himself, but buys them from his cousin, who is married to a cyclops." +"magic" -> "Magic is a thing of the past. Why bother with a colourful bit of rock and a few fancy words when you can have a foot of razor-sharp steel in your hand?!" +"power" -> "There are people who talk about a rebellion against King Tibianus." +"rebellion" -> "Well, Venore is richer than Thais, and some people want to live in a democracy free from an oppressive tyrant - I mean monarch. I'm not one of them." +"spell" -> "Spells - dodgy mumbo jumbo if you ask me. A sword never backfires on its user!" +"elane" -> "A true tragedy - she has lost so many husbands in such unusual circumstances." +"venore" -> "Ah... Venore - a wonderful city! Full of culture! So many friendly faces! So unlike Thais!" +"thais" -> "Thais is OK - I suppose. Not as nice as Venore, but good for business." +"carlin" -> "Those women really know how to run things - look at how well the trade is going there!" +"kazordoon" -> "You need to shrink before you go there - they say the dwarves aren't too keen on sharing their mountain with us Tibians." +"dwarves" -> "I don't know much about them - there are some civilised dwarves, of course, but I can never tell whether they are male or female." +"Ab'dendriel" -> "Aah... a beautiful leafy city. Shame about the elves." +"elves" -> "Elves are good with a bow and arrow, or so I am told. Shame that they are no good at peace-making." +"chester" -> "I have never heard any rumours concerning him, isn't that odd?" +"ardua" -> "Well - she isn't really my kind of person. Please don't mention her name again." +"partos" -> "Some thief they caught for all I know." +"gamel" -> "Some sinister guy that is. He's not allowed to enter that markethall and thats for a good reason." +"gamon" -> "Shhh! He's a spy! He watches us all the time! Just keep smiling and he'll go away!" +"quest" -> "Hmmm yes. I think Topsy might have something for you." +Topic=3 -> "Go on! I can't wait to hear more!" + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/tyrias.npc b/app/SabrehavenServer/data/npc/tyrias.npc new file mode 100644 index 0000000..54b5e16 --- /dev/null +++ b/app/SabrehavenServer/data/npc/tyrias.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Tyrias" +Outfit = (133,57-113-95-113-0) +Home = [32316,32823,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, young %N! If you are heavily wounded or poisoned, I can heal you for free." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking that bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "May the gods bless you, %N!", Idle +"farewell" -> * +"job" -> "Job? I have no job. I just live for the gods of Tibia." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +"time" -> "Now, it is %T." + +"stake",QuestValue(17576)=10,Count(5941)<=0 -> "You don't even have that strange stake with you." +"stake",QuestValue(17576)=10 -> Type=5941, Amount=1, "Brewster sent me a strange message about some strange hocus-pocus. I think it's nonsense, but since you have come that far, I'll play along. Are you ready?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Your mind shall be a vessel for joy, light and wisdom' - uh, wow, something happened. Well, I guess that's it, but next time if you need some mumbo jumbo rather go to Chondur.", SetQuestValue(17576,11), Delete(Type), Create(5942) +Topic=10,"yes" -> "You don't even have that strange stake with you." +Topic=10 -> "No problem, I have other things to do." +"stake",QuestValue(17576)>10 -> "I won't do that rubbish again. Go pester Chondur with your hocus-pocus." +"stake" -> "A blessed stake? I don't believe in things like that. If anyone does, it's probably old Quentin." +} diff --git a/app/SabrehavenServer/data/npc/ubaid.npc b/app/SabrehavenServer/data/npc/ubaid.npc new file mode 100644 index 0000000..b99c44a --- /dev/null +++ b/app/SabrehavenServer/data/npc/ubaid.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ubaid.npc: Datenbank für den Efreetpförtner Ubaid + +Name = "Ubaid" +Outfit = (51,0-0-0-0-0) +Home = [33046,32622,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Still alive, %N?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,"hello$",QuestValue(278)>0,! -> "Shove off, little one! Humans are not welcome here, %N!", Idle +ADDRESS,"hi$",QuestValue(278)>0,! -> * +ADDRESS,"greetings$",QuestValue(278)>0,! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,"djanni'hah$",QuestValue(278)>0,! -> "What? You know the word, %N? All right then - I won't kill you. At least, not now." +ADDRESS,"djanni'hah$",! -> "Hmmm? Is this human %N trying to say something? I don't think so.", Idle +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=3,! -> "%N again. You have to wait.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,"hello$",QuestValue(278)>0,! -> "Shove off, little one! You are not welcome here, %N!" +BUSY,"hi$",QuestValue(278)>0,! -> * +BUSY,"greetings$",QuestValue(278)>0,! -> * +BUSY,"hello$",! -> * +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,"djanni'hah$",QuestValue(278)>0,! -> "Oh no! More of you humans! I'm busy, %N, so you better hold your tongue until I have time for you.", Queue +BUSY,"djanni'hah$",! -> "Hmmm? Is this human trying to say something? I don't think so." +BUSY,! -> NOP + +VANISH,! -> "Why did you waste my time?" + +"bye" -> "Hail King Malor! See you on the battlefield, human worm.", Idle +"farewell" -> * +"name" -> "My name is Ubaid. Why do you want to know that, human? Hmm... suspicious." +"ubaid" -> "That is my name. I don't like it when a human pronounces it." +"job" -> "Well, what do you think? I keep watch around here to make sure people like you don't enter." + +"gate" -> "Only the mighty Efreet, the true djinn of Tibia, may enter Mal'ouquah! ...", + "All Marids and little worms like yourself should leave now or something bad may happen. Am I right?", Topic=1 +"pass" -> * +"door" -> * +"enter" -> * +"join" -> * +"follow" -> * + +"gate",QuestValue(278)=3,! -> "You already pledged loyalty to king Malor!" +"pass",QuestValue(278)=3,! -> * +"door",QuestValue(278)=3,! -> * +"enter",QuestValue(278)=3,! -> * +"join",QuestValue(278)=3,! -> * +"follow",QuestValue(278)=3,! -> * + + + +Topic=1,"no",QuestValue(278)=2,! -> "Who do you think you are? A Marid? Shove off, moron.", Idle +Topic=1,"no",! -> "Of cour... Huh!? No!? I can't believe it! ...", + "You... you got some nerves... Hmm. ...", + "Maybe we have some use for someone like you. Would you be interested in working for us. Helping to fight the Marid?", Topic=2 + +Topic=2,"yes",! -> "So you pledge loyalty to king Malor and you are willing to never ever set foot on Marids' territory, unless you want to kill them? Yes?", Topic=3 +Topic=3,"yes",! -> "Well then - welcome to Mal'ouquah. ...", + "Go now to general Baa'leal and don't forget to greet him correctly! ...", + "And don't touch anything!", SetQuestValue(278,3), Idle +Topic=1,! -> "Of course. Then don't waste my time and shove off.", Idle +Topic=2,! -> * +Topic=3,! -> * + +"king" -> "Well, Malor is not officially king of all djinn yet, but now our beloved leader is back that is a mere formality." +"malor" -> * +"djinn" -> "We are a race of rulers and dominators! Or at least we, the Efreet, are!" +"efreet" -> "The Efreet are the true djinn! Those namby-pamby milksops who call themselves the Marid and still follow Gabel, no longer deserve the honour to call themselves djinn." +"marid" -> "Marid? When? Where? How many? RED ALERT! ...", + "Hey! There is nobody here! Don't do that again, human!" +"gabel" -> "I used to serve under Gabel, but he is no longer my king. If that wacky wimp should ever come here to Mal'ouquah I will personally... you know... turn him away. Yes!" +"mal'ouquah" -> "This place is our home, and as long as I'm here no meddler will trespass!" +"ashta'daramai" -> "The Marids' hideout, isn't it? I have never been there, but I am sure one day I will. That will be the day Ashta'daramai falls into our hands!" +"human" -> "You are an inferior race of feeble, scheming jerks. No offence." +"zathroth" -> "Zathroth is our father! Of course, the son always has a right to hate his father, right?" +"tibia" -> "This world is ours by right, and we will take it!" +"daraman" -> "How dare you utter that name in my presence, human. Don't strain my patience, worm! You may know the secret word, but... who knows... it is always possible that your head is torn off in some terrible accident." +"darashia" -> "A human settlement to the west? I have not been there yet, but when I do I'm sure I will be remembered." +"scarab" -> "They make good pets if you know how to keep them. Did you know they just adore human flesh?" +"edron" -> "Isn't that the name of some petty human settlement?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I know that damn city well. A long time ago we laid siege to it. ...", + "We would have taken it, but those traitorous humans allowed a Marid garrison to entrench itself there, and we never managed to throw them out. Cowards and traitors the lot of them." +"pharaoh" -> "They say Ankrahmun is now ruled by a crazy pharaoh who wants to tell his whole people into drooling undead. That's humans. Sickos and weirdos the lot of them." +"palace" -> "One day we will sack that place and burn it to the ground." +"temple" -> * +"ascension" -> "I think I've heard that term before. Has to do with that weirdo pharaoh, right?" +"rah" -> "Are you drunk?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "This mountain range is our home. Too bad we have to share it with the Marid. That will change, though. And pretty soon, believe me." +"kha'labal" -> "I like the desert. Just ruins and sand. And no human scum to be seen. The Kha'labal is a foretaste of what the djinn will do to the whole of Tibia!" +"war" -> "I don't know why I am stuck here! I should be at the front, killing Marid and humans. Well, perhaps I will kill you..." +"baa'leal" -> "General Baa'leal is our commander-in-chief of all his minions. He is as tough as an ancient scarab's buttocks and as sly a sand weasel." +"alesar" -> "I am not used to the sight of blueskins here in Mal'ouquah, and it does not make me too happy to see one. I am keeping an eye on this guy, and if I should ever find that he is playing games with us I will personally break his neck!" +"fa'hradin" -> "The old wizard is dangerous, but he will get what he deserves sooner or later." +"lamp" -> "I am not taking a nap! I am on duty!" +} diff --git a/app/SabrehavenServer/data/npc/ukea.npc b/app/SabrehavenServer/data/npc/ukea.npc new file mode 100644 index 0000000..f098ffa --- /dev/null +++ b/app/SabrehavenServer/data/npc/ukea.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ukea.npc: Möbelverkäuferin Ukea in Ab'Dendriel + +Name = "Ukea" +Outfit = (144,78-58-64-58-0) +Home = [32651,31665,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N. Welcome to Ab'Dendriel Furniture Store." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"name" -> "My name is Ukea. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/ulrik.npc b/app/SabrehavenServer/data/npc/ulrik.npc new file mode 100644 index 0000000..0184bcd --- /dev/null +++ b/app/SabrehavenServer/data/npc/ulrik.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ulrik.npc: Datenbank für den Schmied Ulrik in Greenshore + +Name = "Ulrik" +Outfit = (131,60-70-97-95-0) +Home = [32282,32056,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye for now." + +"bye" -> "Good bye for now.", Idle +"job" -> "I am a smith. Do you need anything I make?" +"shop" -> * +"name" -> "My name is Ulrik." +"time" -> "It is %T." +"king" -> "What can a simple man as me say about a king?" +"tibianus" -> * +"ferumbras" -> "Oh, I only heard frigthening tales about him." +"excalibug" -> "Every now and then an adventurer like you comes here looking for it." +"news" -> "We live too far away from Thais to hear anything that truly is 'new'." +"help" -> "Sorry, I have no clue how to help you." +"monster" -> "Most monsters live far away, so you can feel safe here in Greenshore." +"dungeon" -> "They say north of Thais is a deep dungeon." +"thanks" -> "You are welcome." +"thank","you" -> * + +"buy" -> "What do you need? I sell weapons and armor." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are weapons and armor." +"weapon" -> "I have longswords, battle hammers, and battle axes. What do you want?" +"armor" -> "I have scale armor, soldier helmets, and steel shields. What do you want?" +"sell" -> "I'm sorry, but I don't buy used equipment." + +"longsword" -> Type=3285, Amount=1, Price=160, "Do you want to buy a longsword for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"scale","armor" -> Type=3377, Amount=1, Price=260, "Do you want to buy a scale armor for %P gold?", Topic=1 +"soldier","helmet"-> Type=3375, Amount=1, Price=110, "Do you want to buy a soldier helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 + +%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=160*%1, "Do you want to buy %A longswords for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"scale","armor" -> Type=3377, Amount=%1, Price=260*%1, "Do you want to buy %A scale armors for %P gold?", Topic=1 +%1,1<%1,"soldier","helmet"-> Type=3375, Amount=%1, Price=110*%1, "Do you want to buy %A soldier helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/app/SabrehavenServer/data/npc/umar.npc b/app/SabrehavenServer/data/npc/umar.npc new file mode 100644 index 0000000..d93d9ac --- /dev/null +++ b/app/SabrehavenServer/data/npc/umar.npc @@ -0,0 +1,134 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# umar.npc: Datenbank für den Maridpförtner Umar + +Name = "Umar" +Outfit = (80,0-0-0-0-0) +Home = [33102,32531,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "%N! How's it going these days?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,"hello$",QuestValue(278)>0,! -> "Whoa! A human! This is no place for you, %N. ...", + "Go and play somewhere else.", Idle +ADDRESS,"hi$",QuestValue(278)>0,! -> * +ADDRESS,"greetings$",QuestValue(278)>0,! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,"djanni'hah$",QuestValue(278)>0,! -> "Whoa? You know the word! Amazing, %N! ...", + "I should go and tell Fa'hradin. ...", + "Well. Why are you here anyway, %N?" +ADDRESS,"djanni'hah$",! -> "Hahahaha! ...", + "%N, that almost sounded like the word of greeting. Humans - cute they are!", Idle +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=2,! -> "Hey %N! Please wait a second!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,"hello$",QuestValue(278)>0,! -> "Another human! This is no place for you, %N. ...", + "Go and play somewhere else." +BUSY,"hi$",QuestValue(278)>0,! -> * +BUSY,"greetings$",QuestValue(278)>0,! -> * +BUSY,"hello$",! -> * +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,"djanni'hah$",QuestValue(278)>0,! -> "Yikes! Another human? Where did you come from? Well, ehm - if you could please wait a second, %N?", Queue +BUSY,"djanni'hah$",! -> "That almost sounded like the word of greeting, %N. Humans - cute they are!" +BUSY,! -> NOP + +VANISH,! -> "Back to work." + +"bye" -> "Aaaa -tention!.", Idle +"farewell" -> * +"name" -> "I am Umar. Pleased to meet you!" +"job" -> "I am the gatekeeper of Ashta'daramai. That's what Gabel told me to do. You know - keeping the courtyard clean, getting rid of salesmen, keeping Efreet scum out... that kind of thing. But in my spare time I work as a part-time philosopher." +"philosopher" -> "Yes. Comes with the job. You see - here I am, sitting on the same chair all day and staring at the same blank wall. So what happens is that my mind starts wandering. And, you know, I start thinking. You know - about all kinds of things." +"things" -> "Yes. About the world and the gods and all that. And about girls. Yes, about girls, mostly." +"girls" -> "You did not know there are female djinns, did you? That's because they are quite rare. They are the greatest treasures of our race, and we guard them jealously." + +"gate" -> "On the orders of king Gabel, who technically is no real king, only Marid may enter Ashta'daramai." +"pass" -> "If you want to enter our fortress you have to become one of us and fight the Efreet. ...", + "So, are you willing to do so?", Topic=1 +"door" -> * +"enter" -> * +"join" -> * +"follow" -> * + +"gate",QuestValue(278)=2,! -> "You allready have the permission to enter Ashta'daramai." +"pass",QuestValue(278)=2,! -> * +"door",QuestValue(278)=2,! -> * +"enter",QuestValue(278)=2,! -> * +"join",QuestValue(278)=2,! -> * +"follow",QuestValue(278)=2,! -> * + + +Topic=1,"yes",QuestValue(278)=3,! -> "I don't believe you! You better go now.", Idle +Topic=1,"yes",! -> "Are you sure? You pledge loyalty to king Gabel, who is... you know. And you are willing to never ever set foot on Efreets' territory, unless you want to kill them? Yes?", Topic=2 +Topic=2,"yes",! -> "Oh. Ok. Welcome then. You may pass. ...", + "And don't forget to kill some Efreets, now and then.", SetQuestValue(278,2) +Topic=1,! -> "This isn't your war anyway, human." +Topic=2,! -> * + +"gabel" -> "He is our king and leader. Well, he isn't a king, you know. I mean, from a technical point of view he is, but he does not wear a crown or anything, and he says he isn't one, so even though he is one he isn't. Right?" +"king" -> "Okay, let's do this again. Gabel says he isn't a king, but he acts like one, which makes him one anyway - right? ...", + "But you know, he does not really act like one, either. I mean, he does give us orders and all that, and we obey sure enough, but it's not that we have to, I mean, technically speaking. ...", + "I mean - I don't know what would happen if anybody would not follow his orders for a change. After all he is no longer a king, right? ...", + "But then I don't want to be the first one to find out what happens if you disobey, so I always do as I'm told. ...", + "Which means I do not really know whether or not he is king. Things were so much easier when Gabel still said he was king. Matters were so much clearer then." +"djinn" -> "Well, I am a djinn, but only as far as my physical aspect is concerned. As far as my way of thinking is concerned I think I might actually be somebody else. You now - not even a djinn. In fact, I think I might be a dwarf." +"dwarf" -> "Yes. Consider this: Dwarves live in the mountains. So do I. And just like dwarves I really like gold. But most of all, dwarves like beer. ...", + "Isn't that amazing? I think that is more than a coincidence. You know - perhaps I am a reincarnated dwarf or something. You never know." +"human" -> "See. That's another problem. In the past, it was us against you - djinn against humans. But one day this guy came along, and all of a sudden things were so much more complicated. ...", + "All of a sudden there was good djinn and evil djinn, and good humans and evil humans. Everything got so damn complicated. ...", + "All of a sudden we did not know who to trust and who to fight. Should we join the evil djnn and battle all humans? ...", + "Or was it smarter to ally with the good humans and to battle the bad djinn? ...", + "Perhaps we should join nobody and fight the bad humans? So many choices." +"efreet" -> "I have thought long and hard about this and I have come to the conclusion that all Efreet are scum." +"ashta'daramai" -> "This place is the Marids' safe haven. No enemy has ever managed to take this fortress by assault, and we will see to it that it stays this way." +"mal'ouquah" -> "That is the Efreets fortress. I have never seen it, but I'm sure it can't compare to this place." +"marid" -> "That's us. I suppose we are the good guys in this war. Although good is relative, of course. So let's say, we are relatively good. Depends on the point of view, really." +"war" -> "We had thought the war was over for good when Malor was finally imprisoned. That little creep is as obstinate as... as... well, as a really obstinate djinn." +"malor" -> "Malor is evil. I mean - really evil. Things used to be much better when he was still locked away in that lamp." +"tibia" -> "Tibia is a beautiful world. Not that I see much of it, staring at this wall night and day." +"world" -> * +"gods" -> "I have not made my mind up what to think about the gods yet. I am still struggling with Daraman's teachings." +"daraman" -> "Daraman has changed our lives. I mean, we were not stupid or anything before he came, but still it was different. Fa'hradin says that while Zathroth made us intelligent, Daraman made us think." +"zathroth" -> "Zathroth is not very popular among the djinn because it is said that he abandoned us even though he was our creator. Legend has it that we failed to meet his expectations. ...", + "Fa'hradin once said that all djinn are oedipally traumatised because of this, but I have no idea what he is talking about." + +"darashia" -> "They say Darashia is a beautiful human city somewhere to the north. I would really love to see it, but I can't abandon my post." +"edron" -> "I understand the humans have founded some beautiful cities. I would like to see them, but as long as I have to stay here that won't happen. Which means I will not go anywhere as long as the war goes on." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I was there, long ago. We had a garrison based in Ankrahmun during the early phases of the war. That was before the whole plains of the Kha'labal were set on fire." + +"scarab" -> "I don't care whether or not they are special animals. None of that creeping vermin will enter Ashta'daramai as long as I am here!" +"pharaoh" -> "They say the new pharaoh is mad!" +"palace" -> "I remember the palace. It was a beautiful place. Ah... those were happy days." +"temple" -> "In these heretic times the priests at Ankrahmun's temple are devoted to the teachings of that pompous pharaoh." +"ascension" -> "Apparently that is what the followers of the pharaoh are striving for. It has to do with that pharaoh's teachings." +"rah" -> "That's just some heretic drivel. Don't ask me about it." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "When I look up from my wall, what do I see? Huge, forbidding mountains! No wonder I feel claustrophobic." +"kha'labal" -> "Ah yes - the desert. I still remember how beautiful that land was back in the days before the war. ...", + "A land full of song and bliss it was - a veritable paradise. Fa'hradin once said its destruction was a supreme example of the transitoriness of all things mortal. ...", + "I am not sure I agree because I don't know what 'transitoriness' means." + +"djema" -> "You know her? She's a human like you. I like her lots because she often comes down here for a chat. Nobody else around here does that." +"bo'ques" -> "That fat old cook. I like his food, but I find him a bit boring. Food and cooking is all he ever talks about." +"alesar" -> "Ah. That guy. He was one of us, a Marid, but he left long ago. I have no idea why. Rumours and hearsay is all I ever get." +"fa'hradin" -> "Fa'hradin is a powerful wizard and the smartest djinn I know. I love talking to him because there is so much he can teach me, but he rarely has time for me." +"lamp" -> "Djinns sleep in lamps. I don't know what is so special about that." +"melchior" -> "That name rings a bell. A trader from Ankrahmun... or was it Darashia? I remember him and his mule. He used to come up here quite often to do business with Haroun. ...", + "Lately I haven't seen him around, though. I think last time he came here was about 20 years ago." +} diff --git a/app/SabrehavenServer/data/npc/urkalio.npc b/app/SabrehavenServer/data/npc/urkalio.npc new file mode 100644 index 0000000..e475bd7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/urkalio.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# urkalio.npc: Datenbank für den Wirt Urkalio + +Name = "Urkalio" +Outfit = (128,39-40-118-76-0) +Home = [32913,32081,10] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the pits of the Hard Rock Tavern, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, some patience, %N. You'll be served soon enough.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "We're some hasty one, aren't we?" + +"bye" -> "Have a good fight, %N.", Idle +"job" -> "I am responsible for the Hard Rock Pits Tavern." +"tavern" -> * +"upper","part" -> "If you can't stand some blood and battlecrys, just go upstairs." +"pits" -> "Choose your enemies with care." +"asrak" -> "He's the best. To be the man, you'll have to beat the minotaur, so to say. Not that you could provoke him to a fight at all." +"name" -> "I am Urkalio." +"maria" -> "She's kind of my boss." +"time" -> "No clue, it's equally dark down here at any time." +"king" -> "Down here everyone is king as far as where his weapons reach." +"tibianus" -> * +"army" -> "A shame they don't visit our pits for some training." +"ferumbras" -> "THAT would be some attraction down here." +"excalibug" -> "I would love to see that weapon in a fight." +"thais" -> "Such a boring city. I wonder why anyone would live there." +"tibia" -> "Sooner or later everyone comes here, so why bother to travel." +"carlin" -> "I don't care about their 'independence war'." +"amazon" -> "Some came here to challenge the local champions. I can't say I was impressed by their skills. However, they took a few heads as trophies." +"news" -> "I bet you want to hear about those swampelves from Shadowthorn." +"rumors" -> * +"swampelves" -> "If they want a fight that bad, why don't they just come here and fight in the pits?" + +"buy" -> "I sell food and drinks for the hungry and thirsty." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "I have cookies, bread, cheese, ham, and meat." +"drink" -> "So do you want beer, wine, lemonade, or ... uhm ... water?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, " So you want to buy a mug of ... water for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, " So you want to buy %A mugs of ... water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I guess you run out of money. Leave before I run out of patience.", Idle +Topic=1 -> "Don't waste my time, kid." +} diff --git a/app/SabrehavenServer/data/npc/ursula.npc b/app/SabrehavenServer/data/npc/ursula.npc new file mode 100644 index 0000000..0b7fbbb --- /dev/null +++ b/app/SabrehavenServer/data/npc/ursula.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ursula.npc: Datenbank für die Zauberlehrerin Ursula + +Name = "Ursula" +Outfit = (54,0-0-0-0-0) +Home = [33268,31849,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Howdy %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Gimme one more minute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye", Idle +"job" -> "I teach some basic spells." +"name" -> "I am Ursula." +"time" -> "Sorry, I don't own a watch." +"king" -> "The king spent a lot of money for our library." +"tibianus" -> * +"army" -> "I would prefer an army of spellcasters, but they are ok." +"ferumbras" -> "Ah, come on, he can't be that poweful and evil as all say." +"excalibug" -> "A myth born out of some knights' inferiority complex." +"thais" -> "Ah, yes, I remember my time there, old Muriel teaching me the basics." +"tibia" -> "Isn't it a fine world we live in." +"carlin" -> "They should have been more careful with this town, before they lost it." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I heard about things you never would believe. Please come back when I have more time to chat." +"rumors" -> * + +"spellbook" -> "I'm sorry, but I don't have one. Ask Thomas in the west tower about that." +"spell" -> "I have 'Conjure Bolt', 'Animate Dead', 'Envenom', 'Heal Friend', 'Desintegrate', 'Poison Bomb', and 'Strong Haste'. Which one do you want to learn?" + +"conjure","bolt",Paladin -> String="Conjure Bolt", Price=750, "Do you want to learn the spell 'Conjure Bolt' for %P gold?", Topic=1 +"conjure","bolt" -> "I'm sorry, but this spell is only for paladins." +"animate","dead",Sorcerer -> String="Animate Dead", Price=1200, "Do you want to learn the spell 'Animate Dead' for %P gold?", Topic=1 +"animate","dead",Druid -> * +"animate","dead" -> "I'm sorry, but this spell is only for druids and sorcerers." +"envenom",Druid -> String="Envenom", Price=1000, "Do you want to learn the spell 'Envenom' for %P gold?", Topic=1 +"envenom" -> "I'm sorry, but this spell is only for druids." +"heal","friend",Druid -> String="Heal Friend", Price=800, "Do you want to learn the spell 'Heal Friend' for %P gold?", Topic=1 +"heal","friend" -> "I'm sorry, but this spell is only for druids." +"desintegrate",Paladin -> String="Desintegrate", Price=900, "Do you want to learn the spell 'Desintegrate' for %P gold?", Topic=1 +"desintegrate",Sorcerer -> * +"desintegrate",Druid -> * +"desintegrate" -> "I'm sorry, but this spell is only for paladins, sorcerers, and druids." +"strong","haste",Sorcerer -> String="Strong Haste", Price=1300, "Do you want to learn the spell 'Strong Haste' for %P gold?", Topic=1 +"strong","haste",Druid -> * +"strong","haste" -> "I'm sorry, but this spell is only for druids and sorcerers." +"poison","bomb",Druid -> String="Poisonbomb", Price=1000, "Do you want to learn the spell 'Poison Bomb' for %P gold?", Topic=1 +"poison","bomb" -> "I'm sorry, but this spell is only for druids." + +Topic=1,"yes",SpellKnown(String)=1 -> "I'm sorry, but you already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "I'm sorry, but you need level %A to learn this spell." +Topic=1,"yes",CountMoney "I'm sorry, but you don't have enough gold to pay for it." +Topic=1,"yes" -> "Congratulations. From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "As you wish." +} diff --git a/app/SabrehavenServer/data/npc/uso.npc b/app/SabrehavenServer/data/npc/uso.npc new file mode 100644 index 0000000..423d1a3 --- /dev/null +++ b/app/SabrehavenServer/data/npc/uso.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uso.npc: Datenbank für den Zwerg Uso der auch Knights trainiert + +Name = "Uso" +Outfit = (160,79-39-77-115-0) +Home = [32580,32756,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Hey, just wait, ok?", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "So long." + +"bye" -> "So long.", Idle +"farewell" -> * +"job" -> "I am the head of a little mining operation here and I train knights in my spare time to prevent my old body from rusting." +"name" -> "I am called Uso Oredigger, son of the flame from the dragoneater fellowship." +"time" -> "Time is of little importance at this forsaken place." +"temple" -> "There is a somewhat provisional temple of the humans here." +"king" -> "Human kings are not of my concern." +"venore" -> "To me one human is like the other. I don't care what city they are from." +"thais" -> * +"carlin" -> * +"edron" -> * +"jungle" -> "All those trees and the heat are horrible. If it wasn't for the gold, we wouldn't be here, jawoll." +"gold" -> "There IS gold out there. And a lot of it. I can feel it in my old bones." + +"tibia" -> "The world is big but the random places where gold and treasures can be found are the ones that are of importance, jawoll." + +"kazordoon" -> "We dwarves call it our home since the dawn of time. I miss Kazordoon a lot, but gold is of more importance. After I have made a fortune here I will return home and might settle down." +"dwarves" -> "We are a proud race. Dwarves are strong and fearless. Even in this forsaken jungle we can survive, jawoll." +"dwarfs" -> * +"ab'dendriel" -> "That is no city but just a bunch of trees." +"elves" -> "I wonder why we see so few elves over here. Those tree people should love this cursed jungle. But even they stay away from here. It makes me wonder why." +"elfs" -> * +"darama" -> "I wonder which part of Darama is worst, that jungle or that desert. This whole continent is a dwarf's nightmare." +"ankrahmun" -> "Another monument of the madness of the human race. Worship of death or undeath or whatever ... I wonder how often I must be slammed on my head to think of such a crazy idea." +"ferumbras" -> "He would be at least a diversion from all those crawling and flying insects." +"excalibug" -> "If it ever had been in this area, it surely would be rusted through by now." + +"apes" -> "It wouldn't surprise me if they were only elves disguised with furs." +"lizard" -> "They say the lizards had a lot of gold in their ancient cities. Perhaps one day we will go there and look for it." +"dworcs" -> "I recently broke the nose of a guy who dared to claim that those headhunters are related to dwarves." + + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "So long.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/app/SabrehavenServer/data/npc/ustan.npc b/app/SabrehavenServer/data/npc/ustan.npc new file mode 100644 index 0000000..5323905 --- /dev/null +++ b/app/SabrehavenServer/data/npc/ustan.npc @@ -0,0 +1,170 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ustan.npc: Datenbank für den Druidenlehrer Ustan + +Name = "Ustan" +Outfit = (144,0-24-13-76-1) +Home = [32580,32757,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Crunor's blessing, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Just some patience please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, traveller.", Idle +"farewell" -> * +"job" -> "I am a druid, an explorer and a part-time teacher." +"teacher" -> "Well, my studies of the local plants and animals are not cheap and I have to earn money somehow. Therefore I teach spells to other druids for a small fee." +"name" -> "I am Ustan." +"time" -> "Those modern watches never caught my interest." +"king" -> "As a commoner I know little about our nobility." +"venore" -> "A rich city with mighty trade barons that have their hands in almost every business from the furthest north to the deepest south." +"thais" -> "I usually shun big cities and Thais is the biggest one of them." +"carlin" -> "I studied in Carlin for a while and found its inhabitants most pleasant." +"edron" -> "I was never there. Perhaps after finishing my studies here I might travel there." +"jungle" -> "The jungle is fascinating and vibrant of life. There is so much to see, to learn and to discover, it's just overwhelming." + +"tibia" -> "It's a world full of wonders, isn't it?" + +"kazordoon" -> "A city of stone, deep in the interior of the earth." +"dwarves" -> "Dwarves are of a different physique than humans. Actually, this is worth to become an own field of research. I wish I'd find the time for such studies, but the jungle has priority." +"dwarfs" -> * +"ab'dendriel" -> "A marvellous city. I travelled there often while I was studying in Carlin." +"elves" -> "Elves have a different view of the world. The wars of the past hurt their collective soul, but time might heal their pain, and maybe one day they will find their way back to the gods." +"elfe" -> * +"darama" -> "The continent is of fascinating diversity. The jungle, the mountains and the desert are seperate regions but united in one marvellous continent. To understand the unity in this, is to understand the world." +"darashia" -> "It's an unremarkable settlement with people following a strange philosophy which I know little about." +"ankrahmun" -> "Those mad cultists mock life and Crunor's blessing, and their undead leaders are even worse. If I'd be a warrior instead of a scientist, I would fight the evil." +"ferumbras" -> "I heard he is some powerful sorcerer" +"excalibug" -> "I think the best weapon anyone could wield is the own mind." +"apes" -> "I am sure we could reach an agreement of some kind with the ape people. They are for sure intelligent and might listen to reasonable arguments." +"lizard" -> "They are fascinating and alien creatures. I wonder what kind of secrets they know about and what could be discovered about them." +"dworcs" -> "Given that the orcish race is able to reproduce itself with all kinds of different humanoid creatures, it is indeed a probability that the dworcs are some crossbreed as one could assume from their name." + +"cough", "syrup" -> "I had some cough syrup a while ago. It was stolen in an ape raid. I fear if you want more cough syrup you will have to buy it in the druids guild in carlin." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Good bye, traveller.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"wolf","tooth","chain" -> Type=3012, Amount=1, Price=100, "Do you want to sell a wolf tooth chain for %P gold?", Topic=4 +"wolf","paw" -> Type=5897, Amount=1, Price=70, "Do you want to sell a wolf paw for %P gold?", Topic=4 +"bear","paw" -> Type=5896, Amount=1, Price=100, "Do you want to sell a bear paw for %P gold?", Topic=4 + +%1,1<%1,"wolf","tooth","chain" -> Type=3012, Amount=%1, Price=100*%1, "Do you want to sell %A wolf tooth chains for %P gold?", Topic=4 +%1,1<%1,"wolf","paw" -> Type=5897, Amount=%1, Price=70*%1, "Do you want to sell %A wolf paws for %P gold?", Topic=4 +%1,1<%1,"bear","paw" -> Type=5896, Amount=%1, Price=100*%1, "Do you want to sell %A bear paws for %P gold?", Topic=4 + +Topic=4,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=4,"yes" -> "Sorry, you do not have it." +Topic=4,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=4 -> "Maybe another time." + +"addon",QuestValue(18504)=0,premium -> "Would you like to wear bear paws like I do? No problem, just bring me 50 bear paws and 50 wolf paws and I'll fit them on.", SetQuestValue(18504,1) +"paws",QuestValue(18504)=0,premium -> * +"addon",QuestValue(18504)=0 -> "Addons can be wear only by premium players." +"paws",QuestValue(18504)=0 -> * + +"addon",QuestValue(18504)=1 -> "Have you brought 50 bear paws and 50 wolf paws?", Topic=5 +"paws",QuestValue(18504)=1 -> * +Topic=5,"yes",Count(5897)>=50,Count(5896)>=50 -> "Excellent! Like promised, here are your bear paws.", DeleteAmount(5897,50), DeleteAmount(5896,50), SetQuestValue(18504,2), AddOutfitAddon(148,1), AddOutfitAddon(144,1), EffectOpp(13) +Topic=5,"yes" -> "You don't have required ingredients." +Topic=5 -> "Maybe another time." + +"addon",QuestValue(18504)=2 -> "I see that you like your new bear paws!" +"paws",QuestValue(18504)=2 -> * +} diff --git a/app/SabrehavenServer/data/npc/uzgod.npc b/app/SabrehavenServer/data/npc/uzgod.npc new file mode 100644 index 0000000..1c6ca03 --- /dev/null +++ b/app/SabrehavenServer/data/npc/uzgod.npc @@ -0,0 +1,159 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uzgod.npc: Datenbank fuer den Waffenhaendler Uzgod + +Name = "Uzgod" +Outfit = (160,77-79-56-115-0) +Home = [32664,31894,9] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N! Wanna weapon, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence %N, Me busy! Wait!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Guut bye. Coming back soon." + +"bye" -> "Guut bye. Coming back soon.", Idle +"farewell" -> * +"job" -> "Me a blacksmith is, an' weapons me sell. You want buy weapons?" +"shop" -> * +"name" -> "Me is Uzgod Hammerslammer, son of Fire, from the Savage Axes. You can say you to me." +"time" -> "Time is %T now." +"help" -> "You can buy the weapons me maked or sell weapons you have, jawoll." +"monster" -> "Me make often hunt on big nasties. Me small, but very big muscles me have, jawoll." +"dungeon" -> "We no dungeon need. We prison isle have." +"prison" -> "Bad ones locked up there. Never come out again there, jawoll." +"mines" -> "Me hacking and smashing rocks as me was little dwarf, jawoll." +"excalibug" -> "You want sell me excalibug for 1000 platinum coins and an enchanted armor?", Topic=3 +"thanks" -> "Me enjoy doing that." +"thank","you" -> * +"obsidian","knife" -> "Me remembering... but long time since seeing such a knife. Maybe can make one from obsidian lance. But only if you paying one draconian steel. Cyclops friend maybe can refine." + +"spear" -> Type=3277, Amount=1, Price=10, "You will buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "You will buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "You will buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "You will buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "You will buy a battle axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "You will buy an axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "You will buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "You will buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "You will buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "You will buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "You will buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "You will buy a mace for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "You will buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "You will buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "You will buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "You will buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "You will buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "You will buy %A axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "You will buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "You will buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "You will buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "You will buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "You will buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "You will buy %A maces for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "You want sell me a mace for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "You want sell me a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "You want sell me a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "You want sell me a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "You want sell me a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "You want sell me a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "You want sell me a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "You want sell me a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "You want sell me a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "You want sell me a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "You want sell me a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "You want sell me a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "You want sell me a war hammer for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "You want sell me a longsword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "You want sell me a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000, "You want sell me a fire sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "You want sell me %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "You want sell me %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "You want sell me %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "You want sell me %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "You want sell me %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "You want sell me %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "You want sell me %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "You want sell me %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "You want sell me %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "You want sell me %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "You want sell me %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "You want sell me %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "You want sell me %A war hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "You want sell me %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "You want sell me %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1, "You want sell me %A fire swords for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Me thank you. Here is your stuff.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No gold, no deal, jawoll." +Topic=1 -> "I sorry you not buy." + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You not have it." +Topic=2,"yes",Amount>1 -> "You not have so many." +Topic=2 -> "You my sorry not to sell." + +topic=3,"yes" -> "Stop make fun of old dwarf, you not having it!" +topic=3,"no" -> "Me wouldn't sell it, too." +topic=3 -> "You joke with me!" + +"do","you","sell" -> "What you need? Me just the weapons sell." +"do","you","have" -> * +"light" -> "Me having clubs, daggers, spears, axes, swords, maces, rapiers, and sabres. What is your choice?" +"heavy" -> "Me having the best two handed swords in tibia. I also sell battle hammers. What is your choice?" +"offer" -> "Me offer you light an' heavy weapons." +"weapon" -> * +"helmet" -> "Me just sell weapons." +"armor" -> * +"shield" -> * + +"pickaxe",QuestValue(325)=2,QuestValue(326)=0 -> "Get me brooch and me get you pickaxe. Look for keys that bringing you to dwarven prison and get brooch." +"pickaxe",QuestValue(325)=1 -> "True dwarven pickaxes having to be maded by true weaponsmith! You wanting to get pickaxe for explorer society?", topic=4 +"pickaxe" -> "True dwarven pickaxes having to be maded by true weaponsmith! Me order book full though." + +topic=4,"yes" -> "Me order book quite full is. But telling you what: You getting me something me lost and Uzgod seeing that your pickaxe comes first. Jawoll! You interested?", topic=5 +topic=4,"no" -> "Stop make fun of old dwarf." +topic=4 -> "You joke with me!" + +topic=5,"yes" -> "Good good. You listening: Me was stolen valuable heirloom. Brooch from my family. Good thing is criminal was caught. Bad thing is, criminal now in dwarven prison of dwacatra is and must have taken brooch with him ...","To get into dwacatra you having to get several keys. Each key opening way to other key until you get key to dwarven prison ...","Last key should be in the generals quarter near armory. Only General might have key to enter there too. But me not knowing how to enter Generals private room at barracks. You looking on your own ...","When got key, then you going down to dwarven prison and getting me that brooch. Tell me that you got brooch when having it.",SetQuestValue(325,2) +topic=5 -> "Then you trying again in five years or so. Maybe pickaxe ready then." +"pickaxe",QuestValue(325)=2,QuestValue(326)=1 -> Type=4834, Amount=1,"You got me brooch?",topic=6 +"brooch",QuestValue(325)=2,QuestValue(326)=1 -> * + +topic=6,"yes",Count(Type)>=Amount -> "Thanking you for brooch. Me guessing you now want your pickaxe?", Delete(Type),Type=4845, Amount=1,SetQuestValue(325,3), topic=7 +topic=6,"yes",Count(Type) "Stop make fun of old dwarf." +topic=6,"no" -> "Stop make fun of old dwarf." +topic=6 -> "You joke with me!" + +"pickaxe",QuestValue(325)=3,QuestValue(326)=1 -> Type=4845, Amount=1,"You want you pickaxe?",topic=7 + + +topic=7,"yes" -> "Here you have it.", Create(Type),SetQuestValue(325,4) +topic=7,"no" -> "Stop make fun of old dwarf." +topic=7 -> "You joke with me!" + +"draconian","steel",ExpiringQuestValue(17575)>0 -> "I am still busy with your draconian steel which you gave me moments ago. Maybe come back later when I will need another one." +"obsidian","lance",ExpiringQuestValue(17575)>0 -> * + +"draconian","steel",ExpiringQuestValue(17575)<0 -> "You bringing me draconian steel and obsidian lance in exchange for obsidian knife?", Topic=8 +"obsidian","lance",ExpiringQuestValue(17575)<0 -> * +Topic=8,"yes",Count(5889)>=1,Count(3313)>=1 -> "Here you have it.", DeleteAmount(5889,1), DeleteAmount(3313,1), Create(5908), SetExpiringQuestValue(17575, 72000000) +Topic=8,"yes" -> "No obsidian lance, no draconian steel." +Topic=8 -> "Stop make fun of old dwarf." + +} diff --git a/app/SabrehavenServer/data/npc/uzon.npc b/app/SabrehavenServer/data/npc/uzon.npc new file mode 100644 index 0000000..68fcb96 --- /dev/null +++ b/app/SabrehavenServer/data/npc/uzon.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uzon: Datenbank für den Teppichpiloten Uzon auf den Femor Hills + +Name = "Uzon" +Outfit = (130,95-5-18-76-0) +Home = [32537,31836,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +BUSY,"bring","me","to","darashia",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,QuestValue(250)>2,CountMoney>=50,! -> Price=50, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +ADDRESS,"bring","me","to","darashia",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) + +BUSY,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +BUSY,"bring","me","to","edron",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,QuestValue(250)>2,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +ADDRESS,"bring","me","to","edron",Premium,CountMoney>=60,! -> Price=60, "Hold on %N!", Queue, DeleteMoney, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) + +BUSY,"hello$",! -> "Hastiness is not the way of the people of Darama, %N. Give me the time I need here.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings", Idle +"name" -> "I am known as Uzon Ibn Kalith." +"job" -> "I am a licensed Darashian carpetpilot. I can bring you to Darashia or Edron." +"time" -> "It's %T right now. The next flight is scheduled soon." +"caliph" -> "The caliph welcomes travellers to his land." +"kazzan" -> * +"daraman" -> "Oh, there is so much to tell about Daraman. You better travel to Darama to learn about his teachings." +"ferumbras" -> "I would never transport this one." +"drefia" -> "So you heared about haunted Drefia? Many adventures travel there to test their skills against the undead: vampires, mummies, and ghosts." +"excalibug" -> "Some people claim it is hidden somewhere under the endless sands of the devourer desert in Darama." +"thais" -> "Thais is noisy and overcroweded. That's why I like Darashia more." +"tibia" -> "I have seen almost every place on the continent." +"continent" -> "I could retell the tales of my travels for hours. Sadly another flight is scheduled soon." +"carlin" -> "Just another Thais but with women to lead them." +"flying","carpet" -> "You can buy flying carpets only in Darashia." +"fly" -> "I transport travellers to the continent of Darama for a small fee. So many want to see the wonders of the desert and learn the secrets of Darama." +"news" -> "I heard too many news to recall them all." +"rumors" -> * + +"passage" -> "I can fly you to Darashia on Darama or Edron if you like. Where do you want to go?" +"transport" -> * +"ride" -> * +"trip" -> * + +"darashia" -> Price=60, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama" -> * +"edron" -> Price=60, "Do you want to get a ride to Edron for %P gold?", Topic=2 + +"darashia",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama",QuestValue(250)>2 -> * +"edron",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to Edron for %P gold?", Topic=2 + +Topic=1,"yes",Premium,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to enter Darama." +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +# für postquest +Topic=2,"yes",Premium, QuestValue(227)=2,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11),SetQuestValue(227,3) + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +Topic=2,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to enter Edron." +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." +} diff --git a/app/SabrehavenServer/data/npc/velvet.npc b/app/SabrehavenServer/data/npc/velvet.npc new file mode 100644 index 0000000..5d7dac2 --- /dev/null +++ b/app/SabrehavenServer/data/npc/velvet.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# velvet.npc: Möbelverkäuferin Velvet in Venore + +Name = "Velvet" +Outfit = (136,59-96-115-95-0) +Home = [32943,32104,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to our shop, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I am Velvet. How can I help you?" +"job" -> "I'm working here in this shop. Are you interested in any of our goods?" +"time",male -> "It's %T, sire." +"time",female -> "It's %T, my lady." + +"offer" -> "I sell pillows and tapestries." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/vera.npc b/app/SabrehavenServer/data/npc/vera.npc new file mode 100644 index 0000000..49cf943 --- /dev/null +++ b/app/SabrehavenServer/data/npc/vera.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# vera.npc: Möbelverkäuferin Vera auf Senja + +Name = "Vera" +Outfit = (136,59-96-115-95-0) +Home = [32138,31672,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need some equipment for your house?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Vera. I sell furniture and equipment." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/vescu.npc b/app/SabrehavenServer/data/npc/vescu.npc new file mode 100644 index 0000000..9ca6a84 --- /dev/null +++ b/app/SabrehavenServer/data/npc/vescu.npc @@ -0,0 +1,97 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Vescu" +Outfit = (152,81-101-122-63-3) +Home = [32565,32651,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",Drunk>0 -> "Hey t-there, you look like someone who enjoys a good booze." +ADDRESS,"hi$",Drunk>0 -> * +ADDRESS,"hello$",Drunk=0 -> "Oh, two t-trolls. Hellooo, wittle twolls. ", Idle +ADDRESS,"hi$",Drunk=0 -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh, two t-trolls. Hellooo, wittle twolls. " + +"bye" -> "T-time for another b-beer. ", Idle +"farewell" -> * + +"addon" -> "I can give you a scar as an addon. Nyahahah." +"booze" -> "Did I say booze? I meant, flamingo. Pink birds are kinda cool, don't you think? Especially on a painting." +"flamingo" -> "You have to enjoy the word. Like, flayyyminnngoooo. Say it with me. " +"flayyyminnngoooo" -> "Yes, you got it! Hahaha, we understand each other. Good job. " +"job" -> "I'm a killer! Yeshindeed! A masterful assassin. I prefer that too 'Peekay'. " +"outfit" -> "O-outfit? You already have a t-troll outfit. That's good enough for you. " + +"sober",QuestValue(17562)=0 -> "I wish there was like a potion which makes you sober in an instant. Dwarven rings wear off so fast. ", Topic=1 +Topic=1,"potion" -> "It's so hard to know the exact time when to stop drinking.hicks; C-could you help me to brew such a potion?", Topic=2 +Topic=1 -> "I don't think so. " +Topic=2,"yes" -> "You're a true buddy. I promise I will t-try to avoid killing you even if someone asks me to. hicks ...", + "Listen, I have this old formula from my grandma. It says... 30 beholder eyes... 10 red dragon scales. ...", + "Then 30 lizard scales... 20 fish fins - ew, this sounds disgusting, I wonder if this is really a potion or rather a cleaning agent. ...", + "Add 20 ounces of vampire dust, 10 ounces of demon dust and mix well with one flask of warrior's sweat. ...", + "Okayyy, this is a lot... we'll take this step by step. Will you help me gathering 30 beholder eyes?", Topic=3 +Topic=2 -> "Then not. " +Topic=3,"yes" -> "G-good. Go get them, I'll have a beer in the meantime.", SetQuestValue(17562,1) +Topic=3 -> "Then not. " + +"beholder","eye",QuestValue(17562)=1 -> Type=5898, Amount=30, "Have you really managed to bring me 30 beholder eyes? ", Topic=4 +"mission",QuestValue(17562)=1 -> * +"task",QuestValue(17562)=1 -> * +Topic=4,"yes",Count(Type)>=Amount -> "Aw-awsome! Squishy! Now, please bring me 10 red dragon scales.", Delete(Type), SetQuestValue(17562,2) +Topic=4,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=4 -> "Then not. " + +"red","dragon","scale",QuestValue(17562)=2 -> Type=5882, Amount=10, "D-did you get all of the 10 red dragon scales? ", Topic=5 +"mission",QuestValue(17562)=2 -> * +"task",QuestValue(17562)=2 -> * +Topic=5,"yes",Count(Type)>=Amount -> "G-good work, ... wha-what's your name again? Anyway... come back with 30 lizard scales.", Delete(Type), SetQuestValue(17562,3) +Topic=5,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=5 -> "Then not. " + +"lizard","scale",QuestValue(17562)=3 -> Type=5881, Amount=30, "Ah, are those - - the 30 lizard scales?", Topic=6 +"mission",QuestValue(17562)=3 -> * +"task",QuestValue(17562)=3 -> * +Topic=6,"yes",Count(Type)>=Amount -> "This potion will become p-pretty scaly. I'm not sure yet if I want to d-drink that. I think the 20 fish fins which come next won't really improve it. ", Delete(Type), SetQuestValue(17562,4) +Topic=6,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=6 -> "Then not. " + +"fish","fin",QuestValue(17562)=4 -> Type=5895, Amount=20, "Eww, is that disgusting smell coming from the 20 fish fins? ", Topic=7 +"mission",QuestValue(17562)=4 -> * +"task",QuestValue(17562)=4 -> * +Topic=7,"yes",Count(Type)>=Amount -> "Alrrrrrrright! Thanks for the f-fish. Get me the 20 ounces of vampire dust now. I'll have another b-beer.", Delete(Type), SetQuestValue(17562,5) +Topic=7,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=7 -> "Then not. " + +"vampire","dust",QuestValue(17562)=5 -> Type=5905, Amount=20, "Have you collected 20 ounces of vampire d-dust? ", Topic=8 +"mission",QuestValue(17562)=5 -> * +"task",QuestValue(17562)=5 -> * +Topic=8,"yes",Count(Type)>=Amount -> "Tha-thank you. Trolls are good for something a-after all. Bring me the 10 ounces of demon dust now. ", Delete(Type), SetQuestValue(17562,6) +Topic=8,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=8 -> "Then not. " + +"demon","dust",QuestValue(17562)=6 -> Type=5906, Amount=10, "Have you slain enough d-demons to gather 10 ounces of demon dust? ", Topic=9 +"mission",QuestValue(17562)=6 -> * +"task",QuestValue(17562)=6 -> * +Topic=9,"yes",Count(Type)>=Amount -> "G-great. You're a reeeal k-killer like me, eh? I think I'll g-give you something fun when the potion is complete. But first, b-bring me warrior's sweat.", Delete(Type), SetQuestValue(17562,7) +Topic=9,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=9 -> "Then not. " + +"warrior","sweat",QuestValue(17562)=7 -> Type=5885, Amount=1, "This s-smells even worse than the fish fins. Is that warrior's sweat?", Topic=10 +"mission",QuestValue(17562)=7 -> * +"task",QuestValue(17562)=7 -> * +Topic=10,"yes",Count(Type)>=Amount -> "Yahaha! Here we g-go. I'll just take a small sip - . Okay, this is disgusting, but it seems to work. I'll teach you something fun, remind me to tell you a secret sometime.", Delete(Type), SetQuestValue(17562,8) +Topic=10,"yes" -> "Next time you lie to me I'll k-kill you. Don't think I can't aim well just because I'm d-drunk." +Topic=10 -> "Then not. " + +"secret",QuestValue(17562)=8 -> "Right. Since you helped me to b-brew that potion and thus ensured the high quality of my work , I'll give you my old assassin costume. It lacks the head part, but it's almost like new. Don't pretend to be me though, 'kay? ", AddOutfit(152), AddOutfit(156), EffectOpp(13), SetQuestValue(17562,9) +"mission",QuestValue(17562)=8 -> * +"task",QuestValue(17562)=8 -> * + +"secret",QuestValue(17562)>8 -> "What secret? " +"mission",QuestValue(17562)>8 -> "Sorry. I have to start working soon." +"task",QuestValue(17562)>8 -> * +} diff --git a/app/SabrehavenServer/data/npc/vladruc.npc b/app/SabrehavenServer/data/npc/vladruc.npc new file mode 100644 index 0000000..78b66e8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/vladruc.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Vladruc.npc: Datenbank für Vladruc Urghain, den Besitzer des magischen Markts + +Name = "Vladruc" +Outfit = (68,0-0-0-0-0) +Home = [32976,32079,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",Count(3083)>0,! -> "Chhhh ... Sorry, I'm busy. ", Idle +ADDRESS,"hi$",Count(3083)>0,! -> * +ADDRESS,"hello$",! -> "I am Vladruc Urghain and welcome you, %N, to my house." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Count(3083)>0,! -> "Sorry, I'm busy." +BUSY,"hi$",Count(3083)>0,! -> * +BUSY,"hello$",! -> "Please be seated and wait, %N." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Go safely, and leave something of the happiness you bring!" + +"bye" -> "Go safely, and leave something of the happiness you bring!", Idle +"farewell" -> * +"name" -> "I am Vladruc Urghain. Welcome to my house!" +"job" -> "I am a humble merchant of little importance to the beautiful Venore." +"thais" -> "Sadly only through books I have come to know your great Thais, and to know her is to love her." +"books" -> "These companions have been good friends and teachers to me." +"adventure" -> "The time I sought out adventure is long gone indeed." +"Tibia" -> "What a wonderful world we do live in ... so full of life." +"shop" -> "Ah, feel free to browse and buy in my humble shop below." +"market" -> * +"time" -> "It is %T." +"king" -> "I am of noble blood myself. I have been so long master that none other should be master of me." +"tibianus" -> * +"venore" -> "Our ways are not your ways, and there shall be to you many strange things." +"army" -> "The Thaian garrsion serves its purpose very well." +"ferumbras" -> "You think he is of ancient evil? Little you know about ancientness or evilness." +"excalibug" -> "A terrifying weapon if it does exist at all." +"news" -> "I am a reclusive person and learn little of the local gossip of the peasants." +"help" -> "I am sorry, but I can't be of much assistance to you." +"monster" -> "Oh yes, the children of the night ... you dwellers in the city cannot enter into the feelings of the hunter." +"dungeon" -> "Such lovely places, unjustly shunned by the people." +"vampire" -> "Please don't talk about such creatures. You are scaring me." +"thanks" -> "That's nothing worth to be mentioned." +"thank","you" -> * + +"offer" -> "Please check my humble market downstairs for the wares that are offered." +"magic" -> "Magic is a tool to be mastered." +"spells" -> "I know a spell or two. You might want to buy some spells downstairs in the market." +"alchemy" -> "You can buy some potions downstairs." +"blood" -> "I like blood ... only the color, that is, of course ... " +"undead" -> "It is not dead, which can eternal lie, and in strange aeons, even death may die." +"necroman" -> "Death is the final frontier. Necromancers boldly go, where no one has gone before." +"coffin" -> "The final restingplace for all of us, isn't it?" +} diff --git a/app/SabrehavenServer/data/npc/vulturenose.npc b/app/SabrehavenServer/data/npc/vulturenose.npc new file mode 100644 index 0000000..daf33c1 --- /dev/null +++ b/app/SabrehavenServer/data/npc/vulturenose.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon + +Name = "Vulturenose" +Outfit = (96,0-0-0-0-0) +Home = [31977,32856,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hrrrrm. So what?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N. I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Get lost." + +"bye" -> "Get lost.", Idle +"farewell" -> * +"what" -> "I guard this door. I won't let anyone pass who is not supposed to enter." +"pirate" -> "Yoho, a pirate's life for me!" +"name" -> "They call me Vulturenose." +"rum" -> "You won't bribe old Vulturenose with some rum. No way." +"raymond striker" -> "Sooner or later he will run out of luck and then we will get his head." +"king tibianus" -> "We are the undisputed kings of the seas." +"excalibug" -> "Only a wimp needs magical weapons." +"ferumbras" -> "If he'd be still alive, he'd make a fine ship magician. Ho Ho Ho." +"sugar" -> "You can call me honey." +"liberty bay" -> "For us Liberty Bay is an open treasure chest. Ho Ho Ho." +"venore" -> "The Venoreans don't care about the likes of you." +"thais" -> "Thais is a town full of wimps." +"carlin" -> "A city full of women? That calls for plunder. Sadly, a bit far away. Ho Ho Ho." + +"enter",QuestValue(17527)=1 -> "Hey, I rarely see a dashing pirate like you! Get in, matey!" +"enter",QuestValue(17527)=0,SlotItem(1)=6096,SlotItem(4)=6095,SlotItem(7)=5918,SlotItem(8)=5461 -> "Hey, I rarely see a dashing pirate like you! Get in, matey!", SetQuestValue(17527,1) +"enter",QuestValue(17527)=0 -> "YOU WILL NOT PASS! Erm ... I mean you don't look like a true pirate to me. You won't get in." +} diff --git a/app/SabrehavenServer/data/npc/wally.npc b/app/SabrehavenServer/data/npc/wally.npc new file mode 100644 index 0000000..ec6a3c9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/wally.npc @@ -0,0 +1,77 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wally.npc: Datenbank für Wally den Postbeamten in der Postlergilde + +Name = "Wally" +Outfit = (129,96-113-95-115-0) +Home = [32566,32019,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, traveller." +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait in line." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + +@"gen-post.ndb" + +"name" -> "I'm Wally the post officer." +"job" -> "I am working here at the post office for Kevin." +"kevin" -> "Oh, our boss is upstairs. Better only disturb him with important issues though." +"postner" -> * +"postmasters","guild" -> "We are an organization of importance for the whole of Tibia. Even kings tremble before our might." +"join" -> "You have to talk with the exalted archpostman Kevin Postner if you want to join our prestigeous guild." +"markwin" -> "This minotaur is quite moody. Better make sure not to anger him. He's likely to call for his guards as soon as he notices a stranger, so you are on tough luck if you ever find him." +"santa","claus" -> "This old whitebeard lives on some hill on the western coast of Vega as far as I know." +"brassacres" -> "This guy might be hard to find. Hes likely disguising himself. If you see someone suspicious, try to ask other people who are around about him. That might give you some clue." +"mission" -> "Ask Mr. Postner about your current missions." + +"ben" -> "Old Ben lost some of his marbles in some battle long ago. He is still a quite capable postman though ... on second thought thats some disturbing fact." +"lokur" -> "Dwarfs make quite good postmen. They are stubborn, strong and ... sturdy. Its a waste that he prefers a job behind the counter." +"dove" -> "Dove is as good as a dozend pigeons. He He He." +"olrik" -> "This Olrik was made postman only for convenience. He is quite aware that his attitude and affiliation with the thaian government makes it impossible for him to rise in rank. This leads only to him behaving even worse tough." +"liane" -> "Although I never met her in person we became penpals over the time." +"wally" -> "Yes, thats me, Wally!" + +"advancement" -> "The exalted archpostman Kevin Postner alone decides about advancement of our members. All ranks come with certain privileges." +"privileges" -> "Our privileges are top secret, other people envy us enough already for beeing members in this splendid guild." + +"uniform" -> "We could badly need new uniforms." +"uniform",QuestValue(234)>6 -> "Oh thank you, this new uniform I just got suits me well!" +"waldo",QuestValue(249)<4 -> "Waldo is an explorer. We allways feared he might one day take a risk too great for him. I hope he is ok though." +"waldo",QuestValue(249)=4 -> "To hear about Waldos death strikes my heart with grief. We did not only loose our greatest explorer but a dear friend." + +"dress","pattern" -> "I vaguely remember the last dress pattern of our uniforms was dependend on certain key elements. It had some technical gadgets, a special smell and was uniquely colored." +"crowbar" -> "Most general stores should sell crowbars. I think the store in Edron sells some for instance." +"hint" -> "I can't help you much with your missions. Of course we tell you everything we know and do't make our missions needlesly difficult." +"headquarter" -> "Its humble and practical. Considering we have bases all over the known world we don't need a bigger base anyways. On the other hand Mr. Postner is dreaming about a postman academy now and then." +"bones" -> "If I would be looking for bones I'd inspect some skeletons ... If I weren't so affraid of them that is." +"banana","skin" -> "Uh? How disgusting. Look for this rubbish in some places where waste is dumped .. and don't ever tell me what you need it for." +"fur" -> "As far as I heard some of the minor orcs carry a pice of fur as a fetish or lucky charm with them." +"moldy","cheese" -> "What a disgusting taste you have. Like those Goblins who carry this stuff with them." +"noodles" -> "This dog is his majestys most priced possesion and heavily guarded. Anger the dog and you anger the king." +"thais" -> "One of the oldest holdings of humanity that still exist and the heart of the biggest kingdom in the known world." +"carlin" -> "Carlin is an upcoming power in theese days. Albeit its ambitions it still dwarfs the old kingdom of thais in power and influence." +"venore" -> "I think no longer is king Tibianus reigning this city, nor are the merchants ruling it, regardless what they might think. The true monarch before whom all there bow is the money." +"edron" -> "I gues Edron isn't the source of wealth and rescources as the thaians hoped. The defection of those knights did cause the expansion and exploitation there to an halt." +"defection" -> "I know nothing special about that story. I only heared that a good part of the knightly order the king sent there succumbed to their lust for wealth and power and turned against their swordbrethren." +"darama" -> "A far away place with strange customs and an even stranger philosophy. One day I might travel there to see it on my own." +"darashia" -> * +"ab'dendriel" -> "Elven volunteers for becoming a postofficer are quite rare. We had to rely on a human living there to ensure our postsystems function. Most elvish members of the guild prefer to work as courriers." +"elf" -> * +"elven" -> * +"Kazordoon" -> "The city of the dwarfs is a bit hidden and new postoficers often get lost while looking for it. Just look for a hidden passage to a western valley in the mountaion called the big old one." +"dwarf" -> * +"dwarves" -> * +"dwarfs" -> * +"big","old","one" -> "Its a huge Mountain, north of here, just across the river." +"posthorn" -> "A posthorn is a postmens bride ... or a postwomans husband. The only true friend a lonly postofficer has in the foregin lands and dangerous places he has to visit." +"cap$" -> "A cap is what shows you are a postofficer. But your heart and your state of mind are what you makes a postofficer." +"mailbox" -> "Our mailboxes are quite reliable but know and then one has to be fixed. Especually in the more rough climates." +} diff --git a/app/SabrehavenServer/data/npc/walter.npc b/app/SabrehavenServer/data/npc/walter.npc new file mode 100644 index 0000000..225b1e9 --- /dev/null +++ b/app/SabrehavenServer/data/npc/walter.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# walter.npc: Datenbank für die Stadtwache am Südtor + +Name = "Walter, the guard" +Outfit = (131,19-19-19-19-0) +Home = [32338,32278,7] +Radius = 2 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/app/SabrehavenServer/data/npc/warbert.npc b/app/SabrehavenServer/data/npc/warbert.npc new file mode 100644 index 0000000..302d514 --- /dev/null +++ b/app/SabrehavenServer/data/npc/warbert.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# warbert.npc: Datenbank für eine Stadtwache in Venore + +Name = "Warbert" +Outfit = (131,113-113-113-115-0) +Home = [32935,32141,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/app/SabrehavenServer/data/npc/willard.npc b/app/SabrehavenServer/data/npc/willard.npc new file mode 100644 index 0000000..f07eb39 --- /dev/null +++ b/app/SabrehavenServer/data/npc/willard.npc @@ -0,0 +1,175 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# willard.npc: Datenbank für den Schmied Willard + +Name = "Willard" +Outfit = (131,58-104-19-116-0) +Home = [33214,31793,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings and Banor be with you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'm ready soon, %N. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"name" -> "I am Willard, the smith." +"job" -> "I am the blacksmith of castle Bloodrock." +"time" -> "Now it is %T." +"king" -> "Hail to the King! He's our benevolent protector." +"tibianus" -> * +"army" -> "I supply them with all they need." +"ferumbras" -> "I would be honored if it's one of my blades that one day delivers him his punishment." +"excalibug" -> "Adventurers search for this blade all over the world. Even here." +"thais" -> "A fine city, but I love the peace of Edron more." +"tibia" -> "It's my dream that one day the whole world will profit from the Thaian governance." +"carlin" -> "If the king sees the time is right, he will certainly start a campaign to reclaim what belongs to Thais." +"edron" -> "Edron is a fine city to live in." +"news" -> "Rumors are too sinister things that a true warrior would care for them." +"rumors" -> * + +"buy" -> "What do you need? I sell weapons, armors, helmets, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are weapons, ammunition, armors, helmets, legs, and shields." +"weapon" -> "I have hand axes, axes, barbarian axes, spears, maces, clerical maces, battle hammers, swords, rapiers, daggers, sabres, bows, and crossbows. What's your choice?" +"ammunition" -> "I have arrows for bows and bolts for crossbows. What do you want?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, steel shields, and viking shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"barbar","axe" -> Type=3317, Amount=1, Price=590, "Do you want to buy a barbarian axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"cleric","mace" -> Type=3311, Amount=1, Price=540, "Do you want to buy a clerical mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"barbar","axe" -> Type=3317, Amount=%1, Price=590*%1, "Do you want to buy %A barbarian axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"cleric","mace" -> Type=3311, Amount=%1, Price=540*%1, "Do you want to buy %A clerical maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"viking","shield" -> Type=3431, Amount=1, Price=260, "Do you want to buy a viking shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=260*%1, "Do you want to buy %A viking shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=240, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","cleric","mace" -> Type=3311, Amount=1, Price=170, "Do you want to sell a clerical mace for %P gold?", Topic=2 +"sell","barbar","axe" -> Type=3317, Amount=1, Price=185, "Do you want to sell a barbarian axe for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=240*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"cleric","mace" -> Type=3311, Amount=%1, Price=170*%1, "Do you want to sell %A clerical maces for %P gold?", Topic=2 +"sell",%1,1<%1,"barbar","axe" -> Type=3317, Amount=%1, Price=185*%1, "Do you want to sell %A barbarian axes for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=150, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","viking","shield" -> Type=3431, Amount=1, Price=85, "Do you want to sell a viking shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=150*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=85*%1, "Do you want to sell %A viking shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/william.npc b/app/SabrehavenServer/data/npc/william.npc new file mode 100644 index 0000000..6f3682a --- /dev/null +++ b/app/SabrehavenServer/data/npc/william.npc @@ -0,0 +1,32 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# william.npc: Datenbank für den carlin trunkenbold William + +Name = "William" +Outfit = (128,115-0-67-114-0) +Home = [32314,31802,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hi$",! -> "Oh, hello! " +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> " Not right now...", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> " Damn, I am starting to imagine things... again." + +"bye" -> "Bye bye .", Idle +"how","are","you"-> "Thanks I am drunk ." +"sell" -> "Hey! Thats my drink, buy your own!" +"job" -> "I forgot what a job I have." +"karl" -> "A good guy with good beer." +"name" -> "My Name? Uh... Wait! 'Bring down the trash, William you...' William, my name is William!" +"time" -> "Its precisely after ." +"help" -> "I need another drink, then I'll help you. Promise." +"carlin" -> "I whish I'd live in Thais, the city of alcohol." +"thais" -> * +"sewer" -> "The sewers are our last refuge." +"refuge" -> "Yes, refuge from womanhood." +"todd" -> "In Todd we trust! TODD! TODD! TODD!" + +} diff --git a/app/SabrehavenServer/data/npc/willie.npc b/app/SabrehavenServer/data/npc/willie.npc new file mode 100644 index 0000000..2aa3201 --- /dev/null +++ b/app/SabrehavenServer/data/npc/willie.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# willie.npc: Datenbank fuer den Farmer Willie + +Name = "Willie" +Outfit = (128,58-63-58-115-0) +Home = [32047,32204,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Nah, I am talking. Wait here.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "YOU RUDE $!@##&" + +"bye" -> "Yeah, bye.", Idle +"farewell" -> "Yeah, farewell.", Idle +"how","are","you" -> "Fine enough." +"job" -> "I am a farmer and a cook." +"cook" -> "I try out old and new recipes. You can sell me all food you have." +"recipe" -> "I would love to try a banana-pie. But I lack the bananas. If you get me one, I will reward you." +"name" -> "Willie." +"time" -> "Am I a clock or what?" +"help" -> "Help yourself, I have not stolen my time." +"monster" -> "Are you afraid of monsters ... you baby?" +"dungeon" -> "I have no time for your dungeon nonsense." +"sewer" -> "What about them? Do you live there?" +"god" -> "I am a farmer, not a preacher." +"king" -> "I'm glad that we don't see many officials here." +"obi" -> "This little $&#@& has only #@$*# in his mind. One day I will put a #@$@ in his *@&&#@!" +"seymour" -> "This joke of a man thinks he is sooo important." +"dallheim" -> "Uhm, fine guy I think." +"cipfried" -> "Our little monkey." +"amber" -> "Quite a babe." +"weapon" -> "I'm not in the weapon business, but if you don't stop to harass me, I will put my hayfork in your &$&#$ and *$!&&*# it." +"magic" -> "I am magician in the kitchen." +"spell" -> "I know how to spell and i know how to spit, you little @!#&&. Wanna see?." +"tibia" -> "If I were you, I would stay here." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"sell","bread" -> Type=3600, Amount=1, Price=1, "So, you want to sell a bread? Hmm, I give you %P gold, ok?", Topic=2 +"sell","cheese" -> Type=3607, Amount=1, Price=2, "So, you want to sell a cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","salmon" -> Type=3579, Amount=1, Price=2, "So, you want to sell a salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with this stinking &*#@@!" +"sell","cherry" -> Type=3590, Amount=1, Price=1, "So, you want to sell a cherry? Hmm, I give you %P gold, ok?", Topic=2 + +"sell",%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=1*%1, "So, you want to sell %A breads? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=2*%1, "So, you want to sell %A cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A hams? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=2*%1, "So, you want to sell %A salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"fish" -> "Go away with this stinking &*#@@!" +"sell",%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "So, you want to sell %A cherries? Hmm, I give you %P gold, ok?", Topic=2 + +"sell" -> "I sell food of many kinds." +"buy" -> "I buy food of any kind. Since I am a great cook I need much of it." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"no" -> "Then not." + +"banana" -> Type=3587, Amount=1, "Have you found a banana for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "A banana! Great. Take this shield, so the &#@&* monsters don't beat the &@*&@ out of you.", Delete(Type), Create(3426) +Topic=3,"yes" -> "Hm, you don't have it." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=3,"no" -> "Too bad." +Topic=3 -> "Too bad." +} diff --git a/app/SabrehavenServer/data/npc/windtrouser.npc b/app/SabrehavenServer/data/npc/windtrouser.npc new file mode 100644 index 0000000..9638095 --- /dev/null +++ b/app/SabrehavenServer/data/npc/windtrouser.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# windtrouser.npc: Fischer Dalbrect Windtrouser nahe Mönchsinsel + +Name = "Dalbrect" +Outfit = (129,76-97-105-76-0) +Home = [32211,31756,7] +Radius = 5 + + +Behaviour = { +ADDRESS,"hello$",QuestValue(220)>0,QuestValue(62)=2,! -> "Sorry my friend, the monks don't allow me to talk to you because of your intrusion in their chambers. Unless you plea to the abbot for absolution I can do nothing for you.",Idle +ADDRESS,"hi$",QuestValue(220)>0,QuestValue(62)=2,! -> * + +ADDRESS,"hello$",QuestValue(220)>0,! -> "The monks forbid me to talk to you for you evil deeds.",Idle +ADDRESS,"hi$",QuestValue(220)>0,! -> * + +ADDRESS,"hello$",! -> "Be greeted, traveler." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(220)>0,QuestValue(62)=2,! -> "Sorry %N, the monks don't allow me to talk to you because of your intrusion in their chambers. Unless you plea to the abbot for absolution I can do nothing for you.",Idle +BUSY,"hi$",QuestValue(220)>0,QuestValue(62)=2,! -> * + +BUSY,"hello$",QuestValue(220)>0,! -> "Sorry %N, the monks forbid me to talk to you for you evil deeds.",Idle +BUSY,"hi$",QuestValue(220)>0,! -> * + +BUSY,"hello$",! -> "One moment please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP + +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Dalbrect Windtrouser, of the once proud windtrouser family." +"job" -> "I am merely a humble fisher now that nothing is left of my noble legacy." +"captain" -> * +"ship" -> "My ship is my only pride and joy." +"ferry" -> * +"legacy" -> "Once my family was once noble and wealthy, but fate turned against us and threw us into poverty." +"family" -> * +"nobility" -> * +"fate" -> "When Carlin tried to colonize the region now known as the ghostlands, my ancestors put their fortune in that project." +"poverty" -> * +"project" -> "Our family fortune was lost when the colonization of those cursed lands failed. Now nothing is left of our fame or our fortune. If I only had something as a reminder of those better times. " +"ghostlands" -> * + + +"brooch",QuestValue(62)=2,! -> "You have recovered my brooch! I shall forever be in your debt, my friend!" +"brooch" -> "What? You want me to examine a brooch?", Type=3205, Amount=1, topic=3 +topic=3, "yes", Count(Type) "What are you talking about? I am too poor to be interested in jewelry." +topic=3, "yes", Count(Type)>=Amount -> "Can it be? I recognize my family's arms! You have found a treasure indeed! I am poor and all I can offer you is my friendship, but ... please ... give that brooch to me?", topic=2 +topic=3 -> "Then stop being a fool. I am poor and I have to work the whole day through!" + +Topic=2,"yes",Count(Type)>=Amount -> "Thank you! I shall consider you my friend from now on! Just let me know if you need something!", Delete(Type), SetQuestValue(62,2) +Topic=2,"yes" -> "I should have known better then to ask for an act of kindness in this cruel, selfish, world!" +Topic=2 -> * + + +"trip" -> "I have only sailed to the isle of the kings once or twice. I dare not anger the monks by bringing travellers there without their permission." +"passage" -> * +"carlin" -> "To think my family used to belong to the local nobility! And now those arrogant women are in charge!" +"island" -> "The only isle I visit regularly is the isle of the kings. I bring food and the occasional visitor to the monastery." +"monastery" -> "The monks are not exactly fond of visitors, so I rarely take somebody there without their permission." +"white","raven" -> "I think that is the name both of the monastery and of the monks' order." +"passage",QuestValue(62)=1 -> Price=20, "Since you have the abbot's permission I can sail you to the isle of the kings for %P gold. Is that ok for you?", Topic=1 +"passage",QuestValue(62)=2 -> Price=10, "Since you are my friend now I will sail you to the isle of the kings for %P gold. Is that okay for you?", Topic=1 +"passage" -> "You do not have the abbot's permission, and I won't risk angering the monks because of some guy I do not even know." + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32190,31957,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Well, I'll be here if you change your mind." + +} diff --git a/app/SabrehavenServer/data/npc/wyat.npc b/app/SabrehavenServer/data/npc/wyat.npc new file mode 100644 index 0000000..57f52a8 --- /dev/null +++ b/app/SabrehavenServer/data/npc/wyat.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wyat.npc: Datenbank für den Sheriff von Thais Wyat + +Name = "Wyat" +Outfit = (129,98-96-95-116-0) +Home = [32325,32276,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$",! -> "Salutations!" +ADDRESS,"salutations$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am busy!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "I have no news for the public." +"king" -> "HAIL TO KING TIBIANUS!" +"leader" -> * +"job" -> "I am the sheriff of the Thaian territory." +"how","are","you"-> "I am fine, thanks." +"sell" -> "I am in the safety business." +"army" -> "I usually work with the townguards only." +"guard" -> * +"general" -> "Old Bloodblade does a fine job." +"enemies" -> "Our enemies are numerous and not all are obvious." +"enemy" -> * +"criminal" -> * +"murderer" -> * +"castle" -> "The castle should be relatively safe from criminal transgressions." +"subject" -> "There are certain criminal objects in the population of our town." +"red","guard" -> "Most of the red guards serve as cityguards, some work for the TBI though." +"secret","police"-> "All i can tell you is, that it's known as the TBI." +"tbi$" -> "The Tibian Bureau of Investigation. If you want to know more, ask Chester Kahs about it, but I doubt you'll get any vital information." +"chester" -> "His bureau is at the northgate." +"silver","guard" -> "They are our elite forces." +"city" -> "The city is not as bad as some people might claim, but we certainly have our problems here." +"problem" -> "We will handle each problem with care." +"stutch" -> "A fine warrior, indeed. He is one of the king's bodyguards." +"harsky" -> * +"bozo" -> "He's so funny, I could listen to his jokes for hours." +"sam" -> "Sam, the Thaian smith, is a man of great diligence. Whenever in need of weapons or armor, just ask him." +"weapon" -> * +"armor" -> * +"elane" -> "A woman of great skill and courage. No one deserves the title of a Grandmaster of the Paladins more then her." +"gorn" -> "He was a rowdy in his youth, but now he's a fine citizen as far as I can tell." +"benjamin" -> "The poor fool lost his mind some years ago. It's a good thing they gave him a job in the post office." +"ferumbras" -> "He attacked our town at several occasions but was repelled each time." +"quest" -> "Look up our 'Tibia's most wanted' lists." +"mission" -> * +"god" -> "I am follower of Banor." +"banor" -> "He is the patron of justice and bravery." +"zathroth" -> "Don't mention this name!" +"brog" -> "The more primitive races such as orcs often worship the raging one." +"monster" -> "Thais should be relatively safe from direct assaults of monsters." +"excalibug" -> "If you have any news about the whereabouts of that blade, report it to me." +"rebellion" -> "Luckily that's nothing I have to care about." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/app/SabrehavenServer/data/npc/wyda.npc b/app/SabrehavenServer/data/npc/wyda.npc new file mode 100644 index 0000000..85a60dd --- /dev/null +++ b/app/SabrehavenServer/data/npc/wyda.npc @@ -0,0 +1,121 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wyda.npc: Datenbank für die Hexe Wyda (Swamp) + +Name = "Wyda" +Outfit = (54,0-0-0-0-0) +Home = [32726,31980,6] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",Druid,Count(3065)>=1,! -> "Welcome back, %N. Hey, nice wand you have there!" +ADDRESS,"greet",Druid,Count(3065)>=1,! -> * +ADDRESS,"hello$",Druid,! -> "Welcome to my hut, %N! It's always nice to see a druid here." +ADDRESS,"greet",Druid,! -> * +ADDRESS,"hello$",Sorcerer,! -> "What do you want, %N?" +ADDRESS,"greet",Sorcerer,! -> * +ADDRESS,"hello$",! -> "Good day, %N." +ADDRESS,"greet",! -> * +ADDRESS,"good","day",! -> * +ADDRESS,"hi$",! -> "What? Talking to me, %N?", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, just a moment, please.", Queue +BUSY,"greet",! -> * +BUSY,"good","day",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Good luck on your journeys.", Idle +"farewell" -> * +"see","you" -> * +"job" -> "I am a witch. Didn't you notice?" +"name" -> "My name is Wyda, and what's yours?" +"my","name","is" -> "Nice to meet you." +"time" -> "I think it is the fourth year after Queen Eloise's crowning, but I cannot tell you date or time." + +"sorcerer" -> "Sorcerers have forgotten about the root of all beings: nature." +"druid" -> "Druids are mostly fine people. I'm always happy when I meet one." +"knight" -> "Knights succumb to the blindness of rage and the desire for violence and blood." +"paladin" -> "Paladins can use bows, but not brains." +"queen" -> "Eloise is Queen of Carlin. I don't care about royals much, as long as they don't try to tax me." +"i$","live" -> "That's nice." +"carlin" -> "Carlin is a beautiful town, but far from here. Do you live there?" +"thais" -> "I've heard stories about that city. It's nowhere near here, that's all I can tell you about it." +"stories" -> "Thais is an overcrowded place inhabited by brutal murderers. At least that's what I've been told." +"tibia$" -> "Tibia is the name of our continent." +"tibianus" -> "Haha, that's a stupid name. Who's that?" +"ferumbras" -> * +"king" -> "There are too many royals on this continent if you ask me..." +"evil" -> "Evilness doesn't scare me." +"aureus" -> "Aureus is a good friend who spends much time in this area!" +"bridge" -> "There's a bridge to the west, but it's guarded by dwarfs." +"plains" -> "Many tales exist about some so-called Plains of Havoc. It seems to be a dangerous place." +"havoc" -> * +"help" -> "I can only help with knowledge. What do you want me to tell you about?" +"hunter" -> "To the east, there is a little settlement of hunters. They are cruel humans who attack everything they see." +"buy" -> "I'm currently not selling anything." +"offer" -> * +"sell" -> "There's nothing I need right now, thanks." +"key" -> "I keep my keys where they belong - in my pocket." +"monster" -> "Many creatures live in, around, and beneath the swamp. Be careful!" +"creature" -> * +"swamp" -> "Be careful of the swamp water, it's poisonous!" +"nature" -> "There are many swamp plants, mushrooms, and herbs around here." +"plant" -> "There are many kinds of swamp plants, some can be used for potions, some not." +"potion" -> "The recipe of the potions is one of the witches' secrets!" +"secret" -> * +"recipe" -> * +"sister" -> "Some sisters of mine are having a meeting nearby. Don't disturb them, or they will get angry and attack you." +"witches" -> * +"mushroom" -> "Mushrooms taste good and are useful for potions." +"heal" -> "I do not have any potions for healing available right now." +"giant","spider"-> "Yes, there is such a thing in the east, on a small island. It's very powerful." +"beholder" -> "Beholders? Strange creatures that have mysterious magical abilities." +"slime" -> "There's lots of slime around. It is said that they live from the swamp water." +"god" -> "I believe that nature itself is God." +"magic" -> "The magic of the witches is one of our secrets!" +"spell" -> * +"weatherwax" -> "I think I've heard that name before..." +"ogg" -> * +"voodoo" -> "I don't practice such nonsense, that's just a rumour." +"coffin" -> "That's none of your business." +"dwarf" -> "The little bearded fellows have a town somewhere in the northwest." +"dwarves" -> * +"little","fellows"-> * +"kazordoon" -> "Isn't that the name of the little bearded fellows' town?" +"gold" -> "Money means nothing to me." +"platin" -> * +"cookie" -> "I bake cookies now and then in my spare time." +"orange" -> "I love exotic fruits. I have oranges imported from the south sometimes, but that's very expensive." +"fly","broom",! -> "Haha, no... where did you get that idea? I use it to sweep my platform." +"ride","broom",!-> * +"broom","fly",! -> * +"broom" -> "What about it?" +"platform" -> "This platform and house were built by my mother, long ago." +"mother" -> "Of course my mother was also a witch!" +"crystal","ball"-> "It's a magical item that only witches can use." +"black","knight"-> "A black knight? Black is the color of witches, why whould any knight carry black?" +"earthquake" -> "The earth in this region shakes now and then. Foolish people think that this is because the Gods are angry." + +"become","witch",female,! -> "You can't just become a witch. Either you are or you aren't - and YOU obviously aren't!" +"become","witch",male,! -> "You're a MAN!" +"witch$" -> "Aye, I am a witch." +"man$" -> "There are only female witches." +"men$" -> * + +"power","wand",! -> "The power of the wand can only be used by witches." +"use","wand",! -> * +"what","do","wand",! -> * +"wand" -> "I use a wooden spellwand. Why are you asking?" +"spellwand" -> * + +"quest" -> "A quest? Well, if you're so keen on doing me a favour... Why don't you try to find a blood herb?" +"herbs" -> "The swamp is home to a wide variety of herbs, but the most famous is the blood herb." +"blood","herb$",Count(3734)>=1,! -> "Do you have a blood herb for me?", Topic=1 +"blood","herb$" -> "The blood herb is very rare. This plant would be very useful for me, but I don't know any accessible places to find it." + +Topic=1,"yes",Count(3734)<1,! -> "Well, do you own one or not?" +#Topic=1,"yes",Sorcerer -> Price=400, "Hmm, thanks. Take this.", Delete(3734), CreateMoney +#Topic=1,"yes",Druid -> "Thank you so much! Here, let me give you a reward...", Delete(3734), Create(3065) +#Topic=1,"yes" -> Price=300, "Thank you! Here are some coins for your help.", Delete(3734), CreateMoney +Topic=1,"yes" -> "Thank you so much! Here, let me give you a reward...", Delete(3734), Create(3211) +} diff --git a/app/SabrehavenServer/data/npc/xed.npc b/app/SabrehavenServer/data/npc/xed.npc new file mode 100644 index 0000000..7e70c7b --- /dev/null +++ b/app/SabrehavenServer/data/npc/xed.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Xed.npc: Datenbank für den Bogner Xed + +Name = "Xed" +Outfit = (129,78-36-57-97-0) +Home = [32904,32117,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. Welcome to the distance fighting booth of the Ironhouse." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye, and may the gods be with you." + +"bye" -> "Goodbye, and may the gods be with you.", Idle +"farewell" -> * +"job" -> "I am the humble supplier for distance fighting weapons of the Ironhouse, owned by Abran Ironeye." +"fletcher" -> * +"name" -> "People call me Xed, but my full name is Xedem." +"time" -> "I don't know, maybe what you really need is a watch." +"hurt" -> "Go to a priest. I am sure they will fix you up." +"Abran","Ironeye" -> "He is the owner of this market, although - just between you and me - I'm not so sure he's honest." +"honest" -> "Well, I overheard the boss discussing some shady deals with a man in a black cloak." +"shady","deals" -> "Something about a sword only great warlords can use and a rare distance fighting item." +"rare","distance" -> "Yes, but I believe this is nothing but lies seeing that there are only a few distance fighting weapons." +"amazons" -> "They are a band or tribe of strange women that have nothing in common with civilized men like me." +"general" -> "You must be talking of the great general Benjamin. He saved the kingdom from ferumbras you know." +"army" -> "We supply the archers of the army with distance fighting weapons." +"ferumbras" -> "I heard rumours somewhere that his father was called Hugo." +"Xed" -> "Yeah, nice name, eh?" +"excalibug" -> "I think that was the sword they were talking about. Said something about a man in Edron that could get it for him." +"news" -> "Some people say Ferumbras isn't really dead. Crazy kids!" +"help" -> "I sell items of the distance type." +"monster" -> "Yeah, these awful beasts. They live in the swamps near the city and in dark dungeons." +"dungeon" -> "Oh, they are all over. You never see more of them than in Kaz, though." +"Kaz" -> "Oh, that's short for Kazordoon." + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrows for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolts for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +"task",QuestValue(17647)=0,paladin -> Amount=17648, "Young paladin, I see you need ammunition but those are too expensive, right. Hmm... I can't give you for free. ...", + "However, if you could kill 50 minotaurs to prove your trustworthy willingness I will reward you the bow and 300 arrows. Deal?", Topic=120 + +"task",QuestValue(17648)=50,QuestValue(17647)=1,paladin -> "Well done, %N. Here is your bow and arrows!", SetQuestValue(17647,2), SetQuestValue(17649,0), Type=3350, Amount=1, Create(Type), Type=3447, Amount=300, Create(Type) + +"task",QuestValue(17649)>0 -> "I see you are still in progress with your task." + +Topic=120,"yes" -> "Very well young paladin. Come back once you are done.", SetQuestValue(17649,Amount), SetQuestValue(Amount,0), SetQuestValue(17647,1) +Topic=120 -> "As you wish." + +"task" -> "I don't have any tasks for you right now." + +} diff --git a/app/SabrehavenServer/data/npc/xodet.npc b/app/SabrehavenServer/data/npc/xodet.npc new file mode 100644 index 0000000..c4faf31 --- /dev/null +++ b/app/SabrehavenServer/data/npc/xodet.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# xodet.npc: Datenbank für den Magieladen-Besitzer Xodet + +Name = "Xodet" +Outfit = (130,19-86-87-95-0) +Home = [32399,32222,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Wait a minute, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye and come again.", Idle +"farewell" -> * +"name" -> "I'm Xodet, the owner of this shop." +"job" -> "I'm sorcerer and trade with all kinds of magic items." +"sorcerer" -> "There is a sorcerer guild in Thais. Just go in the east of the town, it is easly to find." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +"backpack","life","fluid" -> Type=2874, Data=11, Amount=1, Price=61*20, "Do you want to buy a backpack of life fluid for %P gold?", Topic=8 +"bp","life","fluid" -> * +"backpack","mana","fluid" -> Type=2874, Data=10, Amount=1, Price=56*20, "Do you want to buy a backpack of mana fluid for %P gold?", Topic=8 +"bp","mana","fluid" -> * +"backpack","blank","rune" -> Type=3147, Amount=1, Price=11*20, "Do you want to buy a backpack of blank rune for %P gold?", Topic=7 +"bp","blank","rune" -> * + +%1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +%1,1<%1,"backpack","life","fluid" -> Type=2874, Data=11, Amount=%1, Price=61*20*%1, "Do you want to buy %A backpacks of potions of life fluid for %P gold?", Topic=8 +%1,1<%1,"bp","life","fluid" -> * +%1,1<%1,"backpack","mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=56*20*%1, "Do you want to buy %A backpacks of potions of mana fluid for %P gold?", Topic=8 +%1,1<%1,"bp","mana","fluid" -> * +%1,1<%1,"backpack","blank","rune" -> Type=3147, Amount=%1, Price=11*20*%1, "Do you want to buy %A backpacks of blank runes for %P gold?", Topic=7 +%1,1<%1,"bp","blank","rune" -> * + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +Topic=7,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=7,"yes" -> "Come back, when you have enough money." +Topic=7 -> "Hmm, but next time." + +Topic=8,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, CreateContainer(2854,Type,Data) +Topic=8,"yes" -> "Come back, when you have enough money." +Topic=8 -> "Hmm, but next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/yaman.npc b/app/SabrehavenServer/data/npc/yaman.npc new file mode 100644 index 0000000..c15c13a --- /dev/null +++ b/app/SabrehavenServer/data/npc/yaman.npc @@ -0,0 +1,189 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yaman.npc: Datenbank für den Efreethändler Yaman (Magische Gegenstände, Efreet) + +Name = "Yaman" +Outfit = (51,0-0-0-0-0) +Home = [33045,32620,2] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Well, if it isn't a human. Greetings, %N!" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "One at a time, human. Wait until I have time for you, %N.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Goodbye human.", Idle +"farewell" -> * +"name" -> "I'm called Yaman." +"Yaman" -> "That is my name." +"job" -> "I am a trader. Not a popular job around here since it involves dealing with humans, but I don't mind. I rather sit here than have my brain bashed out in this childish war. ...", + "If you have the permission of Malor to trade with us, I will buy all magical equipment you have to offer." +"permission" -> "I am not allowed to buy anything from you unless Malor gave you the permission to trade with us." +"malor" -> "Malor is our leader. He is driven by greed and by ambition. He is clever, though. Personally, I sided with him because Daraman's creed did not appeal to me. ...", + "'Everybody for himself' is my motto. As an Efreet, I can do what I want. No artificial moral restraints. I take what I want." +"efreet" -> "Curious how we have changed physically in the course of time, isn't it? But the fact that we have different skin colours cannot hide the fact that Efreet and Marid are still related by blood." +"marid" -> "I do not hate the Marid - not more than anybody else, anyhow. I joined the Efreet because it seemed the logical thing to do." + +"gabel" -> "Even though Malor would never admit it, Gabel is a strong and charismatic leader. We cannot win this war as long as he is alive." +"king" -> "The djinns do not have kings these days. Gabel renounced his right to the title - a clever move. Malor would love to proclaim himself king, but he does not have the authority to do it. Perhaps one day he will." +"djinn" -> "My race is stuck in a vicious civil war. As long as this war hasn't ended the djinn are too self-absorbed to deal with other races." +"war" -> * + +"mal'ouquah" -> "Mal'ouquah is the name of this place. Malor built it - hence the name. It means 'Malor's rage'." +"ashta'daramai" -> "Ashta'daramai is the name of the Marids' fortress. Gabel tore his former fortress down and erected Ashta'daramai in its place. ...", + "Personally I preferred the older fortress, but it does not make much difference. If our side wins the war it will be torn down anyway." +"human" -> "Humans are selfish, greedy and unnecessarily cruel. In other words, they resemble us in many ways. ...", + "Unlike other Efreet I would not kill a human just for the fun of it. However, I would not hesitate to do it if it seemed profitable. No offence." +"zathroth" -> "The legends say Zathroth abandoned the djinn because they were way too headstrong. I like that story - for two reasons. ...", + "Firstly, it shows that we have independent minds. ...", + "And secondly, it shows that we djinn are free to choose our own destinies. ...", + "No gods, no ties, no rules. We can do as we please and be who we want." +"tibia" -> "This world is our playground." +"daraman" -> "Of course I know Daraman. How could I ever forget him. I was there, remember? ...", + "The man was a dangerous fool. At first I laughed, but I soon realised that one by one my brothers started to believe his nonsense. 'Solidarity and brotherly love' - Yeah right." +"darashia" -> "The wealth of Darashia is proverbial. I have my own little plans with it." +"scarab" -> "Some people find scarabs fascinating. Exactly why is simply beyond me." +"edron" -> "That is a human city, isn't it? I'd like to see it." +"thais" -> * +"venore" -> * +"kazordoon" -> "Isn't that a city built by dwarves?" +"carlin" -> * +"ab'dendriel" -> "I understand that city was built by elves." +"ankrahmun" -> "Ankrahmun is the oldest human settlement in Tibia. I remember how surprised we were when we found that humans were capable of building such fine cities." +"pharaoh" -> "They say that pharaoh established his own religion. Amazing. We djinn are notoriously given to pride, but no djinn would ever have the arrogance to proclaim himself god." +"palace" -> "They say that palace has turned into a home for the living dead under the new pharaoh's rule." +"ascension" -> "Hm. I don't know about this, human." +"kha'zeel" -> "Djinns have lived in these mountains, called Kha'zeel, for as long as anyone can remember. ...", + "Humans rarely come up here, and few djinn care to travel down to the Kha'labal. It is probably better this way. For both our races." +"kha'labal" -> "The Kha'labal we know today was created at the height of the djinn war. Not exactly a glorious chapter in the war's history." +"melchior" -> "Melchior tried to double-cross us. That worm! He paid his treachery dearly. No human messes with the Efreet and lives to tell the tale." +"alesar" -> "Ah yes - Alesar... Everybody here is really excited because of him. These fools seem to believe that winning the war will be a trifle now Malor is back and we have Alesar on our side. I am far more cautious. ...", + "I don't think Alesar is trying to double-cross us, but I have a bad feeling about him. I still don't understand what made him switch sides." +"fa'hradin" -> "I feel a strange kind of respect for Fa'hradin even though he is a Marid. He is more level-headed and clear-sighted than most Efreet I know. ...", + "I don't know what made him side with Gabel, but he is the only djinn I miss. ...", + "In fact he was one of the few djinn I ever liked." +"bo'ques" -> "Bo'ques. The fat cook. Exactly how he fits into the freaky ascetiscim of the Marid is beyond me." +"haroun" -> "Haroun. That name rings a bell. Isn't he Alesar brother? They used to be inseparable." +"baa'leal" -> "Baa'leal is our side's commander-in-chief. No wonder I can't be asked to join the service." +"lamp" -> "Yes - djinns sleep in lamps. How long did it take you to work that one out?" + +"wares" -> "My task is to buy and sell supplies. We are dealing with magical equipment like rings, amulets, rods and some special items." +"offer" -> * +"goods" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"amulets" -> "I'm selling and buying strange talismans, silver amulets, protection amulets and dragon necklaces." +"rings" -> "I'm selling and buying might rings, energy rings, life rings, time rings, dwarven rings and rings of healing." +"rods" -> "I'm buying snakebite rods, moonlight rods, volcanic rods, quagmire rods and tempest rods." +"wands" -> "I'm not interested in wands." +"special" -> "I'm currently looking for some special items. Do you have any ankhs or a mysterious fetish?" + +"might","ring" -> Type=3048, Amount=1, Price=5000, "Do you want to buy a might ring for %P gold?", Topic=10 +"energy","ring" -> Type=3051, Amount=1, Price=2000, "Do you want to buy an energy ring for %P gold?", Topic=10 +"life","ring" -> Type=3052, Amount=1, Price=900, "Do you want to buy a life ring for %P gold?", Topic=10 +"time","ring" -> Type=3053, Amount=1, Price=2000, "Do you want to buy a time ring for %P gold?", Topic=10 +"silver","amulet" -> Type=3054, Amount=1, Price=100, "Do you want to buy a silver amulet for %P gold?", Topic=10 +"strange","talisman" -> Type=3045, Amount=1, Price=100, "Do you want to buy a strange talisman for %P gold?", Topic=10 +"dwarven","ring" -> Type=3097, Amount=1, Price=2000, "Do you want to buy a dwarven ring for %P gold?", Topic=10 +"ring","of","healing" -> Type=3098, Amount=1, Price=2000, "Do you want to buy a ring of healing for %P gold?", Topic=10 +"protection","amulet" -> Type=3084, Amount=1, Price=700, "Do you want to buy a protection amulet for %P gold?", Topic=10 +"dragon","necklace" -> Type=3085, Amount=1, Price=1000, "Do you want to buy a dragon necklace for %P gold?", Topic=10 + +%1,1<%1,"strange","talisman" -> Type=3045, Amount=%1, Price=100*%1, "Do you want to buy %A strange talismans for %P gold?", Topic=10 +%1,1<%1,"might","ring" -> Type=3048, Amount=%1, Price=5000*%1, "Do you want to buy %A might rings for %P gold?", Topic=10 +%1,1<%1,"energy","ring" -> Type=3051, Amount=%1, Price=2000*%1, "Do you want to buy %A energy rings for %P gold?", Topic=10 +%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=900*%1, "Do you want to buy %A life rings for %P gold?", Topic=10 +%1,1<%1,"time","ring" -> Type=3053, Amount=%1, Price=2000*%1, "Do you want to buy %A time rings for %P gold?", Topic=10 +%1,1<%1,"silver","amulet" -> Type=3054, Amount=%1, Price=100*%1, "Do you want to buy %A silver amulets for %P gold?", Topic=10 +%1,1<%1,"dwarven","ring" -> Type=3097, Amount=%1, Price=2000*%1, "Do you want to buy %A dwarven rings for %P gold?", Topic=10 +%1,1<%1,"ring","of","healing" -> Type=3098, Amount=%1, Price=2000*%1, "Do you want to buy %A rings of healing for %P gold?", Topic=10 +%1,1<%1,"protection","amulet" -> Type=3084, Amount=%1, Price=700*%1, "Do you want to buy %A protection amulets for %P gold?", Topic=10 +%1,1<%1,"dragon","necklace" -> Type=3085, Amount=%1, Price=1000*%1, "Do you want to buy %A dragon necklaces for %P gold?", Topic=10 + +"sell","might","ring" -> Type=3048, Amount=1, Price=250, "Do you want to sell a might ring for %P gold?", Topic=11 +"sell","energy","ring" -> Type=3051, Amount=1, Price=100, "Do you want to sell an energy ring for %P gold?", Topic=11 +"sell","life","ring" -> Type=3052, Amount=1, Price=50, "Do you want to sell a life ring for %P gold?", Topic=11 +"sell","time","ring" -> Type=3053, Amount=1, Price=100, "Do you want to sell a time ring for %P gold?", Topic=11 +"sell","silver","amulet" -> Type=3054, Amount=1, Price=50, "Do you want to sell a silver amulet for %P gold?", Topic=11 +"sell","strange","talisman" -> Type=3045, Amount=1, Price=30, "Do you want to sell a strange talisman for %P gold?", Topic=11 +"sell","dwarven","ring" -> Type=3097, Amount=1, Price=100, "Do you want to sell a dwarven ring for %P gold?", Topic=11 +"sell","ring","of","healing" -> Type=3098, Amount=1, Price=100, "Do you want to sell a ring of healing for %P gold?", Topic=11 +"sell","protection","amulet" -> Type=3084, Amount=1, Price=100, "Do you want to sell a protection amulet for %P gold?", Topic=11 +"sell","dragon","necklace" -> Type=3085, Amount=1, Price=100, "Do you want to sell a dragon necklace for %P gold?", Topic=11 +"sell","mysterious","fetish" -> Type=3078, Amount=1, Price=50, "Do you want to sell a mysterious fetish for %P gold?", Topic=11 +"sell","ankh" -> Type=3077, Amount=1, Price=100, "Do you want to sell an ankh for %P gold?", Topic=11 + +"sell",%1,1<%1,"strange","talisman" -> Type=3045, Amount=%1, Price=30*%1, "Do you want to sell %A strange talismans for %P gold?", Topic=11 +"sell",%1,1<%1,"might","ring" -> Type=3048, Amount=%1, Price=250*%1, "Do you want to sell %A might rings for %P gold?", Topic=11 +"sell",%1,1<%1,"energy","ring" -> Type=3051, Amount=%1, Price=100*%1, "Do you want to sell %A energy rings for %P gold?", Topic=11 +"sell",%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=50*%1, "Do you want to sell %A life rings for %P gold?", Topic=11 +"sell",%1,1<%1,"time","ring" -> Type=3053, Amount=%1, Price=100*%1, "Do you want to sell %A time rings for %P gold?", Topic=11 +"sell",%1,1<%1,"silver","amulet" -> Type=3054, Amount=%1, Price=50*%1, "Do you want to sell %A silver amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"dwarven","ring" -> Type=3097, Amount=%1, Price=100*%1, "Do you want to sell %A dwarven rings for %P gold?", Topic=11 +"sell",%1,1<%1,"ring","of","healing" -> Type=3098, Amount=%1, Price=100*%1, "Do you want to sell %A rings of healing for %P gold?", Topic=11 +"sell",%1,1<%1,"protection","amulet" -> Type=3084, Amount=%1, Price=100*%1, "Do you want to sell %A protection amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","necklace" -> Type=3085, Amount=%1, Price=100*%1, "Do you want to sell %A dragon necklaces for %P gold?", Topic=11 +"sell",%1,1<%1,"mysterious","fetish" -> Type=3078, Amount=%1, Price=50*%1, "Do you want to sell %A mysterious fetishes for %P gold?", Topic=11 +"sell",%1,1<%1,"ankh" -> Type=3077, Amount=%1, Price=100*%1, "Do you want to sell %A ankhs for %P gold?", Topic=11 + +"sell","snakebite","rod" -> Type=3066, Amount=1, Price=100, "Do you want to sell a snakebite rod for %P gold?", Topic=11 +"sell","moonlight","rod" -> Type=3070, Amount=1, Price=200, "Do you want to sell a moonlight rod for %P gold?", Topic=11 +"sell","volcanic","rod" -> Type=3069, Amount=1, Price=1000, "Do you want to sell a volcanic rod for %P gold?", Topic=11 +"sell","quagmire","rod" -> Type=3065, Amount=1, Price=2000, "Do you want to sell a quagmire rod for %P gold?", Topic=11 +"sell","tempest","rod" -> Type=3067, Amount=1, Price=3000, "Do you want to sell a tempest rod for %P gold?", Topic=11 + +"sell",%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=100*%1, "Do you want to sell %A snakebite rods for %P gold?", Topic=11 +"sell",%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=200*%1, "Do you want to sell %A moonlight rods for %P gold?", Topic=11 +"sell",%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=1000*%1, "Do you want to sell %A volcanic rods for %P gold?", Topic=11 +"sell",%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=2000*%1, "Do you want to sell %A quagmire rods for %P gold?", Topic=11 +"sell",%1,1<%1,"tempest","rod" -> Type=3067, Amount=%1, Price=3000*%1, "Do you want to sell %A tempest rods for %P gold?", Topic=11 + +Topic=10,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Good. Here you are.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "You do not have enough gold, human!" +Topic=10 -> "As you wish." + +Topic=11,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Good. Here is your money.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one, human!" +Topic=11,"yes",Amount>1 -> "You do not have that many, human!" +Topic=11 -> "As you wish." + +"fighting","spirit" -> Type=3392, Amount=2, "I need two royal helmets to extract one container of fighting spirit. Would you like me to perform the extraction?", Topic=12 +Topic=12,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=12,"yes",Count(Type)>=Amount -> "Good. Here you are.", Delete(Type), Type=5884, Amount=1, Create(Type) +Topic=12,"yes" -> "You do not have one, human!" +Topic=12,"yes",Amount>1 -> "You do not have that many, human!" +Topic=12 -> "As you wish." + +"magic","sulphur" -> Type=3280, Amount=3, "I need three fire sword to extract one magic sulphur. Would you like me to perform the extraction?", Topic=13 +Topic=13,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=13,"yes",Count(Type)>=Amount -> "Good. Here you are.", Delete(Type), Type=5904, Amount=1, Create(Type) +Topic=13,"yes" -> "You do not have one, human!" +Topic=13,"yes",Amount>1 -> "You do not have that many, human!" +Topic=13 -> "As you wish." + +"warrior","sweat" -> Type=3369, Amount=4, "I need four warrior helmets to extract one flask of warrior's sweat. Would you like me to perform the extraction?", Topic=14 +Topic=14,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=14,"yes",Count(Type)>=Amount -> "Good. Here you are.", Delete(Type), Type=5885, Amount=1, Create(Type) +Topic=14,"yes" -> "You do not have one, human!" +Topic=14,"yes",Amount>1 -> "You do not have that many, human!" +Topic=14 -> "As you wish." + +"chicken","wing" -> Type=3079, Amount=1, "I need one pair of boots of haste to extract one enchanted chicken wing. Would you like me to perform the extraction?", Topic=15 +Topic=15,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=15,"yes",Count(Type)>=Amount -> "Good. Here you are.", Delete(Type), Type=5891, Amount=1, Create(Type) +Topic=15,"yes" -> "You do not have one, human!" +Topic=15,"yes",Amount>1 -> "You do not have that many, human!" +Topic=15 -> "As you wish." +} diff --git a/app/SabrehavenServer/data/npc/yanni.npc b/app/SabrehavenServer/data/npc/yanni.npc new file mode 100644 index 0000000..1057b15 --- /dev/null +++ b/app/SabrehavenServer/data/npc/yanni.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yanni.npc: Datenbank für den Waffenhändler Yanni + +Name = "Yanni" +Outfit = (131,22-22-22-57-0) +Home = [32909,32111,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations, %N. Welcome to the Ironhouse, warehouse of Abran Ironeye." +ADDRESS,"hi$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude..." + +"bye" -> "Thanks for doing business with us, traveller!", Idle +"farewell" -> * +"job" -> "I work for Abran Ironeye in the armor department." +"shop" -> "This shop belongs to Abran Ironeye." +"market" -> * +"ironhouse" -> * +"name" -> "My name is Yanni." +"time" -> "It is %T." +"Abran","Ironeye" -> "A very hard boss, but if you ever talk to him, tell him I said he's a great man." +"excalibug" -> "I don't believe in the excalibug myth." +"news" -> "I know nothing of interest." +"help" -> "If you'd like to help, then buy something, because I'm paid on a commisioned basis." +"commision" -> "Commison is when you get paid only when you sell something. If I don't make some sales soon, my kids will get hungry!" +"monster" -> "Monsters? Where! I'm horrified of monsters." +"thanks" -> "You're welcome." +"thank","you" -> * +"news" -> "Don't buy from Xed. He is a thief. He steals my business all the time!" +"offer" -> "I sell armor, legs, helmets, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"helmet" -> "I am selling chain helmets. Do you want to buy any?" +"armor" -> "I am selling chain and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/app/SabrehavenServer/data/npc/yberius.npc b/app/SabrehavenServer/data/npc/yberius.npc new file mode 100644 index 0000000..53b2dbc --- /dev/null +++ b/app/SabrehavenServer/data/npc/yberius.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yberius.npc: Datenbank für den Mönch Yberius + +Name = "Yberius" +Outfit = (57,0-0-0-0-0) +Home = [32961,32076,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, young %N! If you are new in Tibia, ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I can heal you for free." + +"bye" -> "May the gods bless you, %N!", Idle +"farewell" -> * +"job" -> "I have no job. I just live for the gods of Tibia." +"name" -> "I am Brother Yberius." +"tibia" -> "It's the world of Tibia." +"god" -> "They are the creators of Tibia and all life on it." +"life" -> "There are the plants, the citizens, and the monsters." +"plant" -> "Crunor, the god of plants and fertility, watches over all plants, small and big." +"citizen" -> "Just walk around and meet them. Chat and learn about them." +"monster" -> "Even they have their part in the bigger scheme. Even if it eludes us mere mortals." +"king" -> "The king resides in the far away city of Thais." +"tibianus" -> * +"army" -> "The royal army is here to protect us." +"ferumbras" -> "The gods only know what this spawn of darkness might be up to." +"excalibug" -> "This blasphemous weapon has to be destroyed." +"news" -> "I won't take part in idle gossip." +"help" -> "Earn some gold and upgrade your equipment." +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "If you need money, you have to slay monsters and take their gold. Look for swamptrolls in the swamps. But be careful. They use poisoned weapons." +"money" -> * +"equipment" -> "First you should buy a machete. You will need it in the swamps. And better don't explore without a shovel and a rope." +"eat" -> "If you want to heal your wounds, buy somthething to eat or hunt some small game. If you are very weak just ask me to heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"time" -> "Now, it is %T. Ask Shiantis for a watch, if you need one." + +"stake",QuestValue(17576)=4,Count(5941)<=0 -> "I think you have forgotten to bring your stake." +"stake",QuestValue(17576)=4 -> Type=5941, Amount=1, "Yes, I was informed what to do. Are you prepared to receive my line of the prayer?", Topic=10 +Topic=10,"yes",Count(Type)>=Amount -> "So receive my prayer: 'Protection will be granted - from dangers at hand'. Now, bring your stake to Isimov in the dwarven settlement for the next line of the prayer. I will inform him what to do.", SetQuestValue(17576,5) +Topic=10,"yes" -> "I think you have forgotten to bring your stake." +Topic=10 -> "I will wait for you." +"stake",QuestValue(17576)=5 -> "You should visit Isimov in the dwarven settlement now." +"stake",QuestValue(17576)>5 -> "You already received my line of the prayer." +"stake" -> "A blessed stake? That is a strange request. Maybe Quentin knows more, he is one of the oldest monks after all." +} diff --git a/app/SabrehavenServer/data/npc/yoem.npc b/app/SabrehavenServer/data/npc/yoem.npc new file mode 100644 index 0000000..e97486a --- /dev/null +++ b/app/SabrehavenServer/data/npc/yoem.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yoem.npc: Möbelverkäufer Yoem auf Cormaya + +Name = "Yoem" +Outfit = (128,41-112-105-96-0) +Home = [33305,31966,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need some equipment for your house?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Yoem. I sell furniture and equipment." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/yulas.npc b/app/SabrehavenServer/data/npc/yulas.npc new file mode 100644 index 0000000..08ca8e7 --- /dev/null +++ b/app/SabrehavenServer/data/npc/yulas.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yulas.npc: Möbelverkäufer Yulas in Venore + +Name = "Yulas" +Outfit = (128,58-43-38-76-0) +Home = [33000,32065,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Plank and Treasurechest Market, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I am Yulas. I will be your salesperson today." +"job" -> "We are into home improvement." +"time",male -> "It's %T, sire." +"time",female -> "It's %T, my lady." +"news" -> "Sorry, we are not allowed to chat." +"allen" -> "To think just because he is around here to watch what we do, he want to be considered one of us..." +"richardson" -> * + +"offer" -> "We sell furniture and equipment. At this counter you can buy tables." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-tables-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/zaidal.npc b/app/SabrehavenServer/data/npc/zaidal.npc new file mode 100644 index 0000000..7b46381 --- /dev/null +++ b/app/SabrehavenServer/data/npc/zaidal.npc @@ -0,0 +1,67 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zaildal.npc: Datenbank für den Bambusmöbelhändler Zaidal + +Name = "Zaidal" +Outfit = (128,76-43-77-76-0) +Home = [32621,32747,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling furniture, especially bamboo made furniture. I also buy elephant tusks to create my famous tusk tables and ivory chairs." +"name" -> "I am known as Zaidal." +"time" -> "Sorry, I have no idea." +"king" -> "Perhaps one day even the king will use our furniture." +"venore" -> "Well, Venore is the centre of commerce." +"thais" -> "One day I might visit Thais." +"carlin" -> "Carlin is very far to the north. I have never been there." +"edron" -> "An isle with interesting forests. With the right organisation the furniture and shipbuilding business could prosper enormously." +"jungle" -> "You call it a jungle, I call it furniture in the making." + +"offer" -> "I sell tables, chairs and drawers, all handmade with the material that the jungle has to offer." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +"tibia" -> "The world is a big treasure chest for those who know to turn resources into profit." + +"kazordoon" -> "The dwarves of Kazordoon are experts in mining resources like gems and ore." +"dwarvs" -> * +"dwarfes" -> * +"ab'dendriel" -> "Those elves are a bit complicated when it comes to trees and environmental matters. They must learn that you have to crush an egg to bake a cake." +"elves" -> * +"elfs" -> * +"darama" -> "The jungle is rich in resources and who knows what profit lies hidden in the desert? Of course not for a carpenter, but there are other resources." +"darashia" -> "People there seem to know little about the world of economy. Perhaps someone might teach them a lesson one day." +"ankrahmun" -> "I was there only once, but I left it with the certainty that I never want to return there ever again." +"ferumbras" -> "If we could guide his attention in useful directions, he wouldn't be the problem he poses nowadays." +"excalibug" -> "Even if it would exist, which I doubt, it would be only an extremely expensive weapon and nothing more." +"apes" -> "They live in the depth of the jungle, and their only visits here are annoying raids to steal and plunder." +"lizard" -> "I did not see much of the lizzards yet." +"dworcs" -> "If we could get rid of them, a whole new area which is rich in bamboo would be ours." + +"sell","tusk" -> Type=3044, Amount=1, Price=100, "Do you want to sell a tusk for %P gold?", Topic=2 +"sell",%1,1<%1,"tusk" -> Type=3044, Amount=%1, Price=100*%1, "Do you want to sell %A tusks for %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +@"gen-t-furniture-jungle-s.ndb" +} diff --git a/app/SabrehavenServer/data/npc/zebron.npc b/app/SabrehavenServer/data/npc/zebron.npc new file mode 100644 index 0000000..ebce225 --- /dev/null +++ b/app/SabrehavenServer/data/npc/zebron.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zebron.npc: Datenbank für den Spieler Zebron + +Name = "Zebron" +Outfit = (128,95-15-109-76-0) +Home = [32931,32071,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, high roller. So you care for a game, %N?", Topic=1 +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Take a minute to count your money, %N. I'll be here soon.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hey, you can't leave. Luck is smiling on you. I can feel it!" + +"bye" -> "Hey, you can't leave. Luck is smiling on you. I can feel it!", Idle +"farewell" -> * +"job" -> "Oh, I am just sitting around here and gamble now and then." +"tavern" -> "It's a fine place to be around, isn't it?" +"name" -> "I am known as Zebron." +"time" -> "It is exactly %T." +"king" -> "Ah, our beloved king! Bless him for the gambling licence of Venore." +"tibianus" -> * +"licence" -> "I don't care much for that law stuff, but as far as I know those Venore merchants got a royal gambling licence for the city." +"venore" -> "Aaaah, Venore, Venore, what a wonderful town. Especially for someone with love for gambling like me." +"army" -> "Hehe, they spent a good part of their salary here in the tavern." +"excalibug" -> "I would not bet that anyone will ever find it." +"thais" -> "Thais is a bit too conservative for me." +"tibia" -> "What would I need more than that what I can get right here?" +"carlin" -> "Carlin, the beerless ... what a shame." +"hugo" -> "I had a cousin named hugo, why do you ask?" +"news" -> "Bah, always the same chitchat. Swampelves this and amazons that ... blah blah." +"rumors" -> * +"swamp" -> * +"amazon" -> * + +"gambl" -> "So you care for a civilized game of dice?", Topic=1 +"game" -> * +"dice" -> * + +Topic=1,"yes" -> "Hmmm, would you like to play for money or for a chance to win your own dice?", Topic=3 +Topic=1,"no" -> "Oh come on, don't be a child." + +Topic=3,"money" -> "I thought so. Okay, I will roll a dice. If it shows 6, you will get five times your bet. How much do you want to bet?", Amount=Random(1,6), Topic=2 +Topic=3,"dice" -> "Hehe, good choice. Okay, the price for this game is 100 gold pieces. I will roll a dice. If I roll a 6, you can have my dice. Agreed?", Amount=Random(1,6), Topic=4 + +Topic=6,"yes" -> "Okay, I will roll a dice. If it shows 6, you will get five times your bet. How much do you want to bet?", Amount=Random(1,6), Topic=2 +Topic=6,"no" -> "Oh come on, don't be a child." +Topic=2,%1,0<%1,100>%1,CountMoney>=%1,Amount=6 -> Price=%1*5, "Ok, here we go ... 6! You have won %P, congratulations. One more game?", CreateMoney, EffectMe(27), Topic=6 +Topic=2,%1,0<%1,100>%1,CountMoney>=%1 -> Price=%1, "Ok, here we go ... %A! You have lost. Bad luck. One more game?", DeleteMoney, EffectMe(27), Topic=6 +Topic=2,%1,0<%1,100>%1 -> "I am sorry, but you don't have so much money. How much do you want to bet?", Topic=2 +Topic=2,%1 -> "I am sorry, but I accept only bets between 1 and 99 gold. I don't want to ruin you after all. How much do you want to bet?", Topic=2 + +Topic=5,"yes" -> "Okay, the price for this game is 100 gold pieces. I will roll a dice. If I roll a 6, you can have my dice. Agreed?", Amount=Random(1,6), Topic=4 +Topic=5,"no" -> "Oh come on, don't be a child." +Topic=4,"yes",CountMoney>=100,Amount=6 -> Price=100, Type=5792, Amount=1, "Ok, here we go ... 6! You have won a dice, congratulations. One more game?", DeleteMoney, Create(Type), EffectMe(27), Topic=5 +Topic=4,"yes",CountMoney>=100 -> Price=100, "Ok, here we go ... %A! You have lost. Bad luck. One more game?", DeleteMoney, EffectMe(27), Topic=5 +Topic=4,"yes" -> "I am sorry, but you don't have so much money." +} diff --git a/app/SabrehavenServer/data/npc/zerbrus.npc b/app/SabrehavenServer/data/npc/zerbrus.npc new file mode 100644 index 0000000..1fe19f4 --- /dev/null +++ b/app/SabrehavenServer/data/npc/zerbrus.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zerbrus.npc: Datenbank für den Dorfwächter Zerbrus (Rookgaard) + +Name = "Zerbrus" +Outfit = (131,76-38-76-95-0) +Home = [32022,32203,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings young traveller." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not now." +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye.", Idle +"farewell" -> * +"how","are","you" -> "Fine." +"sell" -> "Ask the shopowners for their wares." +"advice",level<4 -> "Be careful out there and avoid the dungeons." +"advice",level>3 -> "Be careful out there." +"job" -> "I am the bridgeguard. I defend Rookgaard against the beasts of the wilderness and the dungeons!" +"name" -> "Zerbrus at your service." +"time" -> "My duty is eternal. Time is of no importance." +"help" -> "I have to stay here, sorry, but I can heal you if you are wounded." +"monster" -> "I will slay all monsters who dare to attack this little town." +"dungeon" -> "Dungeons are dangerous, be prepared." +"wilderness" -> "There are wolves, bears, snakes, deers, and spiders. You can find some dungeon entrances there, too." +"sewer" -> "In the sewers are crowded with rats. They make fine targets for young heroes." +"god" -> "I am a follower of Banor." +"dallheim" -> "He does a fine job." +"banor" -> "The heavenly warrior! Read books to learn about him." +"king" -> "HAIL TO THE KING!" +"seymour" -> "His job to teach the young heroes is important for our all survival." +"willie" -> "He can swear and curse as good as the rowdyest seaman I met." +"amber" -> "Shes verry attractive. To bad my duty leaves me no time to date her." +"hyacinth" -> "One of theese reclusive druids." +"weapon" -> "My weapon is property of the royal army. Find your own one." +"magic" -> "You will learn about magic soon enough." +"tibia" -> "In the world of tibia many challenges await the brave adventurers." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +} diff --git a/app/SabrehavenServer/data/npc/zoltan.npc b/app/SabrehavenServer/data/npc/zoltan.npc new file mode 100644 index 0000000..108da83 --- /dev/null +++ b/app/SabrehavenServer/data/npc/zoltan.npc @@ -0,0 +1,99 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zoltan.npc Datenbank fuer den Zauberlehrer Zoltan + +Name = "Zoltan" +Outfit = (130,95-94-95-57-0) +Home = [33268,31849,4] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N, student of the arcane arts." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N. Your time will come.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Use your knowledge wisely." + +"bye" -> "Use your knowledge wisely", Idle +"job" -> "I am a teacher of the most powerful spells in Tibia." +"name" -> "I am known in this world as Zoltan." +"time" -> "It's %T." +"king" -> "King Tibianus III was the founder of our academy." +"tibianus" -> * +"army" -> "They rely too much on their brawn instead of their brain." +"excalibug" -> "You will need no weapon if you manipulate the essence of magic." +"thais" -> "Thais is a place of barbary." +"tibia" -> "There is still much left to be explored in this world." +"carlin" -> "Carlin's druids waste the influence they have in enviromentalism." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I have no time for chit chat." +"rumors" -> * +"eremo" -> "He is an old and wise man that has seen a lot of Tibia. He is also one of the best magicians. Visit him on his little island." +"visit" -> "You should visit Eremo on his little island. Just ask Pemaret on Cormaya for passage." +"tibianus","talon" -> "We know not much yet. However, we are afraid for the variety of horrible ways of how they could be used." + +"yenny","gentle" -> "Ah, Yenny the Gentle was one of the founders of the druid order called Crunor's Caress, that has been originated in her hometown Carlin." +"yenny" -> "Yenny? Which Yenny? That is a common name." +"crunor","caress",QuestValue(211)=1 -> "A quite undruidic order of druids they were, as far as we know. I have no more enlightening knowledge about them though.",SetQuestValue(211,2) +"crunor","caress",QuestValue(211)>1 -> * +"crunor","caress",QuestValue(211)=0 -> "I am quite busy, ask another time!" + + +"spellbook" -> "Don't bother me with that. Ask in the shops for it." +"spell" -> "I have some very powerful spells: 'Energy Bomb', 'Mass Healing', 'Poison Storm', 'Paralyze', and 'Ultimate Explosion'." + +"energy","bomb",Sorcerer -> String="Energybomb", Price=2300, "Are you prepared to learn the spell 'Energy Bomb' for %P gold?", Topic=1 +"energy","bomb" -> "No, no, no. This dangerous spell is only for sorcerers." +"mass","healing",Druid -> String="Mass Healing", Price=2200, "Are you prepared to learn the spell 'Mass Healing' for %P gold?", Topic=1 +"mass","healing" -> "No, no, no. This elemental spell is only for druids." +"poison","storm",Druid -> String="Poison Storm", Price=3400, "Are you prepared to learn the spell 'Poison Storm' for %P gold?", Topic=1 +"poison","storm" -> "No, no, no. This elemental spell is only for druids." +"paralyze",Druid -> String="Paralyze", Price=1900, "Are you prepared to learn the spell 'Paralyze' for %P gold?", Topic=1 +"paralyze" -> "No, no, no. This elemental spell is only for druids." +"ultimate","explosion",Sorcerer -> String="Ultimate Explosion", Price=8000, "Are you prepared to learn the spell 'Ultimate Explosion' for %P gold?", Topic=1 +"ultimate","explosion" -> "No, no, no. This dangerous spell is only for sorcerers." + +Topic=1,"yes",SpellKnown(String)=1 -> "Want to fool me? You already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "Don't be in high spirits. Advance to level %A, and then come again." +Topic=1,"yes",CountMoney "Want to learn one of the most powerful spells, and don't even know how much money you have?" +Topic=1,"yes" -> "Congratulations. You now know one of the most powerful spells. Use it wisely.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Lost your heart?" + +"ferumbras", QuestValue(17513)>0 -> "I see, you managed to reach Kharos, the harbinger isle, and discovered the gates to Ferumbras' citadel, and now you are here full of questions. Are you ready to listen?", Topic=3 +Topic=3,"yes" -> "So know that destroying the mortal shell of the being called Ferumbras was the best we were able to achieve with our combined efforts in the past. ...", + "He was destroyed not only once but several times. Eventually we were able to figure out the secret of his seeming immortality. ...", + "On one of the most remote islands of the Shattered Isles, he built a citadel with demonic aid right around a powerful magical nexus. ...", + "The only reason for the whole complex was to establish a point of return into our world. Whenever he is slain, his soul retreats to some demonic dimension to regain enough strength to re-enter the world. ...", + "We were not able to destroy his citadel, this unholy construct. To make matters worse, the nexus makes it easy for demons of all kind to pass into our world. ...", + "The best thing we could do was to seal the citadel and to install a device that will alarm us whenever Ferumbras tries to re-enter our world. ...", + "We grant heroes like you the permission to pass our seals and enter Ferumbras' citadel. ...", + "Just ask for the permission if you are ready to go there. Be warned that the citadel is no holiday place though. You will encounter large amounts of demons and traps that scare off most adventurers. ...", + "On the other hand, WHEN Ferumbras re-enters the world we need heroes like you to face him on his very own ground before he can escape. ...", + "His return is not very likely but it can happen each and every day. If you should manage to defeat him, bring a proof of his death here and you will be rewarded." +Topic=3 -> "Lost your heart?" +"ferumbras", QuestValue(17513)=0 -> "A fallen sorcerer, indeed. What a shame." + +"permission",QuestValue(17513)=0 -> "I see no reason to discuss this matter with you." +"permission",QuestValue(17513)=1 -> Price=500, "The attuning to our seals is a costly process and it will grant you access to the citadel ONLY ONCE. Each time you want to enter, you will need a new attuning. Are you willing to pay 500 gold pieces to become attuned to the seal of the citadel?", Topic=2 +Topic=2,"yes",CountMoney>=Price -> "SO BE IT!", DeleteMoney, SetQuestValue(17513,2), EffectOpp(15) +Topic=2,"yes" -> "Don't even know how much money you have?" +Topic=2 -> "Lost your heart?" + +"permission",QuestValue(17513)=2 -> "You already have the permission to enter the citadel." + +"myra",QuestValue(17547)=11,male -> "Bah, I know. I received some sort of 'nomination' from our outpost in Port Hope. ...", + "Usually it takes a little more than that for an award though. However, I honour Myra's word. ...", + "I hereby grant you the right to wear a special sign of honour, acknowledged by the academy of Edron. Since you are a man, I guess you don't want girlish stuff. There you go.", SetQuestValue(17547,12), AddOutfitAddon(138,2), AddOutfitAddon(133,2), EffectOpp(13) + +"myra",QuestValue(17547)=11,female -> "Bah, I know. I received some sort of 'nomination' from our outpost in Port Hope. ...", + "Usually it takes a little more than that for an award though. However, I honour Myra's word.", SetQuestValue(17547,12), AddOutfitAddon(138,2), AddOutfitAddon(133,2), EffectOpp(13) + +"myra" -> "She was sent to Port Hope by the academy of Edron to function as an adviser in magical matters and as a teacher for sorcerers in need of training." + +"proof",QuestValue(17548)=0,Count(5903)>=1 -> "... I cannot believe my eyes. You retrieved this hat from Ferumbras' remains? That is incredible. If you give it to me, I will grant you the right to wear this hat as addon. What do you say?", Topic=4 +Topic=4,"yes" -> "I bow to you, %N, and hereby grant you the right to wear Ferumbras' hat as accessory. Congratulations!", DeleteAmount(5903, 1), SetQuestValue(17548,1), AddOutfitAddon(130,2), AddOutfitAddon(141,2), EffectOpp(13) +Topic=4 -> "Maybe another time." +"proof",QuestValue(17548)=0 -> "If you should manage to defeat Ferumbras, bring a proof of his death here and you will be rewarded." +"proof",QuestValue(17548)=1 -> "The Ferumbras death proof will remain for eternity to you." +} diff --git a/app/SabrehavenServer/data/raids/abdendrielbadgers.xml b/app/SabrehavenServer/data/raids/abdendrielbadgers.xml new file mode 100644 index 0000000..0e4a428 --- /dev/null +++ b/app/SabrehavenServer/data/raids/abdendrielbadgers.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/abdendrielwolfattack.xml b/app/SabrehavenServer/data/raids/abdendrielwolfattack.xml new file mode 100644 index 0000000..5c62242 --- /dev/null +++ b/app/SabrehavenServer/data/raids/abdendrielwolfattack.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/ankrahmunscarabinvasion.xml b/app/SabrehavenServer/data/raids/ankrahmunscarabinvasion.xml new file mode 100644 index 0000000..d8b2aea --- /dev/null +++ b/app/SabrehavenServer/data/raids/ankrahmunscarabinvasion.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/carlintowerorcs.xml b/app/SabrehavenServer/data/raids/carlintowerorcs.xml new file mode 100644 index 0000000..ed92619 --- /dev/null +++ b/app/SabrehavenServer/data/raids/carlintowerorcs.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam0.xml b/app/SabrehavenServer/data/raids/cavesgrorlam0.xml new file mode 100644 index 0000000..6639a7c --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam0.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam1.xml b/app/SabrehavenServer/data/raids/cavesgrorlam1.xml new file mode 100644 index 0000000..035da83 --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam1.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam2.xml b/app/SabrehavenServer/data/raids/cavesgrorlam2.xml new file mode 100644 index 0000000..0923366 --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam2.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam3.xml b/app/SabrehavenServer/data/raids/cavesgrorlam3.xml new file mode 100644 index 0000000..eb50904 --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam3.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam4.xml b/app/SabrehavenServer/data/raids/cavesgrorlam4.xml new file mode 100644 index 0000000..c40383e --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam4.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cavesgrorlam5.xml b/app/SabrehavenServer/data/raids/cavesgrorlam5.xml new file mode 100644 index 0000000..4987d83 --- /dev/null +++ b/app/SabrehavenServer/data/raids/cavesgrorlam5.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/cormayadwarfattack.xml b/app/SabrehavenServer/data/raids/cormayadwarfattack.xml new file mode 100644 index 0000000..6314aa0 --- /dev/null +++ b/app/SabrehavenServer/data/raids/cormayadwarfattack.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/darashiaundeadinvasion.xml b/app/SabrehavenServer/data/raids/darashiaundeadinvasion.xml new file mode 100644 index 0000000..4bdf4c4 --- /dev/null +++ b/app/SabrehavenServer/data/raids/darashiaundeadinvasion.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/darashiawaspplague.xml b/app/SabrehavenServer/data/raids/darashiawaspplague.xml new file mode 100644 index 0000000..24e279d --- /dev/null +++ b/app/SabrehavenServer/data/raids/darashiawaspplague.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/dracoriadieingdragons.xml b/app/SabrehavenServer/data/raids/dracoriadieingdragons.xml new file mode 100644 index 0000000..4c609b3 --- /dev/null +++ b/app/SabrehavenServer/data/raids/dracoriadieingdragons.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/drefianecromancer.xml b/app/SabrehavenServer/data/raids/drefianecromancer.xml new file mode 100644 index 0000000..3417e56 --- /dev/null +++ b/app/SabrehavenServer/data/raids/drefianecromancer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/edronorshabaal.xml b/app/SabrehavenServer/data/raids/edronorshabaal.xml new file mode 100644 index 0000000..6c3250c --- /dev/null +++ b/app/SabrehavenServer/data/raids/edronorshabaal.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/edronskunks.xml b/app/SabrehavenServer/data/raids/edronskunks.xml new file mode 100644 index 0000000..5215071 --- /dev/null +++ b/app/SabrehavenServer/data/raids/edronskunks.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/ferumbras.xml b/app/SabrehavenServer/data/raids/ferumbras.xml new file mode 100644 index 0000000..41cd74a --- /dev/null +++ b/app/SabrehavenServer/data/raids/ferumbras.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/raids/foldayetis.xml b/app/SabrehavenServer/data/raids/foldayetis.xml new file mode 100644 index 0000000..4ee0726 --- /dev/null +++ b/app/SabrehavenServer/data/raids/foldayetis.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/halloweenhare.xml b/app/SabrehavenServer/data/raids/halloweenhare.xml new file mode 100644 index 0000000..061d036 --- /dev/null +++ b/app/SabrehavenServer/data/raids/halloweenhare.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/kazordoonhornedfox.xml b/app/SabrehavenServer/data/raids/kazordoonhornedfox.xml new file mode 100644 index 0000000..c244a9d --- /dev/null +++ b/app/SabrehavenServer/data/raids/kazordoonhornedfox.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/kazordoonspiderplague.xml b/app/SabrehavenServer/data/raids/kazordoonspiderplague.xml new file mode 100644 index 0000000..d50decb --- /dev/null +++ b/app/SabrehavenServer/data/raids/kazordoonspiderplague.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/libertybaypirates.xml b/app/SabrehavenServer/data/raids/libertybaypirates.xml new file mode 100644 index 0000000..caebcca --- /dev/null +++ b/app/SabrehavenServer/data/raids/libertybaypirates.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/raids/mintwalinminogeneral.xml b/app/SabrehavenServer/data/raids/mintwalinminogeneral.xml new file mode 100644 index 0000000..ccf5d0f --- /dev/null +++ b/app/SabrehavenServer/data/raids/mintwalinminogeneral.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/mistisledruid.xml b/app/SabrehavenServer/data/raids/mistisledruid.xml new file mode 100644 index 0000000..99ec53d --- /dev/null +++ b/app/SabrehavenServer/data/raids/mistisledruid.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/morgaroth.xml b/app/SabrehavenServer/data/raids/morgaroth.xml new file mode 100644 index 0000000..de7210e --- /dev/null +++ b/app/SabrehavenServer/data/raids/morgaroth.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/data/raids/necropolisbeholder.xml b/app/SabrehavenServer/data/raids/necropolisbeholder.xml new file mode 100644 index 0000000..93ca69d --- /dev/null +++ b/app/SabrehavenServer/data/raids/necropolisbeholder.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/northroadoutlaws.xml b/app/SabrehavenServer/data/raids/northroadoutlaws.xml new file mode 100644 index 0000000..b330e3c --- /dev/null +++ b/app/SabrehavenServer/data/raids/northroadoutlaws.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/orclandorc.xml b/app/SabrehavenServer/data/raids/orclandorc.xml new file mode 100644 index 0000000..2d661de --- /dev/null +++ b/app/SabrehavenServer/data/raids/orclandorc.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/pohdemodras.xml b/app/SabrehavenServer/data/raids/pohdemodras.xml new file mode 100644 index 0000000..e8addab --- /dev/null +++ b/app/SabrehavenServer/data/raids/pohdemodras.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/pohwidow.xml b/app/SabrehavenServer/data/raids/pohwidow.xml new file mode 100644 index 0000000..e86fc7c --- /dev/null +++ b/app/SabrehavenServer/data/raids/pohwidow.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/raids.xml b/app/SabrehavenServer/data/raids/raids.xml new file mode 100644 index 0000000..59c40c0 --- /dev/null +++ b/app/SabrehavenServer/data/raids/raids.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/rookgaardrats.xml b/app/SabrehavenServer/data/raids/rookgaardrats.xml new file mode 100644 index 0000000..1a15db6 --- /dev/null +++ b/app/SabrehavenServer/data/raids/rookgaardrats.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/shadowthorndharalion.xml b/app/SabrehavenServer/data/raids/shadowthorndharalion.xml new file mode 100644 index 0000000..72ee27b --- /dev/null +++ b/app/SabrehavenServer/data/raids/shadowthorndharalion.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/stonehomeghoulattack.xml b/app/SabrehavenServer/data/raids/stonehomeghoulattack.xml new file mode 100644 index 0000000..5e28fe5 --- /dev/null +++ b/app/SabrehavenServer/data/raids/stonehomeghoulattack.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/SabrehavenServer/data/raids/thaiscaverats.xml b/app/SabrehavenServer/data/raids/thaiscaverats.xml new file mode 100644 index 0000000..7311310 --- /dev/null +++ b/app/SabrehavenServer/data/raids/thaiscaverats.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/thaislighthouseorcs.xml b/app/SabrehavenServer/data/raids/thaislighthouseorcs.xml new file mode 100644 index 0000000..e14907c --- /dev/null +++ b/app/SabrehavenServer/data/raids/thaislighthouseorcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/thaisorcinvasion.xml b/app/SabrehavenServer/data/raids/thaisorcinvasion.xml new file mode 100644 index 0000000..52e2066 --- /dev/null +++ b/app/SabrehavenServer/data/raids/thaisorcinvasion.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/venoreelfinvasion.xml b/app/SabrehavenServer/data/raids/venoreelfinvasion.xml new file mode 100644 index 0000000..79ac206 --- /dev/null +++ b/app/SabrehavenServer/data/raids/venoreelfinvasion.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/raids/venoreswampelves.xml b/app/SabrehavenServer/data/raids/venoreswampelves.xml new file mode 100644 index 0000000..7aa3ec8 --- /dev/null +++ b/app/SabrehavenServer/data/raids/venoreswampelves.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/spells/lib/spells.lua b/app/SabrehavenServer/data/spells/lib/spells.lua new file mode 100644 index 0000000..2c28f45 --- /dev/null +++ b/app/SabrehavenServer/data/spells/lib/spells.lua @@ -0,0 +1,230 @@ +function healingFormula(level, maglevel, base, variation, value_min, value_max) + local value = 3 * maglevel + (2 * level) + + if value_min ~= nil and value <= value_min then + value = value_min + end + + if value_max ~= nil and value >= value_max then + value = value_max + end + + local min = value * (base - variation) / 100 + local max = value * (base + variation) / 100 + return min, max +end + +function damageFormula(level, maglevel, base, variation) + local value = 3 * maglevel + (2 * level) + + local min = value * (base - variation) / 100 + local max = value * (base + variation) / 100 + return min, max +end + +function computeFormula(level, maglevel, base, variation) + local damage = base + if variation > 0 then + damage = math.random(-variation, variation) + damage + end + + local level_formula = 2 * level + local magic_formula = 3 * maglevel + level_formula + + return magic_formula * damage / 100 +end + +--------------------------------------------------------------------------------------- + +AREA_WAVE3 = { +{1, 1, 1}, +{1, 1, 1}, +{0, 3, 0} +} + +AREA_WAVE4 = { +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0}, +{0, 1, 1, 1, 0}, +{0, 0, 3, 0, 0} +} + +AREA_WAVE6 = { +{0, 0, 0, 0, 0}, +{0, 1, 3, 1, 0}, +{0, 0, 0, 0, 0} +} + +AREA_SQUAREWAVE5 = { +{1, 1, 1}, +{1, 1, 1}, +{1, 1, 1}, +{0, 1, 0}, +{0, 3, 0} +} + +AREA_SQUAREWAVE6 = { +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0} +} + +AREA_SQUAREWAVE7 = { +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0} +} + +--Diagonal waves +AREADIAGONAL_WAVE4 = { +{0, 0, 0, 0, 1, 0}, +{0, 0, 0, 1, 1, 0}, +{0, 0, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 0}, +{0, 0, 0, 0, 0, 3} +} + +AREADIAGONAL_SQUAREWAVE5 = { +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +AREADIAGONAL_WAVE6 = { +{0, 0, 1}, +{0, 3, 0}, +{1, 0, 0} +} + +--Beams +AREA_BEAM1 = { +{3} +} + +AREA_BEAM5 = { +{1}, +{1}, +{1}, +{1}, +{3} +} + +AREA_BEAM7 = { +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{3} +} + +AREA_BEAM8 = { +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{3} +} + +--Diagonal Beams +AREADIAGONAL_BEAM5 = { +{1, 0, 0, 0, 0}, +{0, 1, 0, 0, 0}, +{0, 0, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +AREADIAGONAL_BEAM7 = { +{1, 0, 0, 0, 0, 0, 0}, +{0, 1, 0, 0, 0, 0, 0}, +{0, 0, 1, 0, 0, 0, 0}, +{0, 0, 0, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 0, 0}, +{0, 0, 0, 0, 0, 1, 0}, +{0, 0, 0, 0, 0, 0, 3} +} + +--Circles +AREA_CIRCLE2X2 = { +{0, 1, 1, 1, 0}, +{1, 1, 1, 1, 1}, +{1, 1, 3, 1, 1}, +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0} +} + +AREA_CIRCLE3X3 = { +{0, 0, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 1, 1}, +{1, 1, 1, 3, 1, 1, 1}, +{1, 1, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 0, 0} +} + +-- Crosses +AREA_CROSS1X1 = { +{0, 1, 0}, +{1, 3, 1}, +{0, 1, 0} +} + +AREA_CIRCLE5X5 = { +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0} +} + +--Squares +AREA_SQUARE1X1 = { +{1, 1, 1}, +{1, 3, 1}, +{1, 1, 1} +} + +-- Walls +AREA_WALLFIELD = { +{1, 1, 3, 1, 1} +} + +AREADIAGONAL_WALLFIELD = { +{0, 0, 0, 0, 1}, +{0, 0, 0, 1, 1}, +{0, 1, 3, 1, 0}, +{1, 1, 0, 0, 0}, +{1, 0, 0, 0, 0}, +} \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/animate dead.lua b/app/SabrehavenServer/data/spells/scripts/runes/animate dead.lua new file mode 100644 index 0000000..c5e55c6 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/animate dead.lua @@ -0,0 +1,29 @@ +local humanBodies = { + 4240, 4241, 4247, 4248 +} + +function onCastSpell(creature, variant) + local position = Variant.getPosition(variant) + local tile = Tile(position) + if tile then + local corpse = tile:getTopDownItem() + if corpse then + local itemType = corpse:getType() + if not table.contains(humanBodies, itemType:getId()) then + if itemType:isCorpse() and itemType:isMovable() then + local monster = Game.createMonster("Skeleton", position) + if monster then + corpse:remove() + monster:setMaster(creature) + position:sendMagicEffect(CONST_ME_MAGIC_BLUE) + return true + end + end + end + end + end + + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/cure poison.lua b/app/SabrehavenServer/data/spells/scripts/runes/cure poison.lua new file mode 100644 index 0000000..444d92d --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/cure poison.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_TARGETCASTERORTOPMOST, true) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onCastSpell(creature, variant) + creature:removeCondition(CONDITION_POISON) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/destroy field.lua b/app/SabrehavenServer/data/spells/scripts/runes/destroy field.lua new file mode 100644 index 0000000..076cdc3 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/destroy field.lua @@ -0,0 +1,29 @@ +local fieldIds = { + 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, + 2126, 2127, 2131, 2132, 2133, 2134, 2135 +} + +function onCastSpell(creature, variant, isHotkey) + local position = Variant.getPosition(variant) + local tile = Tile(position) + local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD) + + if field and table.contains(fieldIds, field:getId()) then + field:remove() + position:sendMagicEffect(CONST_ME_POFF) + return true + end + + for _, id in ipairs(fieldIds) do + field = tile and tile:getItemById(id) + if field then + field:remove() + position:sendMagicEffect(CONST_ME_POFF) + return true + end + end + + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/disintegrate.lua b/app/SabrehavenServer/data/spells/scripts/runes/disintegrate.lua new file mode 100644 index 0000000..90a92dd --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/disintegrate.lua @@ -0,0 +1,26 @@ +local dead_human = { + 4240, 4241, 4242, 4247, 4248 +} +local removalLimit = 10 + +function onCastSpell(creature, variant) + local position = variant:getPosition() + local tile = Tile(position) + if tile then + local items = tile:getItems() + if items then + for i, item in ipairs(items) do + if item:getType():isMovable() and item:getActionId() == 0 and not table.contains(dead_human, item:getId()) then + item:remove() + end + + if i == removalLimit then + break + end + end + end + end + + position:sendMagicEffect(CONST_ME_POFF) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/energy field.lua b/app/SabrehavenServer/data/spells/scripts/runes/energy field.lua new file mode 100644 index 0000000..84dbafb --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/energy field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/energy wall.lua b/app/SabrehavenServer/data/spells/scripts/runes/energy wall.lua new file mode 100644 index 0000000..ed33b55 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/energy wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/energybomb.lua b/app/SabrehavenServer/data/spells/scripts/runes/energybomb.lua new file mode 100644 index 0000000..77ef1ec --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/energybomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/envenom.lua b/app/SabrehavenServer/data/spells/scripts/runes/envenom.lua new file mode 100644 index 0000000..5947017 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/envenom.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYPOISON) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_POISON) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 70, 20)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/explosion.lua b/app/SabrehavenServer/data/spells/scripts/runes/explosion.lua new file mode 100644 index 0000000..84b113e --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/explosion.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) +combat:setArea(createCombatArea(AREA_CROSS1X1)) + +function onGetFormulaValues(player, level, maglevel) + local base = 60 + local variation = 40 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/fire field.lua b/app/SabrehavenServer/data/spells/scripts/runes/fire field.lua new file mode 100644 index 0000000..4743641 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/fire field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/fire wall.lua b/app/SabrehavenServer/data/spells/scripts/runes/fire wall.lua new file mode 100644 index 0000000..f75ab13 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/fire wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/fireball.lua b/app/SabrehavenServer/data/spells/scripts/runes/fireball.lua new file mode 100644 index 0000000..0225655 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/fireball.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setArea(createCombatArea(AREA_CIRCLE2X2)) + +function onGetFormulaValues(player, level, maglevel) + local base = 20 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/firebomb.lua b/app/SabrehavenServer/data/spells/scripts/runes/firebomb.lua new file mode 100644 index 0000000..90ca33f --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/firebomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/great fireball.lua b/app/SabrehavenServer/data/spells/scripts/runes/great fireball.lua new file mode 100644 index 0000000..3da1f52 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/great fireball.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +function onGetFormulaValues(player, level, maglevel) + local base = 50 + local variation = 15 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/heavy magic missile.lua b/app/SabrehavenServer/data/spells/scripts/runes/heavy magic missile.lua new file mode 100644 index 0000000..107cd40 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/heavy magic missile.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +function onGetFormulaValues(player, level, maglevel) + local base = 30 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/intense healing.lua b/app/SabrehavenServer/data/spells/scripts/runes/intense healing.lua new file mode 100644 index 0000000..37f4d0a --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/intense healing.lua @@ -0,0 +1,15 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + return healingFormula(level, maglevel, 70, 30, 100) +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/light magic missile.lua b/app/SabrehavenServer/data/spells/scripts/runes/light magic missile.lua new file mode 100644 index 0000000..65fb7bb --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/light magic missile.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +function onGetFormulaValues(player, level, maglevel) + local base = 15 + local variation = 5 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/magic wall.lua b/app/SabrehavenServer/data/spells/scripts/runes/magic wall.lua new file mode 100644 index 0000000..25a7c05 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/magic wall.lua @@ -0,0 +1,7 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2128) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/paralyze.lua b/app/SabrehavenServer/data/spells/scripts/runes/paralyze.lua new file mode 100644 index 0000000..9248c01 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/paralyze.lua @@ -0,0 +1,16 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local condition = Condition(CONDITION_PARALYZE) +condition:setParameter(CONDITION_PARAM_TICKS, 10000) +condition:setSpeedDelta(-101) +combat:setCondition(condition) + +function onCastSpell(creature, variant, isHotkey) + if not combat:execute(creature, variant) then + return false + end + + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/poison bomb.lua b/app/SabrehavenServer/data/spells/scripts/runes/poison bomb.lua new file mode 100644 index 0000000..4e75d3b --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/poison bomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/poison field.lua b/app/SabrehavenServer/data/spells/scripts/runes/poison field.lua new file mode 100644 index 0000000..c895b87 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/poison field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/poison wall.lua b/app/SabrehavenServer/data/spells/scripts/runes/poison wall.lua new file mode 100644 index 0000000..4669236 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/poison wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/soulfire.lua b/app/SabrehavenServer/data/spells/scripts/runes/soulfire.lua new file mode 100644 index 0000000..33ad3e6 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/soulfire.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_FIRE) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 120, 20)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/sudden death.lua b/app/SabrehavenServer/data/spells/scripts/runes/sudden death.lua new file mode 100644 index 0000000..4911c5d --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/sudden death.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_DEATH) + +function onGetFormulaValues(player, level, maglevel) + local base = 150 + local variation = 20 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/runes/ultimate healing.lua b/app/SabrehavenServer/data/spells/scripts/runes/ultimate healing.lua new file mode 100644 index 0000000..9274f21 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/runes/ultimate healing.lua @@ -0,0 +1,16 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_TARGETCASTERORTOPMOST, true) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + return healingFormula(level, maglevel, 250, 0, 100) +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/antidote.lua b/app/SabrehavenServer/data/spells/scripts/spells/antidote.lua new file mode 100644 index 0000000..abecde8 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/antidote.lua @@ -0,0 +1,8 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onCastSpell(creature, variant) + creature:removeCondition(CONDITION_POISON) + return combat:execute(creature, variant) +end diff --git a/app/SabrehavenServer/data/spells/scripts/spells/berserk.lua b/app/SabrehavenServer/data/spells/scripts/spells/berserk.lua new file mode 100644 index 0000000..67e46ec --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/berserk.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onGetFormulaValues(player, level, maglevel) + local base = 80 + local variation = 20 + + local formula = 4 * level + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/cancel invisibility.lua b/app/SabrehavenServer/data/spells/scripts/spells/cancel invisibility.lua new file mode 100644 index 0000000..4daab2d --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/cancel invisibility.lua @@ -0,0 +1,8 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_INVISIBLE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/challenge.lua b/app/SabrehavenServer/data/spells/scripts/spells/challenge.lua new file mode 100644 index 0000000..4cdc09e --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/challenge.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onTargetCreature(creature, target) + return doChallengeCreature(creature, target) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/energy beam.lua b/app/SabrehavenServer/data/spells/scripts/spells/energy beam.lua new file mode 100644 index 0000000..2858301 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/energy beam.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setArea(createCombatArea(AREA_BEAM5, AREADIAGONAL_BEAM5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 60 + local variation = 20 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/energy strike.lua b/app/SabrehavenServer/data/spells/scripts/spells/energy strike.lua new file mode 100644 index 0000000..91a9d90 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/energy strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_TELEPORT) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/energy wave.lua b/app/SabrehavenServer/data/spells/scripts/spells/energy wave.lua new file mode 100644 index 0000000..158850a --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/energy wave.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_TELEPORT) +combat:setArea(createCombatArea(AREA_SQUAREWAVE5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 150 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/fire wave.lua b/app/SabrehavenServer/data/spells/scripts/spells/fire wave.lua new file mode 100644 index 0000000..904aeee --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/fire wave.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +combat:setArea(createCombatArea(AREA_WAVE4, AREADIAGONAL_WAVE4)) + +function onGetFormulaValues(player, level, maglevel) + local base = 30 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/flame strike.lua b/app/SabrehavenServer/data/spells/scripts/spells/flame strike.lua new file mode 100644 index 0000000..cd9eb53 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/flame strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/food.lua b/app/SabrehavenServer/data/spells/scripts/spells/food.lua new file mode 100644 index 0000000..bf185ec --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/food.lua @@ -0,0 +1,9 @@ +local food = { + 3577, 3582, 3585, 3592, 3602 +} + +function onCastSpell(creature, variant) + creature:addItem(food[math.random(#food)]) + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/force strike.lua b/app/SabrehavenServer/data/spells/scripts/spells/force strike.lua new file mode 100644 index 0000000..e693a52 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/force strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/great energy beam.lua b/app/SabrehavenServer/data/spells/scripts/spells/great energy beam.lua new file mode 100644 index 0000000..a5de443 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/great energy beam.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +combat:setArea(createCombatArea(AREA_BEAM8)) + +function onGetFormulaValues(player, level, maglevel) + local base = 120 + local variation = 80 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/great light.lua b/app/SabrehavenServer/data/spells/scripts/spells/great light.lua new file mode 100644 index 0000000..25e8be0 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/great light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 8) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (11 * 60 + 35) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/haste.lua b/app/SabrehavenServer/data/spells/scripts/spells/haste.lua new file mode 100644 index 0000000..ac6cdf5 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/haste.lua @@ -0,0 +1,12 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_HASTE) +condition:setParameter(CONDITION_PARAM_TICKS, 30000) +condition:setSpeedDelta(30) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/heal friend.lua b/app/SabrehavenServer/data/spells/scripts/spells/heal friend.lua new file mode 100644 index 0000000..eff6de3 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/heal friend.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 120 + local variation = 40 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/intense healing.lua b/app/SabrehavenServer/data/spells/scripts/spells/intense healing.lua new file mode 100644 index 0000000..95ff7e6 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/intense healing.lua @@ -0,0 +1,15 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + return healingFormula(level, maglevel, 40, 20, 100) +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/invisible.lua b/app/SabrehavenServer/data/spells/scripts/spells/invisible.lua new file mode 100644 index 0000000..f538e45 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/invisible.lua @@ -0,0 +1,11 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_INVISIBLE) +condition:setParameter(CONDITION_PARAM_TICKS, 200000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/light healing.lua b/app/SabrehavenServer/data/spells/scripts/spells/light healing.lua new file mode 100644 index 0000000..ecce6f2 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/light healing.lua @@ -0,0 +1,15 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + return healingFormula(level, maglevel, 20, 10, 100) +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/light.lua b/app/SabrehavenServer/data/spells/scripts/spells/light.lua new file mode 100644 index 0000000..dd9e624 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 6) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (6 * 60 + 10) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/magic rope.lua b/app/SabrehavenServer/data/spells/scripts/spells/magic rope.lua new file mode 100644 index 0000000..0efdd4e --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/magic rope.lua @@ -0,0 +1,23 @@ +local ropeSpots = { + 386, 421 +} + +function onCastSpell(creature, variant) + local position = creature:getPosition() + position:sendMagicEffect(CONST_ME_POFF) + + local tile = Tile(position) + if table.contains(ropeSpots, tile:getGround():getId()) then + tile = Tile(position:moveUpstairs()) + if tile then + creature:teleportTo(position) + position:sendMagicEffect(CONST_ME_TELEPORT) + else + creature:sendCancelMessage(RETURNVALUE_NOTENOUGHROOM) + end + else + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/magic shield.lua b/app/SabrehavenServer/data/spells/scripts/spells/magic shield.lua new file mode 100644 index 0000000..86a5b4d --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/magic shield.lua @@ -0,0 +1,11 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_MANASHIELD) +condition:setParameter(CONDITION_PARAM_TICKS, 200000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/mass healing.lua b/app/SabrehavenServer/data/spells/scripts/spells/mass healing.lua new file mode 100644 index 0000000..21eb5de --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/mass healing.lua @@ -0,0 +1,34 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +local healMonsters = true + +function onTargetCreature(creature, target) + if not healMonsters then + local master = target:getMaster() + if target:isMonster() and not master or master and master:isMonster() then + return true + end + end + + local player = creature:getPlayer() + + local base = 200 + local variation = 40 + + local value = math.random(-variation, variation) + base + local formula = 3 * player:getMagicLevel() + (2 * player:getLevel()) + local total = formula * value / 100 + + doTargetCombatHealth(0, target, COMBAT_HEALING, total, total, CONST_ME_NONE) + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/poison storm.lua b/app/SabrehavenServer/data/spells/scripts/spells/poison storm.lua new file mode 100644 index 0000000..25ec6b8 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/poison storm.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_POISON) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 200, 50)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/poison strike.lua b/app/SabrehavenServer/data/spells/scripts/spells/poison strike.lua new file mode 100644 index 0000000..906dc58 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/poison strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/poison wave.lua b/app/SabrehavenServer/data/spells/scripts/spells/poison wave.lua new file mode 100644 index 0000000..b2893d5 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/poison wave.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setArea(createCombatArea(AREA_SQUAREWAVE5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 150 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/strong haste.lua b/app/SabrehavenServer/data/spells/scripts/spells/strong haste.lua new file mode 100644 index 0000000..9b08a62 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/strong haste.lua @@ -0,0 +1,12 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_HASTE) +condition:setParameter(CONDITION_PARAM_TICKS, 20000) +condition:setSpeedDelta(70) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/ultimate explosion.lua b/app/SabrehavenServer/data/spells/scripts/spells/ultimate explosion.lua new file mode 100644 index 0000000..8c19f46 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/ultimate explosion.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1) +combat:setParameter(COMBAT_PARAM_BLOCKSHIELD, 1) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 250 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/ultimate healing.lua b/app/SabrehavenServer/data/spells/scripts/spells/ultimate healing.lua new file mode 100644 index 0000000..436efff --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/ultimate healing.lua @@ -0,0 +1,15 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + return healingFormula(level, maglevel, 250, 50, 100) +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/ultimate light.lua b/app/SabrehavenServer/data/spells/scripts/spells/ultimate light.lua new file mode 100644 index 0000000..254d2d1 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/ultimate light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 9) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (60 * 33 + 10) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/undead legion.lua b/app/SabrehavenServer/data/spells/scripts/spells/undead legion.lua new file mode 100644 index 0000000..b5d9dd3 --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/undead legion.lua @@ -0,0 +1,34 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +local humanBodies = { + 4240, 4241, 4247, 4248 +} + +function onCastSpell(creature, variant) + local position = Variant.getPosition(variant) + local tile = Tile(position) + if tile then + local corpse = tile:getTopDownItem() + if corpse then + local itemType = corpse:getType() + if not table.contains(humanBodies, itemType:getId()) then + if itemType:isCorpse() and itemType:isMovable() then + local monster = Game.createMonster("Skeleton", position) + if monster then + corpse:remove() + monster:setMaster(creature) + position:sendMagicEffect(CONST_ME_MAGIC_BLUE) + return true + end + end + end + end + end + + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/scripts/spells/wild growth.lua b/app/SabrehavenServer/data/spells/scripts/spells/wild growth.lua new file mode 100644 index 0000000..c865b0d --- /dev/null +++ b/app/SabrehavenServer/data/spells/scripts/spells/wild growth.lua @@ -0,0 +1,7 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2130) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/spells/spells.xml b/app/SabrehavenServer/data/spells/spells.xml new file mode 100644 index 0000000..71c9945 --- /dev/null +++ b/app/SabrehavenServer/data/spells/spells.xml @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/talkactions/lib/talkactions.lua b/app/SabrehavenServer/data/talkactions/lib/talkactions.lua new file mode 100644 index 0000000..585eb19 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/lib/talkactions.lua @@ -0,0 +1 @@ +-- Nothing -- diff --git a/app/SabrehavenServer/data/talkactions/scripts/469.lua b/app/SabrehavenServer/data/talkactions/scripts/469.lua new file mode 100644 index 0000000..a3fdb57 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/469.lua @@ -0,0 +1,47 @@ +local area = { + fromPos = {x = 33144, y = 32863, z = 7}, + toPos = {x = 33154, y = 32873, z = 7} +} + +function onSay(player, words, param) + if player:getPosition():isInRange(area.fromPos, area.toPos) then + local tile1 = Tile(Position(33147, 32868, 7)) + if tile1 then + local stoneRailing = tile1:getItemById(2212) + if stoneRailing ~= nil then + stoneRailing:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + stoneRailing:remove() + broadcastMessage("The player " .. player:getName() .. " have opened The Serpentine Tower!", MESSAGE_STATUS_WARNING) + end + end + + local tile2 = Tile(Position(33149, 32866, 7)) + if tile2 then + local stoneRailing = tile2:getItemById(2210) + if stoneRailing ~= nil then + stoneRailing:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + stoneRailing:remove() + end + end + + local tile3 = Tile(Position(33151, 32868, 7)) + if tile3 then + local stoneRailing = tile3:getItemById(2211) + if stoneRailing ~= nil then + stoneRailing:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + stoneRailing:remove() + end + end + + local tile4 = Tile(Position(33149, 32870, 7)) + if tile4 then + local stoneRailing = tile4:getItemById(2209) + if stoneRailing ~= nil then + stoneRailing:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + stoneRailing:remove() + end + end + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/add_skill.lua b/app/SabrehavenServer/data/talkactions/scripts/add_skill.lua new file mode 100644 index 0000000..f9b83e5 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/add_skill.lua @@ -0,0 +1,66 @@ +local function getSkillId(skillName) + if skillName == "club" then + return SKILL_CLUB + elseif skillName == "sword" then + return SKILL_SWORD + elseif skillName == "axe" then + return SKILL_AXE + elseif skillName:sub(1, 4) == "dist" then + return SKILL_DISTANCE + elseif skillName:sub(1, 6) == "shield" then + return SKILL_SHIELD + elseif skillName:sub(1, 4) == "fish" then + return SKILL_FISHING + else + return SKILL_FIST + end +end + +local function getExpForLevel(level) + level = level - 1 + return ((50 * level * level * level) - (150 * level * level) + (400 * level)) / 3 +end + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local split = param:split(",") + if split[2] == nil then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if target == nil then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + -- Trim left + split[2] = split[2]:gsub("^%s*(.-)$", "%1") + + local count = 1 + if split[3] ~= nil then + count = tonumber(split[3]) + end + + local ch = split[2]:sub(1, 1) + for i = 1, count do + if ch == "l" or ch == "e" then + target:addExperience(getExpForLevel(target:getLevel() + 1) - target:getExperience(), false) + elseif ch == "m" then + target:addManaSpent(target:getVocation():getRequiredManaSpent(target:getBaseMagicLevel() + 1) - target:getManaSpent()) + else + local skillId = getSkillId(split[2]) + target:addSkillTries(skillId, target:getVocation():getRequiredSkillTries(skillId, target:getSkillLevel(skillId) + 1) - target:getSkillTries(skillId)) + end + end + + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/add_tutor.lua b/app/SabrehavenServer/data/talkactions/scripts/add_tutor.lua new file mode 100644 index 0000000..621b6e8 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/add_tutor.lua @@ -0,0 +1,21 @@ +function onSay(player, words, param) + if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + return true + end + + local target = Player(param) + if target == nil then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + if target:getAccountType() ~= ACCOUNT_TYPE_NORMAL then + player:sendCancelMessage("You can only promote a normal player to a tutor.") + return false + end + + target:setAccountType(ACCOUNT_TYPE_TUTOR) + target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have been promoted to a tutor by " .. player:getName() .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have promoted " .. target:getName() .. " to a tutor.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/animationeffect.lua b/app/SabrehavenServer/data/talkactions/scripts/animationeffect.lua new file mode 100644 index 0000000..ba55558 --- /dev/null +++ b/app/SabrehavenServer/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/app/SabrehavenServer/data/talkactions/scripts/ban.lua b/app/SabrehavenServer/data/talkactions/scripts/ban.lua new file mode 100644 index 0000000..21bd0b0 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/ban.lua @@ -0,0 +1,57 @@ +local banDays = 7 + +function onSay(cid, words, param) + local player = Player(cid) + if not player:getGroup():getAccess() then + return true + end + + local name = param + local reason = '' + local banInfo = '' + local banTime = 0 + local banMultiplier = 0 + local params = param:split(',') + if params ~= nil then + name = params[1] + reason = string.trim(params[2]) + banInfo = string.trim(params[3]) + print(banInfo) + end + if banInfo then + if banInfo:find('h') then + banTime = banInfo:sub(0, banInfo:find('h') - 1) + banMultiplier = 3600 + elseif banInfo:find('d') then + banTime = banInfo:sub(0, banInfo:find('d') - 1) + banMultiplier = 86400 + else + banTime = banDays + banMultiplier = 86400 + end + banTime = banTime * banMultiplier + end + + local accountId = getAccountNumberByPlayerName(name) + if accountId == 0 then + return false + end + + local resultId = db.storeQuery("SELECT 1 FROM `account_bans` WHERE `account_id` = " .. accountId) + if resultId ~= false then + result.free(resultId) + return false + end + + local timeNow = os.time() + db:query("INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" .. + accountId .. ", " .. db.escapeString(reason) .. ", " .. timeNow .. ", " .. timeNow + banTime .. ", " .. player:getGuid() .. ")") + + local target = Player(name) + if target ~= nil then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, target:getName() .. " has been banned.") + target:remove() + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, name .. " has been banned.") + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/broadcast.lua b/app/SabrehavenServer/data/talkactions/scripts/broadcast.lua new file mode 100644 index 0000000..a6e3933 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/broadcast.lua @@ -0,0 +1,11 @@ +function onSay(player, words, param) + if not getPlayerFlagValue(player, PlayerFlag_CanBroadcast) then + return true + end + + print("> " .. player:getName() .. " broadcasted: \"" .. param .. "\".") + for _, targetPlayer in ipairs(Game.getPlayers()) do + targetPlayer:sendTextMessage(MESSAGE_STATUS_WARNING, param) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/buyhouse.lua b/app/SabrehavenServer/data/talkactions/scripts/buyhouse.lua new file mode 100644 index 0000000..7141ba4 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/buyhouse.lua @@ -0,0 +1,4 @@ +function onSay(player, words, param) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Buying a house is available only through auction which is available at website: https://tibianus.com/house.php") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/buyprem.lua b/app/SabrehavenServer/data/talkactions/scripts/buyprem.lua new file mode 100644 index 0000000..1aa4f5f --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/buyprem.lua @@ -0,0 +1,25 @@ +local config = { + days = 90, + maxDays = 365, + price = 10000 +} + +function onSay(player, words, param) + if configManager.getBoolean(configKeys.FREE_PREMIUM) then + return true + end + + if player:getPremiumDays() <= config.maxDays then + if player:removeMoney(config.price) then + player:addPremiumDays(config.days) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have bought " .. config.days .." days of premium account.") + else + player:sendCancelMessage("You don't have enough money, " .. config.maxDays .. " days premium account costs " .. config.price .. " gold coins.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + else + player:sendCancelMessage("You can not buy more than " .. config.maxDays .. " days of premium account.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/chameleon.lua b/app/SabrehavenServer/data/talkactions/scripts/chameleon.lua new file mode 100644 index 0000000..d3fcbe2 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/chameleon.lua @@ -0,0 +1,25 @@ +local condition = Condition(CONDITION_OUTFIT, CONDITIONID_COMBAT) +condition:setTicks(-1) + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local itemType = ItemType(param) + if itemType:getId() == 0 then + itemType = ItemType(tonumber(param)) + if itemType:getId() == 0 then + player:sendCancelMessage("There is no item with that id or name.") + return false + end + end + + condition:setOutfit(itemType:getId()) + player:addCondition(condition) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/changesex.lua b/app/SabrehavenServer/data/talkactions/scripts/changesex.lua new file mode 100644 index 0000000..c8afee9 --- /dev/null +++ b/app/SabrehavenServer/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/app/SabrehavenServer/data/talkactions/scripts/clean.lua b/app/SabrehavenServer/data/talkactions/scripts/clean.lua new file mode 100644 index 0000000..0d5f7aa --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/clean.lua @@ -0,0 +1,15 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local itemCount = cleanMap() + if itemCount > 0 then + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Cleaned " .. itemCount .. " item" .. (itemCount > 1 and "s" or "") .. " from the map.") + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/closeserver.lua b/app/SabrehavenServer/data/talkactions/scripts/closeserver.lua new file mode 100644 index 0000000..2f7c95e --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/closeserver.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + if param == "shutdown" then + Game.setGameState(GAME_STATE_SHUTDOWN) + else + Game.setGameState(GAME_STATE_CLOSED) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now closed.") + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/create_item.lua b/app/SabrehavenServer/data/talkactions/scripts/create_item.lua new file mode 100644 index 0000000..ddc37e6 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/create_item.lua @@ -0,0 +1,59 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local split = param:split(",") + + local itemType = ItemType(split[1]) + if itemType:getId() == 0 then + itemType = ItemType(tonumber(split[1])) + if itemType:getId() == 0 then + player:sendCancelMessage("There is no item with that id or name.") + return false + end + end + + local keynumber = 0 + local count = tonumber(split[2]) + if count ~= nil then + if itemType:isStackable() then + count = math.min(10000, math.max(1, count)) + elseif itemType:isKey() then + keynumber = count + count = 1 + elseif not itemType:hasSubType() then + count = math.min(100, math.max(1, count)) + else + count = math.max(1, count) + end + else + count = 1 + end + + local result = player:addItem(itemType:getId(), count) + if result ~= nil then + if not itemType:isStackable() then + if type(result) == "table" then + for _, item in ipairs(result) do + if itemType:isKey() then + item:setAttribute(ITEM_ATTRIBUTE_KEYNUMBER, keynumber) + end + item:decay() + end + else + if itemType:isKey() then + result:setAttribute(ITEM_ATTRIBUTE_KEYNUMBER, keynumber) + end + result:decay() + end + end + + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/deathlist.lua b/app/SabrehavenServer/data/talkactions/scripts/deathlist.lua new file mode 100644 index 0000000..c1066a9 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/deathlist.lua @@ -0,0 +1,62 @@ +local function getArticle(str) + return str:find("[AaEeIiOoUuYy]") == 1 and "an" or "a" +end + +local function getMonthDayEnding(day) + if day == "01" or day == "21" or day == "31" then + return "st" + elseif day == "02" or day == "22" then + return "nd" + elseif day == "03" or day == "23" then + return "rd" + else + return "th" + end +end + +local function getMonthString(m) + return os.date("%B", os.time{year = 1970, month = m, day = 1}) +end + +function onSay(player, words, param) + local resultId = db.storeQuery("SELECT `id`, `name` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId ~= false then + local targetGUID = result.getDataInt(resultId, "id") + local targetName = result.getDataString(resultId, "name") + result.free(resultId) + local str = "" + local breakline = "" + + local resultId = db.storeQuery("SELECT `time`, `level`, `killed_by`, `is_player` FROM `player_deaths` WHERE `player_id` = " .. targetGUID .. " ORDER BY `time` DESC") + if resultId ~= false then + repeat + if str ~= "" then + breakline = "\n" + end + local date = os.date("*t", result.getDataInt(resultId, "time")) + + local article = "" + local killed_by = result.getDataString(resultId, "killed_by") + if result.getDataInt(resultId, "is_player") == 0 then + article = getArticle(killed_by) .. " " + killed_by = string.lower(killed_by) + end + + if date.day < 10 then date.day = "0" .. date.day end + if date.hour < 10 then date.hour = "0" .. date.hour end + if date.min < 10 then date.min = "0" .. date.min end + if date.sec < 10 then date.sec = "0" .. date.sec end + str = str .. breakline .. " " .. date.day .. getMonthDayEnding(date.day) .. " " .. getMonthString(date.month) .. " " .. date.year .. " " .. date.hour .. ":" .. date.min .. ":" .. date.sec .. " Died at Level " .. result.getDataInt(resultId, "level") .. " by " .. article .. killed_by .. "." + until not result.next(resultId) + result.free(resultId) + end + + if str == "" then + str = "No deaths." + end + player:popupFYI("Deathlist for player, " .. targetName .. ".\n\n" .. str) + else + player:sendCancelMessage("A player with that name does not exist.") + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/down.lua b/app/SabrehavenServer/data/talkactions/scripts/down.lua new file mode 100644 index 0000000..7a1d986 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/down.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position.z = position.z + 1 + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/experienceshare.lua b/app/SabrehavenServer/data/talkactions/scripts/experienceshare.lua new file mode 100644 index 0000000..320d577 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/experienceshare.lua @@ -0,0 +1,28 @@ +function onSay(player, words, param) + local party = player:getParty() + if not party then + player:sendCancelMessage("You are not part of a party.") + return false + end + + if party:getLeader() ~= player then + player:sendCancelMessage("You are not the leader of the party.") + return false + end + + if party:isSharedExperienceActive() then + if player:getCondition(CONDITION_INFIGHT) then + player:sendCancelMessage("You are in fight. Experience sharing not disabled.") + else + party:setSharedExperience(false) + end + else + if player:getCondition(CONDITION_INFIGHT) then + player:sendCancelMessage("You are in fight. Experience sharing not enabled.") + else + party:setSharedExperience(true) + end + end + + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/ghost.lua b/app/SabrehavenServer/data/talkactions/scripts/ghost.lua new file mode 100644 index 0000000..30f06b2 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/ghost.lua @@ -0,0 +1,22 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + return false + end + + local position = player:getPosition() + local isGhost = not player:isInGhostMode() + + player:setGhostMode(isGhost) + if isGhost then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are now invisible.") + position:sendMagicEffect(CONST_ME_POFF) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are visible again.") + position:sendMagicEffect(CONST_ME_TELEPORT) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/global_rate_boost.lua b/app/SabrehavenServer/data/talkactions/scripts/global_rate_boost.lua new file mode 100644 index 0000000..e4a3ca6 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/global_rate_boost.lua @@ -0,0 +1,48 @@ +local config = { + ['exp'] = {skillKey = 17585, timeKey = 17589}, + ['skill'] = {skillKey = 17586, timeKey = 17590}, + ['magic'] = {skillKey = 17587, timeKey = 17591}, + ['loot'] = {skillKey = 17588, timeKey = 17592} -- TODO +} + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local split = param:split(",") + if split[3] == nil then + player:sendCancelMessage("Insufficient parameters [(exp,skill,loot,magic),percentage,hours].") + return false + end + + local skillName = split[1] + local percentage = tonumber(split[2]) + local hours = tonumber(split[3]) + + local globalStorage = config[skillName] + if not globalStorage then + player:sendCancelMessage("Skill name value must be one of the following: exp, skill, loot, magic.") + return false + end + + if percentage <= 0 then + player:sendCancelMessage("Percentage value must be higher than 0. For example, 50% means 1.5x higher rate.") + return false + end + + if hours <= 0 then + player:sendCancelMessage("Hours value must be higher than 0.") + return false + end + + setGlobalStorageValue(globalStorage.skillKey, percentage) + setGlobalStorageValue(globalStorage.timeKey, os.time() + hours * 60 * 60) + broadcastMessage(player:getName() .. " have activated the global " .. percentage .. "% " .. skillName .. " rate boost for next " .. hours .. " " .. (hours == 1 and "hour" or "hours") .. ".", MESSAGE_STATUS_WARNING) + + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/impersonate.lua b/app/SabrehavenServer/data/talkactions/scripts/impersonate.lua new file mode 100644 index 0000000..67b3593 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/impersonate.lua @@ -0,0 +1,20 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local split = param:split(",") + local target = Player(split[1]) + if target == nil then + player:sendCancelMessage("Player not found.") + return false + end + + if target:getGroup():getAccess() then + player:sendCancelMessage("You cannot impersonate this player.") + return false + end + + target:say(split[2], TALKTYPE_SAY) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/info.lua b/app/SabrehavenServer/data/talkactions/scripts/info.lua new file mode 100644 index 0000000..3708741 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/info.lua @@ -0,0 +1,39 @@ +function onSay(player, words, param) + if player:getAccountType() == ACCOUNT_TYPE_NORMAL then + return true + end + + local target = Player(param) + if not target then + player:sendCancelMessage("Player not found.") + return false + end + + if target:getAccountType() > player:getAccountType() then + player:sendCancelMessage("You can not get info about this player.") + return false + end + + local targetIp = target:getIp() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Name: " .. target:getName()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) + if player:getGroup():getAccess() then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Level: " .. target:getLevel()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Magic Level: " .. target:getMagicLevel()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Speed: " .. target:getSpeed()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) + end + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "IP: " .. Game.convertIpToString(targetIp)) + + local players = {} + for _, targetPlayer in ipairs(Game.getPlayers()) do + if targetPlayer:getIp() == targetIp and targetPlayer ~= target then + players[#players + 1] = targetPlayer:getName() .. " [" .. targetPlayer:getLevel() .. "]" + end + end + + if #players > 0 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Other players on same IP: " .. table.concat(players, ", ") .. ".") + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/ipban.lua b/app/SabrehavenServer/data/talkactions/scripts/ipban.lua new file mode 100644 index 0000000..1a393e2 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/ipban.lua @@ -0,0 +1,36 @@ +local ipBanDays = 7 + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + return false + end + + local targetIp = result.getDataLong(resultId, "lastip") + result.free(resultId) + + local targetPlayer = Player(param) + if targetPlayer then + targetIp = targetPlayer:getIp() + targetPlayer:remove() + end + + if targetIp == 0 then + return false + end + + resultId = db.storeQuery("SELECT 1 FROM `ip_bans` WHERE `ip` = " .. targetIp) + if resultId ~= false then + result.free(resultId) + return false + end + + local timeNow = os.time() + db.query("INSERT INTO `ip_bans` (`ip`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" .. + targetIp .. ", '', " .. timeNow .. ", " .. timeNow + (ipBanDays * 86400) .. ", " .. player:getGuid() .. ")") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/kick.lua b/app/SabrehavenServer/data/talkactions/scripts/kick.lua new file mode 100644 index 0000000..67f8ec4 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/kick.lua @@ -0,0 +1,19 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local target = Player(param) + if target == nil then + player:sendCancelMessage("Player not found.") + return false + end + + if target:getGroup():getAccess() then + player:sendCancelMessage("You cannot kick this player.") + return false + end + + target:remove() + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/kills.lua b/app/SabrehavenServer/data/talkactions/scripts/kills.lua new file mode 100644 index 0000000..8dcee05 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/kills.lua @@ -0,0 +1,75 @@ +function onSay(player, words, param) + if Game.getWorldType() == WORLD_TYPE_PVP_ENFORCED then + player:showTextDialog(1998, "Your character has not murders history.", false) + return false + end + + local today = os.time() + local skullTicks = player:getPlayerKillerEnd() + local lastDay = 0 + local lastWeek = 0 + local lastMonth = 0 + local egibleMurders = 0 + local dayTimestamp = today - (24 * 60 * 60) + local weekTimestamp = today - (7 * 24 * 60 * 60) + local monthTimestamp = today - (30 * 24 * 60 * 60) + + local killsDayRedSkull = configManager.getNumber(configKeys.KILLS_DAY_RED_SKULL) + local killsWeekRedSkull = configManager.getNumber(configKeys.KILLS_WEEK_RED_SKULL) + local killsMonthRedSkull = configManager.getNumber(configKeys.KILLS_MONTH_RED_SKULL) + + local killsDayBanishment = configManager.getNumber(configKeys.KILLS_DAY_BANISHMENT) + local killsWeekBanishment = configManager.getNumber(configKeys.KILLS_WEEK_BANISHMENT) + local killsMonthBanishment = configManager.getNumber(configKeys.KILLS_MONTH_BANISHMENT) + + for _, timestamp in pairs(player:getMurderTimestamps()) do + if timestamp > dayTimestamp then + lastDay = lastDay + 1 + end + + if timestamp > weekTimestamp then + lastWeek = lastWeek + 1 + end + + egibleMurders = lastMonth + 1 + + if timestamp <= monthTimestamp then + egibleMurders = lastMonth + end + + lastMonth = egibleMurders + end + + local message = "" + message = message .. "Default murders\n" + message = message .. "- Daily kills for red skull " .. killsDayRedSkull .. "\n" + message = message .. "- Weekly kills for red skull " .. killsWeekRedSkull .. "\n" + message = message .. "- Monthly kills for red skull " .. (killsMonthRedSkull >= 99999 and "unlimited" or tostring(killsMonthRedSkull)) .. "\n" + + message = message .. "- Daily kills for banishment " .. killsDayBanishment .. "\n" + message = message .. "- Weekly kills for banishment " .. killsWeekBanishment .. "\n" + message = message .. "- Monthly kills for banishment " .. (killsMonthBanishment >= 99999 and "unlimited" or tostring(killsMonthBanishment)) .. "\n" + + message = message .. "\n" + + message = message .. "Last murders within 24 hours " .. lastDay .. "\n" + message = message .. "Last murders within a week " .. lastDay .. "\n" + message = message .. "Last murders within a month " .. lastDay .. "\n" + + message = message .. "\n" + + message = message .. "Players you may kill for a red skull:\n" + message = message .. "- Within 24 hours " .. killsDayRedSkull - lastDay .. " murders.\n" + message = message .. "- Within a week " .. killsWeekRedSkull - lastWeek .. " murders.\n" + message = message .. "- Within a month " .. killsMonthRedSkull - lastDay .. " murders.\n" + + message = message .. "\n" + + message = message .. "Players you may kill for a banishment:\n" + message = message .. "- Within 24 hours " .. killsDayBanishment - lastDay .. " murders.\n" + message = message .. "- Within a week " .. killsWeekBanishment - lastWeek .. " murders.\n" + message = message .. "- Within a month " .. killsMonthBanishment - lastDay .. " murders.\n" + + player:showTextDialog(1998, message, false) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/leavehouse.lua b/app/SabrehavenServer/data/talkactions/scripts/leavehouse.lua new file mode 100644 index 0000000..18938cc --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/leavehouse.lua @@ -0,0 +1,21 @@ +function onSay(player, words, param) + local position = player:getPosition() + local tile = Tile(position) + local house = tile and tile:getHouse() + if house == nil then + player:sendCancelMessage("You are not inside a house.") + position:sendMagicEffect(CONST_ME_POFF) + return false + end + + if house:getOwnerGuid() ~= player:getGuid() then + player:sendCancelMessage("You are not the owner of this house.") + position:sendMagicEffect(CONST_ME_POFF) + return false + end + + house:setOwnerGuid(0) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully left your house.") + position:sendMagicEffect(CONST_ME_POFF) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/looktype.lua b/app/SabrehavenServer/data/talkactions/scripts/looktype.lua new file mode 100644 index 0000000..1a1b11d --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/looktype.lua @@ -0,0 +1,15 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local lookType = tonumber(param) + if lookType >= 0 and lookType ~= 1 and lookType ~= 135 and lookType ~= 411 and lookType ~= 415 and lookType ~= 424 and (lookType <= 160 or lookType >= 192) and lookType ~= 439 and lookType ~= 440 and lookType ~= 468 and lookType ~= 469 and (lookType < 474 or lookType > 485) and lookType ~= 501 and lookType ~= 518 and lookType ~= 519 and lookType ~= 520 and lookType ~= 524 and lookType ~= 525 and lookType ~= 536 and lookType ~= 543 and lookType ~= 549 and lookType ~= 576 and lookType ~= 581 and lookType ~= 582 and lookType ~= 597 and lookType ~= 616 and lookType ~= 623 and lookType ~= 625 and (lookType <= 637 or lookType >= 644) and (lookType <= 644 or lookType >= 647) and (lookType <= 651 or lookType >= 664) and lookType <= 699 then + local playerOutfit = player:getOutfit() + playerOutfit.lookType = lookType + player:setOutfit(playerOutfit) + else + player:sendCancelMessage("A look type with that id does not exist.") + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/loot_test.lua b/app/SabrehavenServer/data/talkactions/scripts/loot_test.lua new file mode 100644 index 0000000..7bf8bcc --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/loot_test.lua @@ -0,0 +1,33 @@ +local config = { + ['exp'] = {skillKey = 17585, timeKey = 17589}, + ['skill'] = {skillKey = 17586, timeKey = 17590}, + ['magic'] = {skillKey = 17587, timeKey = 17591}, + ['loot'] = {skillKey = 17588, timeKey = 17592} -- TODO +} + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local i = 0 + local function generateMonsterLoad() + if (i <= 1000) then + Game.createMonster("cyclops", {x = 32316, y = 31942, z = 7}) + local monster = Game.isMonsterThere({x = 32316, y = 31942, z = 7}, "cyclops") + monster:addHealth(-monster:getMaxHealth()) + addEvent(generateMonsterLoad, 1000) + i = i + 1 + print(i) + end + end + + addEvent(generateMonsterLoad, 1000) + + return false +end + diff --git a/app/SabrehavenServer/data/talkactions/scripts/magiceffect.lua b/app/SabrehavenServer/data/talkactions/scripts/magiceffect.lua new file mode 100644 index 0000000..ebc26b3 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/magiceffect.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + player:getPosition():sendMagicEffect(tonumber(param)) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/mccheck.lua b/app/SabrehavenServer/data/talkactions/scripts/mccheck.lua new file mode 100644 index 0000000..13ffbc9 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/mccheck.lua @@ -0,0 +1,40 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Multiclient Check List:") + + local ipList = {} + local players = Game.getPlayers() + for i = 1, #players do + local tmpPlayer = players[i] + local ip = tmpPlayer:getIp() + if ip ~= 0 then + local list = ipList[ip] + if not list then + ipList[ip] = {} + list = ipList[ip] + end + list[#list + 1] = tmpPlayer + end + end + + for ip, list in pairs(ipList) do + local listLength = #list + if listLength > 1 then + local tmpPlayer = list[1] + local message = ("%s: %s [%d]"):format(Game.convertIpToString(ip), tmpPlayer:getName(), tmpPlayer:getLevel()) + for i = 2, listLength do + tmpPlayer = list[i] + message = ("%s, %s [%d]"):format(message, tmpPlayer:getName(), tmpPlayer:getLevel()) + end + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message .. ".") + end + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/minimap_scan.lua b/app/SabrehavenServer/data/talkactions/scripts/minimap_scan.lua new file mode 100644 index 0000000..519fda4 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/minimap_scan.lua @@ -0,0 +1,114 @@ +local distanceBetweenPositionsX = 8 +local distanceBetweenPositionsY = 8 +local addEventDelay = 500 +local teleportsPerEvent = 1 +local maxEventExecutionTime = 2000 + +local function teleportToClosestPosition(player, x, y, z) + -- direct to position + local tile = Tile(x, y, z) + + if not tile or not tile:getGround() or tile:hasFlag(TILESTATE_TELEPORT) or not player:teleportTo(tile:getPosition()) then + for distance = 1, 3 do + -- try to find some close tile + for changeX = -distance, distance, distance do + for changeY = -distance, distance, distance do + tile = Tile(x + changeX, y + changeY, z) + if tile and tile:getGround() and not tile:hasFlag(TILESTATE_TELEPORT) and player:teleportTo(tile:getPosition()) then + return true + end + end + end + end + + return false + end + + return true +end + +local function sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + local progress = math.floor(((y - minY + (((x - minX) / (maxX - minX)) * distanceBetweenPositionsY)) / (maxY - minY)) * 100) + if progress ~= lastProgress then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan progress: ~' .. progress .. '%') + end + + return progress +end + +local function minimapScan(cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + local player = Player(cid) + + if not player then + --print('Minimap scan stopped - player logged out', cid, minX, maxX, minY, maxY, x, y, z) + return + end + + local scanStartTime = os.mtime() + local teleportsDone = 0 + while true do + if scanStartTime + maxEventExecutionTime < os.mtime() then + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + break + end + + x = x + distanceBetweenPositionsX + if x > maxX then + x = minX + y = y + distanceBetweenPositionsY + if y > maxY then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan finished: ' .. os.time()) + --print('Minimap scan complete', player:getName(), minX, maxX, minY, maxY, x, y, z) + break + end + end + + if teleportToClosestPosition(player, x, y, z) then + teleportsDone = teleportsDone + 1 + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + + --print('Minimap scan teleport', player:getName(), minX, maxX, minY, maxY, x, y, z, progress, teleportsDone) + if teleportsDone == teleportsPerEvent then + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, progress) + break + end + end + end +end + +local function minimapStart(player, minX, maxX, minY, maxY, x, y, z) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan started: ' .. os.time()) + --print('Minimap scan start', player:getName(), minX, maxX, minY, maxY, x, y, z) + minimapScan(player:getId(), minX, maxX, minY, maxY, minX - 5, minY, z) +end + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local positions = param:split(',') + if #positions ~= 5 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Command requires 5 parameters: /minimap minX, maxX, minY, maxY, z') + return false + end + + for key, position in pairs(positions) do + local value = tonumber(position) + + if not value then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Invalid parameter ' .. key .. ': ' .. position) + return false + end + + positions[key] = value + end + + minimapStart(player, positions[1], positions[2], positions[3], positions[4], positions[1] - distanceBetweenPositionsX, positions[3], positions[5]) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/online.lua b/app/SabrehavenServer/data/talkactions/scripts/online.lua new file mode 100644 index 0000000..f9b0656 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/online.lua @@ -0,0 +1,31 @@ +local maxPlayersPerMessage = 10 + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local hasAccess = player:getGroup():getAccess() + local players = Game.getPlayers() + local onlineList = {} + + for _, targetPlayer in ipairs(players) do + if hasAccess or not targetPlayer:isInGhostMode() then + table.insert(onlineList, ("%s [%d]"):format(targetPlayer:getName(), targetPlayer:getLevel())) + end + end + + local playersOnline = #onlineList + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, ("%d players online."):format(playersOnline)) + + for i = 1, playersOnline, maxPlayersPerMessage do + local j = math.min(i + maxPlayersPerMessage - 1, playersOnline) + local msg = table.concat(onlineList, ", ", i, j) .. "." + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, msg) + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/openserver.lua b/app/SabrehavenServer/data/talkactions/scripts/openserver.lua new file mode 100644 index 0000000..c3896e7 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/openserver.lua @@ -0,0 +1,13 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + Game.setGameState(GAME_STATE_NORMAL) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now open.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/owner.lua b/app/SabrehavenServer/data/talkactions/scripts/owner.lua new file mode 100644 index 0000000..d56f489 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/owner.lua @@ -0,0 +1,30 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local tile = Tile(player:getPosition()) + local house = tile and tile:getHouse() + if house == nil then + player:sendCancelMessage("You are not inside a house.") + return false + end + + if param == "" or param == "none" then + house:setOwnerGuid(0) + return false + end + + local targetPlayer = Player(param) + if targetPlayer == nil then + player:sendCancelMessage("Player not found.") + return false + end + + house:setOwnerGuid(targetPlayer:getGuid()) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/physical_damage.lua b/app/SabrehavenServer/data/talkactions/scripts/physical_damage.lua new file mode 100644 index 0000000..02733ef --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/physical_damage.lua @@ -0,0 +1,308 @@ +local weaponSkillsConfig = { + [WEAPON_SWORD] = SKILL_SWORD, + [WEAPON_CLUB] = SKILL_CLUB, + [WEAPON_AXE] = SKILL_AXE, + [WEAPON_DISTANCE] = SKILL_DISTANCE +} + +local skillNameConfig = { + [SKILL_SWORD] = "Sword Fighting", + [SKILL_CLUB] = "Club Fighting", + [SKILL_AXE] = "Axe Fighting", + [SKILL_DISTANCE] = "Distance Fighting", + [SKILL_FIST] = "Fist Fighting" +} + +local function ltrim(s) + if s == nil then + return s + end + + return s:match'^%s*(.*)' +end + +local function getWeapon(player) + local itemLeft = player:getSlotItem(CONST_SLOT_LEFT) + if itemLeft and itemLeft:getType():getWeaponType() ~= WEAPON_NONE and itemLeft:getType():getWeaponType() ~= WEAPON_SHIELD and itemLeft:getType():getWeaponType() ~= WEAPON_AMMO and itemLeft:getType():getWeaponType() ~= WEAPON_WAND then + return itemLeft:getType() + end + + local itemRight = player:getSlotItem(CONST_SLOT_RIGHT) + if itemRight and itemRight:getType():getWeaponType() ~= WEAPON_NONE and itemRight:getType():getWeaponType() ~= WEAPON_SHIELD and itemRight:getType():getWeaponType() ~= WEAPON_AMMO and itemRight:getType():getWeaponType() ~= WEAPON_WAND then + return itemRight:getType() + end +end + +local function getAmmunition(player) + local item = player:getSlotItem(CONST_SLOT_AMMO) + if item and item:getType():getWeaponType() == WEAPON_AMMO then + return item:getType() + end +end + +local function getAttack(weapon, ammunition) + local attack = 7 + if weapon ~= nil then + attack = weapon:getAttack() + if weapon:getWeaponType() == WEAPON_DISTANCE and weapon:getAmmoType() ~= 0 then + if ammunition ~= nil and ammunition:getAmmoType() == weapon:getAmmoType() then + attack = attack + ammunition:getAttack() + end + end + end + + return attack +end + +local function getDamageFormula(attack, attackSkill, fightMode) + local damage = attack + if fightMode == FIGHTMODE_ATTACK then + damage = math.floor(damage + 2 * damage / 10) + elseif fightMode == FIGHTMODE_DEFENSE then + damage = math.floor(damage - 4 * damage / 10) + end + + local formula = math.floor((5 * (attackSkill) + 50) * damage) + return formula +end + +local function getDamage(attack, attackSkill, fightMode, random1, random2) + local formula = getDamageFormula(attack, attackSkill, fightMode) + local randResult = math.floor(random1 % 100); + local damage = -math.floor((math.ceil(formula * ((random2 % 100 + randResult) / 2) / 10000.))); + return damage +end + +local function setVocationDamageIncrease(vocationId, damage) + if vocationId == 4 or vocationId == 8 then + local knightCloseAttackDamageIncreasePercent = configManager.getNumber(configKeys.KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT) + if knightCloseAttackDamageIncreasePercent ~= -1 then + damage = math.floor(damage + damage * knightCloseAttackDamageIncreasePercent / 100); + end + elseif vocationId == 3 or vocationId == 7 then + local paladinRangeAttackDamageIncreasePercent = configManager.getNumber(configKeys.PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT) + if paladinRangeAttackDamageIncreasePercent ~= -1 then + damage = math.floor(damage + damage * paladinRangeAttackDamageIncreasePercent / 100); + end + end + + return damage +end + +local function getDefense(creature, random1, random2) + local totalDefense = creature:getType():getDefense() + 1 + local defenseSkill = creature:getType():getSkill() + local formula = math.floor((5 * (defenseSkill) + 50) * totalDefense) + local randresult = math.floor(random1 % 100) + + return math.floor(formula * ((random2 % 100 + randresult) / 2) / 10000.) +end + +local function rshift(x, by) + return math.floor(x / 2 ^ by) +end + +local function getArmor(creature, rand) + local armor = creature:getType():getArmor() + if armor > 1 then + return rand % rshift(armor, 1) + rshift(armor, 1); + end + + return armor +end + +local function setPhysicalDamageBlock(creature, isCloseRange, damage, random1, random2, randomArmor) + if bit.band(creature:getType():getCombatImmunities(), COMBAT_PHYSICALDAMAGE) == COMBAT_PHYSICALDAMAGE then + return 0 + end + + if isCloseRange then + damage = damage + getDefense(creature, random1, random2) + end + + if damage >= 0 then + return 0 + end + + if damage < 0 then + damage = damage + getArmor(creature, randomArmor) + if damage >= 0 then + return 0 + end + end + + return damage +end + +local function isThrowableHit(weapon, ammunition, skillValue, rand) + local distance = 75 -- we consider distance is always the best + local hitChance = 75 -- throwables and such + + if weapon:getAmmoType() ~= 0 then + hitChance = 90 -- bows and crossbows + if ammunition == nil or ammunition:getAmmoType() ~= weapon:getAmmoType() then + hitChance = -1 -- no ammo or invalid ammo + end + end + + if rand % distance <= skillValue then + return rand % 100 <= hitChance + end + + return false +end + +local function getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, fightMode) + local damage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, os.rand(), os.rand())) + local minDamage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, 0, 0)) + local maxDamage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, 99, 99)) + + if weapon ~= nil and weapon:getWeaponType() == WEAPON_DISTANCE then + if isThrowableHit(weapon, ammunition, skillValue, os.rand()) then + damage = setPhysicalDamageBlock(creature, false, damage, os.rand(), os.rand(), os.rand()) + else + damage = 0 + end + minDamage = 0 + if isThrowableHit(weapon, ammunition, skillValue, 0) then + maxDamage = setPhysicalDamageBlock(creature, false, maxDamage, 0, 0, 0) + else + maxDamage = 0 + end + else + damage = setPhysicalDamageBlock(creature, true, damage, os.rand(), os.rand(), os.rand()) + minDamage = setPhysicalDamageBlock(creature, true, minDamage, 99, 99, rshift(creature:getType():getArmor(), 1) + 1) + maxDamage = setPhysicalDamageBlock(creature, true, maxDamage, 0, 0, 0) + end + + local container = {} + container[0] = damage + container[1] = minDamage + container[2] = maxDamage + return container +end + +function onSay(player, words, param) + local split = param:split(",") + local creatureName = ltrim(split[1]) + local skillValueNumber = ltrim(split[2]) + local vocationName = ltrim(split[3]) + local weaponName = ltrim(split[4]) + local ammunitionName = ltrim(split[5]) + + local creature = Creature("Troll") + if creatureName ~= nil then + creature = Creature(creatureName) + if creature == nil then + player:sendCancelMessage("The monster does not exist.") + return false + end + else + creatureName = creature:getName() + end + + local weapon = getWeapon(player) + if weaponName ~= nil then + weapon = ItemType(weaponName) + if weapon == nil or weapon:getWeaponType() == WEAPON_NONE or weapon:getWeaponType() == WEAPON_SHIELD or weapon:getWeaponType() == WEAPON_AMMO or weapon:getWeaponType() == WEAPON_WAND then + player:sendCancelMessage("The weapon does not exist.") + return false + end + else + if weapon ~= nil then + weaponName = weapon:getName() + end + end + + local ammunition = getAmmunition(player) + if ammunitionName ~= nil then + ammunition = ItemType(ammunitionName) + if ammunition == nil or ammunition:getWeaponType() ~= WEAPON_AMMO then + player:sendCancelMessage("The ammunition does not exist.") + return false + end + else + if ammunition ~= nil then + ammunitionName = ammunition:getName() + end + end + + local skillType = SKILL_FIST + local attack = getAttack(weapon, ammunition) + if weapon ~= nil then + skillType = weaponSkillsConfig[weapon:getWeaponType()] + end + + local skillValue = player:getSkillLevel(skillType) + if skillValueNumber ~= nil then + if tonumber(skillValueNumber) > 0 then + if tonumber(skillValueNumber) <= 300 then + skillValue = tonumber(skillValueNumber) + else + player:sendCancelMessage("The skill value has to be no bigger than 300.") + return false + end + else + player:sendCancelMessage("The skill value has to be a number and greater than zero.") + return false + end + end + + local vocation = player:getVocation() + if vocationName ~= nil then + vocation = Vocation(vocationName) + if vocation == nil then + player:sendCancelMessage("The vocation does not exist.") + return false + end + else + vocationName = vocation:getName() + end + + local commandStr = "Executing command: !physicaldamage " .. creatureName .. ", " .. skillValue .. ", " .. vocationName .. "" + + if weapon ~= nil then + commandStr = commandStr .. ", " .. weaponName + end + + if ammunition ~= nil and weapon ~= nil and weapon:getWeaponType() == WEAPON_DISTANCE then + commandStr = commandStr .. ", " .. ammunitionName + end + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, commandStr) + + local offensiveDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_ATTACK) + local balancedDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_BALANCED) + local defensiveDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_DEFENSE) + + local message = "" + message = message .. "Vocation: " .. vocation:getName() .. "\n" + message = message .. "Skill Name: " .. skillNameConfig[skillType] .. ", Skill Value: " .. skillValue .. "\n" + message = message .. "Weapon: " .. (weaponName or 'none') .. " (atk: " .. attack .. ")\n" + message = message .. "Creature: " .. creatureName .. " (arm: " .. creature:getType():getArmor() .. ", def: " .. creature:getType():getDefense() .. ", skill: " .. creature:getType():getSkill() .. ")\n" + message = message .. "\nOffensive Fighting Damage\n" + message = message .. "Min: " .. offensiveDamageContainer[1] .. ", Max: " .. offensiveDamageContainer[2] .. "\n" + message = message .. "\nBalanced Fighting Damage\n" + message = message .. "Min: " .. balancedDamageContainer[1] .. ", Max: " .. balancedDamageContainer[2] .. "\n" + message = message .. "\nDefensive Fighting Damage\n" + message = message .. "Min: " .. defensiveDamageContainer[1] .. ", Max: " .. defensiveDamageContainer[2] .. "\n" + + message = message .. "\nFirst 100 Hits Damage Simulator in Offensive Fighting\n" + local creatureHealth = creature:getType():getMaxHealth() + local creatureHitsTillDeath = 1 + for i=1,100 do + local damageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_ATTACK) + message = message .. "Hit: " .. i .. ", Damage: " .. damageContainer[0] .. "\n" + creatureHealth = creatureHealth + damageContainer[0] + if creatureHealth > 0 then + creatureHitsTillDeath = creatureHitsTillDeath + 1 + end + end + + if creatureHealth <= 0 then + message = message .. "\nIt would take you approximately " .. creatureHitsTillDeath .. " hits to slain " .. creature:getName() .. ".\n" + else + message = message .. "\nIt would take you more than 100 hits to slain " .. creature:getName() .. ".\n" + end + player:showTextDialog(weapon and weapon:getId() or 2950, message, false) + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/place_monster.lua b/app/SabrehavenServer/data/talkactions/scripts/place_monster.lua new file mode 100644 index 0000000..8446c0b --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/place_monster.lua @@ -0,0 +1,20 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local position = player:getPosition() + local monster = Game.createMonster(param, position) + if monster ~= nil then + monster:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/place_npc.lua b/app/SabrehavenServer/data/talkactions/scripts/place_npc.lua new file mode 100644 index 0000000..aaf6ef6 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/place_npc.lua @@ -0,0 +1,20 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local position = player:getPosition() + local npc = Game.createNpc(param, position) + if npc ~= nil then + npc:setMasterPos(position) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/place_summon.lua b/app/SabrehavenServer/data/talkactions/scripts/place_summon.lua new file mode 100644 index 0000000..f511d20 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/place_summon.lua @@ -0,0 +1,20 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local position = player:getPosition() + local monster = Game.createMonster(param, position) + if monster ~= nil then + monster:setMaster(player) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/position.lua b/app/SabrehavenServer/data/talkactions/scripts/position.lua new file mode 100644 index 0000000..299ce6e --- /dev/null +++ b/app/SabrehavenServer/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/app/SabrehavenServer/data/talkactions/scripts/query_houses.lua b/app/SabrehavenServer/data/talkactions/scripts/query_houses.lua new file mode 100644 index 0000000..e49b99d --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/query_houses.lua @@ -0,0 +1,34 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local searchItemId = tonumber(param) + + for _, house in pairs(Game.getHouses()) do + for _, tile in pairs(house:getTiles()) do + for _, item in pairs(tile:getItems()) do + if item ~= nil then + local isFound = false + if item:isContainer() then + local items = item:getItemsById(searchItemId) + isFound = #items > 0 + else + isFound = item:getId() == searchItemId + end + + if isFound then + local position = item:getPosition() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Item position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + end + end + end + end + end + + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/raid.lua b/app/SabrehavenServer/data/talkactions/scripts/raid.lua new file mode 100644 index 0000000..7eba681 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/raid.lua @@ -0,0 +1,13 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + Game.startRaid(param) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid with name " .. param .. " started.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/reload.lua b/app/SabrehavenServer/data/talkactions/scripts/reload.lua new file mode 100644 index 0000000..57e8f90 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/reload.lua @@ -0,0 +1,68 @@ +local reloadTypes = { + ["all"] = { targetType = RELOAD_TYPE_ALL, name = "all" }, + + ["action"] = { targetType = RELOAD_TYPE_ACTIONS, name = "actions" }, + ["actions"] = { targetType = RELOAD_TYPE_ACTIONS, name = "actions" }, + + ["chat"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" }, + ["channel"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" }, + ["chatchannels"] = { targetType = RELOAD_TYPE_CHAT, name = "chatchannels" }, + + ["config"] = { targetType = RELOAD_TYPE_CONFIG, name = "config" }, + ["configuration"] = { targetType = RELOAD_TYPE_CONFIG, name = "config" }, + + ["creaturescript"] = { targetType = RELOAD_TYPE_CREATURESCRIPTS, name = "creature scripts" }, + ["creaturescripts"] = { targetType = RELOAD_TYPE_CREATURESCRIPTS, name = "creature scripts" }, + + ["events"] = { targetType = RELOAD_TYPE_EVENTS, name = "events" }, + + ["global"] = { targetType = RELOAD_TYPE_GLOBAL, name = "global.lua" }, + + ["globalevent"] = { targetType = RELOAD_TYPE_GLOBALEVENTS, name = "globalevents" }, + ["globalevents"] = { targetType = RELOAD_TYPE_GLOBALEVENTS, name = "globalevents" }, + + ["items"] = { targetType = RELOAD_TYPE_ITEMS, name = "items" }, + + ["monster"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" }, + ["monsters"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" }, + + ["move"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" }, + ["movement"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" }, + ["movements"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" }, + + ["npc"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" }, + ["npcs"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" }, + + ["quest"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" }, + ["quests"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" }, + + ["raid"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" }, + ["raids"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" }, + + ["spell"] = { targetType = RELOAD_TYPE_SPELLS, name = "spells" }, + ["spells"] = { targetType = RELOAD_TYPE_SPELLS, name = "spells" }, + + ["talk"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" }, + ["talkaction"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" }, + ["talkactions"] = { targetType = RELOAD_TYPE_TALKACTIONS, name = "talk actions" } +} + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + return false + end + + local reloadType = reloadTypes[param and param:lower()] + if not reloadType then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found.") + return false + end + + Game.reload(reloadType.targetType) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Reloaded %s.", reloadType.name)) + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/remove_house.lua b/app/SabrehavenServer/data/talkactions/scripts/remove_house.lua new file mode 100644 index 0000000..c7f4f86 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/remove_house.lua @@ -0,0 +1,22 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local position = player:getPosition() + local tile = Tile(position) + local house = tile and tile:getHouse() + if house == nil then + player:sendCancelMessage("You are not inside a house.") + position:sendMagicEffect(CONST_ME_POFF) + return false + end + + house:setOwnerGuid(0) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully removed this house.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/remove_tutor.lua b/app/SabrehavenServer/data/talkactions/scripts/remove_tutor.lua new file mode 100644 index 0000000..27b0bdb --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/remove_tutor.lua @@ -0,0 +1,27 @@ +function onSay(player, words, param) + if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + return true + end + + local resultId = db.storeQuery("SELECT `name`, `account_id`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + player:sendCancelMessage("A player with that name does not exist.") + return false + end + + if result.getDataInt(resultId, "account_type") ~= ACCOUNT_TYPE_TUTOR then + player:sendCancelMessage("You can only demote a tutor to a normal player.") + return false + end + + local target = Player(param) + if target ~= nil then + target:setAccountType(ACCOUNT_TYPE_NORMAL) + else + db.query("UPDATE `accounts` SET `type` = " .. ACCOUNT_TYPE_NORMAL .. " WHERE `id` = " .. result.getDataInt(resultId, "account_id")) + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have demoted " .. result.getDataString(resultId, "name") .. " to a normal player.") + result.free(resultId) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/removething.lua b/app/SabrehavenServer/data/talkactions/scripts/removething.lua new file mode 100644 index 0000000..8216e03 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/removething.lua @@ -0,0 +1,33 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection()) + + local tile = Tile(position) + if not tile then + player:sendCancelMessage("Object not found.") + return false + end + + local thing = tile:getTopVisibleThing(player) + if not thing then + player:sendCancelMessage("Thing not found.") + return false + end + + if thing:isCreature() then + thing:remove() + elseif thing:isItem() then + if thing == tile:getGround() then + player:sendCancelMessage("You may not remove a ground tile.") + return false + end + thing:remove(tonumber(param) or -1) + end + + position:sendMagicEffect(CONST_ME_MAGIC_RED) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/save.lua b/app/SabrehavenServer/data/talkactions/scripts/save.lua new file mode 100644 index 0000000..d704c4c --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/save.lua @@ -0,0 +1,23 @@ +function onSay(player, words, param) + + local function timeSave(delay, msg) + broadcastMessage(msg, MESSAGE_STATUS_WARNING) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, msg) + addEvent(saveServer, delay) + end + + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + if isNumber(param) then + local delay = tonumber(param) * 1000 + timeSave(delay, "Saving server in " .. tonumber(param) .. " seconds.") + else + saveServer() + end +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/sellhouse.lua b/app/SabrehavenServer/data/talkactions/scripts/sellhouse.lua new file mode 100644 index 0000000..9920a8a --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/sellhouse.lua @@ -0,0 +1,19 @@ +function onSay(player, words, param) + local tradePartner = Player(param) + if not tradePartner or tradePartner == player then + player:sendCancelMessage("Trade player not found.") + return false + end + + local house = player:getTile():getHouse() + if not house then + player:sendCancelMessage("You must stand in your house to initiate the trade.") + return false + end + + local returnValue = house:startTrade(player, tradePartner) + if returnValue ~= RETURNVALUE_NOERROR then + player:sendCancelMessage(returnValue) + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/serpentine_tower_event.lua b/app/SabrehavenServer/data/talkactions/scripts/serpentine_tower_event.lua new file mode 100644 index 0000000..d5e21fc --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/serpentine_tower_event.lua @@ -0,0 +1,279 @@ +local area = { + fromPos = {x = 33140, y = 32859, z = 7}, + toPos = {x = 33155, y = 32874, z = 4} +} + +local availablePlayerTeleportPositions = { + Position(33139, 32859, 7), + Position(33139, 32860, 7), + Position(33139, 32861, 7), + Position(33139, 32862, 7), + Position(33139, 32863, 7), + Position(33139, 32864, 7), + Position(33139, 32865, 7), + Position(33139, 32866, 7), + Position(33139, 32867, 7), + Position(33139, 32868, 7), + Position(33139, 32869, 7), + Position(33139, 32870, 7), + Position(33139, 32871, 7), + Position(33139, 32872, 7), + Position(33139, 32873, 7), + Position(33139, 32874, 7), + Position(33139, 32875, 7), + Position(33140, 32875, 7), + Position(33143, 32875, 7), + Position(33144, 32875, 7), + Position(33146, 32875, 7), + Position(33148, 32875, 7), + Position(33149, 32875, 7), + Position(33150, 32875, 7), + Position(33155, 32875, 7), + Position(33156, 32875, 7), + Position(33156, 32874, 7), + Position(33156, 32873, 7), + Position(33156, 32872, 7), + Position(33156, 32871, 7), + Position(33156, 32870, 7), + Position(33156, 32869, 7), + Position(33156, 32868, 7), + Position(33156, 32867, 7), + Position(33156, 32866, 7), + Position(33156, 32865, 7), + Position(33156, 32864, 7), + Position(33156, 32863, 7), + Position(33156, 32862, 7), + Position(33156, 32861, 7), + Position(33156, 32860, 7), + Position(33156, 32859, 7), + Position(33156, 32858, 7) +} + +local downstairsIds = {451, 466, 465, 467} + +local earthquakeEffects = {CONST_ME_POFF, CONST_ME_EXPLOSIONHIT, CONST_ME_EXPLOSIONAREA, CONST_ME_FIREAREA, CONST_ME_ENERGYHIT, CONST_ME_BLOCKHIT} + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + teleportPlayersToSerpentineTower() + addEvent(wave1, 20000) + addEvent(wave2, 40000) + addEvent(wave3, 100000) + addEvent(wave4, 160000) + addEvent(wave5, 170000) + addEvent(wave6, 175000) + addEvent(wave7, 180000) + addEvent(wave8, 195000) + addEvent(wave9, 205000) + + return false +end + +function teleportPlayersToSerpentineTower() + for _, player in ipairs(Game.getPlayers()) do + player:setStorageValue(17596, 1) + local teleportPosition = availablePlayerTeleportPositions[math.random(#availablePlayerTeleportPositions)] + player:teleportTo(teleportPosition) + player:getPosition():sendMonsterSay("accersi " .. player:getName()) + end +end + +function wave1() + broadcastMessage("LOOK AT MY EYES! ... THE EYES! ... LET ME OUT! ...", MESSAGE_STATUS_WARNING) + earthquakeTower(area.fromPos, area.toPos) +end + +function wave2() + broadcastMessage("Ankrahmun: The Academy of Magic Arts are reporting that Ankrahmun city is experiencing issues! Please stay safe in the protection zones, NOW!", MESSAGE_STATUS_WARNING) + earthquakeTower(area.fromPos, area.toPos) +end + +function wave3() + broadcastMessage("Ankrahmun: All Tibianus PEOPLE. THIS IS NOT A PRACTICE. Leave our city NOW!", MESSAGE_STATUS_WARNING) + earthquakeTower(area.fromPos, area.toPos) +end + +function wave4() + broadcastMessage("THE EYES ARE EVERYWHERE!", MESSAGE_STATUS_WARNING) + earthquakeTower(area.fromPos, area.toPos) +end + +function wave5() + Position(33149, 32868, 7):sendMonsterSay("accersi Tothdral") + local tothdral = Creature("Tothdral") + tothdral:teleportTo(Position(33149, 32867, 7)) + tothdral:getPosition():sendMagicEffect(CONST_ME_TELEPORT) +end + +function wave6() + Position(33147, 32870, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33151, 32870, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33151, 32866, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33147, 32866, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + + local tile = Tile(Position(33149, 32868, 7)) + if tile then + local obelisk = tile:getItemById(2199) + if obelisk ~= nil then + obelisk:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + obelisk:remove() + local hole = Game.createItem(5731, 1, tile:getPosition()) + hole:setAttribute(ITEM_ATTRIBUTE_MOVEMENTID, 17596) + end + end +end + +function wave7() + Position(33147, 32870, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33151, 32870, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33151, 32866, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33147, 32866, 7):sendMonsterSay("The Serpentine Tower Secret Is Real!") + Position(33149, 32868, 7):sendMonsterSay("LET ME OUT!") +end + +function wave8() + broadcastMessage("Ankrahmun: WE ARE LOST FOREVER!", MESSAGE_STATUS_WARNING) + for xx = area.fromPos.x, area.toPos.x do + for yy = area.fromPos.y, area.toPos.y do + local position = Position(xx, yy, 7) + local tile = Tile(position) + if tile then + local ground = tile:getGround() + if ground ~= nil and ground:getId() == 231 then + ground:transform(2144) + end + end + end + end + + Game.createItem(2199, 1, Position(33140, 32859, 7)) + Position(33140, 32859, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33140, 32874, 7)) + Position(33140, 32874, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33155, 32874, 7)) + Position(33155, 32874, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33155, 32859, 7)) + Position(33155, 32859, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33149, 32863, 7)) + Position(33149, 32863, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33153, 32866, 7)) + Position(33153, 32866, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33153, 32871, 7)) + Position(33153, 32871, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33149, 32873, 7)) + Position(33149, 32873, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33145, 32871, 7)) + Position(33145, 32871, 7):sendMagicEffect(CONST_ME_TELEPORT) + Game.createItem(2199, 1, Position(33145, 32866, 7)) + Position(33145, 32866, 7):sendMagicEffect(CONST_ME_TELEPORT) +end + +function wave9() + broadcastMessage("Ankrahmun: PROTEGO MAXIMA!", MESSAGE_STATUS_WARNING) + + for _, player in ipairs(Game.getPlayers()) do + player:teleportTo(player:getTown():getTemplePosition()) + end +end + +function earthquakeTower(frompos, topos) + for zz = frompos.z, topos.z, -1 do + if zz == 6 then + topos.x = topos.x + 1 + topos.y = topos.y + 1 + end + + for xx = frompos.x, topos.x do + for yy = frompos.y, topos.y do + local position = Position(xx, yy, zz) + removeFloorItems(position) + copyHigherFloorItems(position) + end + end + + if zz == 6 then + topos.x = topos.x - 1 + topos.y = topos.y - 1 + end + end +end + +function removeFloorItems(position) + local tile = Tile(position) + if tile then + -- If any creature is in area then teleport it to safe zone to properly work with tile items + local creature = tile:getTopCreature() + if creature then + local teleportPosition = availablePlayerTeleportPositions[math.random(#availablePlayerTeleportPositions)] + creature:teleportTo(teleportPosition) + creature:getPosition():sendMonsterSay("The Gods Protecting You!") + Game.sendMagicEffect(teleportPosition, 11) + if creature:isPlayer() then + if creature:getStorageValue(17596) ~= 2 then + creature:setStorageValue(17596, 2) + end + end + end + + local currentFloorItem = tile:getItemByType(0) + while currentFloorItem ~= nil do + currentFloorItem:remove() + currentFloorItem = tile:getItemByType(0) + end + + local items = tile:getItems() + if items ~= nil then + for _, item in pairs(items) do + item:remove() + end + end + + -- Create sand floor only for ground level + if position.z == 7 then + Game.createTile(position) + Game.createItem(231, 1, position) + end + end +end + +function copyHigherFloorItems(position) + local higherFloorPosition = {x = position.x, y = position.y, z = position.z - 1} + local tile = Tile(higherFloorPosition) + if tile then + local higherFloorItem = tile:getItemByType(0) + while(higherFloorItem ~= nil) do + if position.z ~= 7 or isInArray(downstairsIds, higherFloorItem:getId()) == false then + Game.createItem(higherFloorItem:getId(), 1, position) + end + + higherFloorItem:remove() + higherFloorItem = tile:getItemByType(0) + end + + local items = tile:getItems() + if items ~= nil then + for _, item in pairs(items) do + if position.z ~= 7 or isInArray(downstairsIds, item:getId()) == false then + Game.createItem(item:getId(), 1, position) + end + + item:remove() + end + end + + local effectRandomness = math.random(5) + if effectRandomness == 5 then + local earthquakeEffect = earthquakeEffects[math.random(#earthquakeEffects)] + tile:getPosition():sendMagicEffect(earthquakeEffect) + end + end +end + +-- TODO: Implement that bitch npc is busy and says that I don't understand what is happening. The Academy of Magic Arts are not commenting the current situation. \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/serverinfo.lua b/app/SabrehavenServer/data/talkactions/scripts/serverinfo.lua new file mode 100644 index 0000000..6b26b98 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/serverinfo.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server Info:" + .. "\nExp rate: " .. Game.getExperienceStage(player:getLevel()) + .. "\nSkill rate: " .. configManager.getNumber(configKeys.RATE_SKILL) + .. "\nMagic rate: " .. configManager.getNumber(configKeys.RATE_MAGIC) + .. "\nLoot rate: " .. configManager.getNumber(configKeys.RATE_LOOT)) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/storagevalue.lua b/app/SabrehavenServer/data/talkactions/scripts/storagevalue.lua new file mode 100644 index 0000000..6ed9bb9 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/storagevalue.lua @@ -0,0 +1,15 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local split = param:split(",") + if split[2] == nil then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + player:setStorageValue(tonumber(split[1]), tonumber(split[2])) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "[Storage Value] " .. split[1] .. " changed it's value to " .. split[2] .. ".") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_creature_here.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_creature_here.lua new file mode 100644 index 0000000..ea36919 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_creature_here.lua @@ -0,0 +1,24 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local creature = Creature(param) + if not creature then + player:sendCancelMessage("A creature with that name could not be found.") + return false + end + + local oldPosition = creature:getPosition() + local newPosition = creature:getClosestFreePosition(player:getPosition(), false) + if newPosition.x == 0 then + player:sendCancelMessage("You can not teleport " .. creature:getName() .. ".") + return false + elseif creature:teleportTo(newPosition) then + if not creature:isInGhostMode() then + oldPosition:sendMagicEffect(CONST_ME_POFF) + newPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_home.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_home.lua new file mode 100644 index 0000000..6485230 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_home.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + player:teleportTo(player:getTown():getTemplePosition()) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_ntiles.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_ntiles.lua new file mode 100644 index 0000000..4ffc5b5 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_ntiles.lua @@ -0,0 +1,28 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local steps = tonumber(param) + if not steps then + return false + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection(), steps) + + position = player:getClosestFreePosition(position, false) + if position.x == 0 then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_to_creature.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_creature.lua new file mode 100644 index 0000000..17a7da9 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_creature.lua @@ -0,0 +1,14 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local target = Creature(param) + if target == nil then + player:sendCancelMessage("Creature not found.") + return false + end + + player:teleportTo(target:getPosition()) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_to_pos.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_pos.lua new file mode 100644 index 0000000..8068e21 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_pos.lua @@ -0,0 +1,9 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local split = param:split(",") + local position = {x = split[1], y = split[2], z = split[3]} + return player:teleportTo(position) +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/teleport_to_town.lua b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_town.lua new file mode 100644 index 0000000..87cdfae --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/teleport_to_town.lua @@ -0,0 +1,18 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local town = Town(param) + if town == nil then + town = Town(tonumber(param)) + end + + if town == nil then + player:sendCancelMessage("Town not found.") + return false + end + + player:teleportTo(town:getTemplePosition()) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/unban.lua b/app/SabrehavenServer/data/talkactions/scripts/unban.lua new file mode 100644 index 0000000..b65c4c7 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/unban.lua @@ -0,0 +1,16 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + return false + end + + db.asyncQuery("DELETE FROM `account_bans` WHERE `account_id` = " .. result.getDataInt(resultId, "account_id")) + db.asyncQuery("DELETE FROM `ip_bans` WHERE `ip` = " .. result.getDataInt(resultId, "lastip")) + result.free(resultId) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, param .. " has been unbanned.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/up.lua b/app/SabrehavenServer/data/talkactions/scripts/up.lua new file mode 100644 index 0000000..de3477f --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/up.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position.z = position.z - 1 + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/uptime.lua b/app/SabrehavenServer/data/talkactions/scripts/uptime.lua new file mode 100644 index 0000000..7c0e291 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/uptime.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + local uptime = getWorldUpTime() + + local hours = math.floor(uptime / 3600) + local minutes = math.floor((uptime - (3600 * hours)) / 60) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/vial.lua b/app/SabrehavenServer/data/talkactions/scripts/vial.lua new file mode 100644 index 0000000..817fcd1 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/vial.lua @@ -0,0 +1,10 @@ +function onSay(player, words, param) + if player:getStorageValue(17742) ~= 1 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Vials auto removing enabled.") + player:setStorageValue(17742, 1) + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Vials auto removing disabled.") + player:setStorageValue(17742, 0) + end + return false +end diff --git a/app/SabrehavenServer/data/talkactions/scripts/war.lua b/app/SabrehavenServer/data/talkactions/scripts/war.lua new file mode 100644 index 0000000..146dfdd --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/war.lua @@ -0,0 +1,60 @@ +function onSay(player, words, param) + if not player:getGuild() or player:getGuildLevel() < 3 then + player:sendCancelMessage("You cannot execute this talkaction.") + return true + end + + local t = param:split(",") + + if not t[2] then + player:sendCancelMessage("Not enough param(s).") + return true + end + + local enemyGuildName = string.trim(t[2]) + + local enemyGuild = Guild(getGuildId(enemyGuildName)) + if not enemyGuild then + player:sendCancelMessage("Guild \"" .. enemyGuildName .. "\" does not exists or nobody is online from the guild.") + return true + end + + if enemyGuild:getId() == player:getGuild():getId() then + player:sendCancelMessage("You cannot perform war action on your own guild.") + return true + end + + local warCommand = string.trim(t[1]) + + if isInArray({"accept", "reject", "cancel"}, warCommand) then + local pendingWarId, bounty = 0 + + if warCommand == "cancel" then + pendingWarId, bounty = guildwars:getPendingInvitation(player:getGuild():getId(), enemyGuild:getId()) + else + pendingWarId, bounty = guildwars:getPendingInvitation(enemyGuild:getId(), player:getGuild():getId()) + end + + if pendingWarId == 0 then + player:sendCancelMessage("Currently there's no pending invitation for a war with " .. enemyGuild:getName() .. ".") + return true + end + + if warCommand == "reject" then + guildwars:rejectWar(pendingWarId, player:getGuild(), enemyGuild, bounty) + elseif warCommand == "cancel" then + guildwars:cancelWar(pendingWarId, player:getGuild(), enemyGuild, bounty) + else + guildwars:startWar(player, pendingWarId, player:getGuild(), enemyGuild, bounty) + end + + return true + end + + if warCommand == "invite" then + guildwars:invite(player, player:getGuild(), enemyGuild, tonumber(t[3] and string.trim(t[3]) or 100), tonumber(t[4] and string.trim(t[4]) or 0)) + return true + end + + return true +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/scripts/znoteshop.lua b/app/SabrehavenServer/data/talkactions/scripts/znoteshop.lua new file mode 100644 index 0000000..788ef6a --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/scripts/znoteshop.lua @@ -0,0 +1,130 @@ +-- Znote Shop v1.0 for Znote AAC on TFS 1.1 +function onSay(player, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if player:getStorageValue(storage) <= os.time() then + player:setStorageValue(storage, os.time() + cooldown) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. player:getName() .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getNumber(orderQuery, "id") + local q_type = result.getNumber(orderQuery, "type") + local q_itemid = result.getNumber(orderQuery, "itemid") + local q_count = result.getNumber(orderQuery, "count") + + print("Processing type "..q_type..": ".. type_desc[q_type]) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get wheight + if player:getFreeCapacity() >= ItemType(q_itemid):getWeight(q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addItem(q_itemid, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " x " .. ItemType(q_itemid):getName() .. "!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(q_itemid) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addMount(q_itemid) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if q_type == 7 then + served = true + local house = House(q_itemid) + -- Logged in player is not neccesarily the player that bough the house. So we need to load player from db. + print(q_count) + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..q_count.." LIMIT 1") + + if buyerQuery ~= false then + local buyerName = result.getDataString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + house:setOwnerGuid(q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has recieved house: ["..house:getName().."]") + end + end + end + + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- So use type 7+ for custom stuff, like etc packages. + -- if q_type == 7 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every " .. cooldown .. " seconds. Remaining cooldown: " .. player:getStorageValue(storage) - os.time()) + end + return false +end \ No newline at end of file diff --git a/app/SabrehavenServer/data/talkactions/talkactions.xml b/app/SabrehavenServer/data/talkactions/talkactions.xml new file mode 100644 index 0000000..f4eeb38 --- /dev/null +++ b/app/SabrehavenServer/data/talkactions/talkactions.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/world780/houses.xml b/app/SabrehavenServer/data/world780/houses.xml new file mode 100644 index 0000000..1adfc32 --- /dev/null +++ b/app/SabrehavenServer/data/world780/houses.xml @@ -0,0 +1,933 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/world780/map.otbm b/app/SabrehavenServer/data/world780/map.otbm new file mode 100644 index 0000000..45ddb72 Binary files /dev/null and b/app/SabrehavenServer/data/world780/map.otbm differ diff --git a/app/SabrehavenServer/data/world780/mymap-house.xml b/app/SabrehavenServer/data/world780/mymap-house.xml new file mode 100644 index 0000000..9d137e4 --- /dev/null +++ b/app/SabrehavenServer/data/world780/mymap-house.xml @@ -0,0 +1,2 @@ + + diff --git a/app/SabrehavenServer/data/world780/mymap-spawn.xml b/app/SabrehavenServer/data/world780/mymap-spawn.xml new file mode 100644 index 0000000..2bde032 --- /dev/null +++ b/app/SabrehavenServer/data/world780/mymap-spawn.xml @@ -0,0 +1,2 @@ + + diff --git a/app/SabrehavenServer/data/world780/mymap.otbm b/app/SabrehavenServer/data/world780/mymap.otbm new file mode 100644 index 0000000..b0ed9ec Binary files /dev/null and b/app/SabrehavenServer/data/world780/mymap.otbm differ diff --git a/app/SabrehavenServer/data/world780/spawns.xml b/app/SabrehavenServer/data/world780/spawns.xml new file mode 100644 index 0000000..17072cf --- /dev/null +++ b/app/SabrehavenServer/data/world780/spawns.xml @@ -0,0 +1,48980 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/world792/houses.xml b/app/SabrehavenServer/data/world792/houses.xml new file mode 100644 index 0000000..1adfc32 --- /dev/null +++ b/app/SabrehavenServer/data/world792/houses.xml @@ -0,0 +1,933 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/data/world792/map.otbm b/app/SabrehavenServer/data/world792/map.otbm new file mode 100644 index 0000000..a724650 Binary files /dev/null and b/app/SabrehavenServer/data/world792/map.otbm differ diff --git a/app/SabrehavenServer/data/world792/mymap-house.xml b/app/SabrehavenServer/data/world792/mymap-house.xml new file mode 100644 index 0000000..9d137e4 --- /dev/null +++ b/app/SabrehavenServer/data/world792/mymap-house.xml @@ -0,0 +1,2 @@ + + diff --git a/app/SabrehavenServer/data/world792/mymap-spawn.xml b/app/SabrehavenServer/data/world792/mymap-spawn.xml new file mode 100644 index 0000000..2bde032 --- /dev/null +++ b/app/SabrehavenServer/data/world792/mymap-spawn.xml @@ -0,0 +1,2 @@ + + diff --git a/app/SabrehavenServer/data/world792/mymap.otbm b/app/SabrehavenServer/data/world792/mymap.otbm new file mode 100644 index 0000000..b0ed9ec Binary files /dev/null and b/app/SabrehavenServer/data/world792/mymap.otbm differ diff --git a/app/SabrehavenServer/data/world792/spawns.xml b/app/SabrehavenServer/data/world792/spawns.xml new file mode 100644 index 0000000..71b3bc9 --- /dev/null +++ b/app/SabrehavenServer/data/world792/spawns.xml @@ -0,0 +1,49439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/SabrehavenServer/key.pem b/app/SabrehavenServer/key.pem new file mode 100644 index 0000000..ec19bb4 --- /dev/null +++ b/app/SabrehavenServer/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/app/SabrehavenServer/private_custom_rsa.pem b/app/SabrehavenServer/private_custom_rsa.pem new file mode 100644 index 0000000..b31eab7 --- /dev/null +++ b/app/SabrehavenServer/private_custom_rsa.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC37FCfKlJJWTRGIlY1bwJWKI2t90TRtiSi5Gx856yVI7YYVVEW +/2DGP2H7HH6YMaTPN7wPge/Fi4fcr/x1b3BKv4x4jnFjrDpwA7C1dF12c6D2Ls1I +5b9R7Fs0jXjMea5q9yV78rtx41qJso8kfc3IEFFfjA96vJV0OYc4EvpoSwIDAQAB +AoGAZq+ru5G3a3hAdT/Kff7CgTQXFh/N7oARKFj8MShv9LyBeEh6A6sFbuoIXFjm +XTaqW380ojvbkKF8czxWaYKUYFP6mG2N+VIqwaYzhZhXA2cmyiIc41HrPxVa2Kxm +JcPHhVCyIB1NLBZMND7LMsHzVTmf9Gm5FdBFxKUOGaRX3gECQQDujk+rakseSgWp ++dH3nkbUrq5xlBpP40fRZNUMONQwR6LMKIE6eOj6mNrTjiYqFF/gacYKMn7wdtUf +Do0Q+kqBAkEAxV9LqHqH+pfpYp6j7rqKzCRhOlc29iQDDHdb/3lahHFIbN7jlFvv +qakblsMwozBzRtvYaFf7/2ulXdBe3o9UywJBANXQj+/f0XxQdEspMtxx7KJr/sam +K/82gwRXD+1ocibjzjKWi11RSeByaI/9dI96u4R0yaASKlx05gh0DlQNQgECQQCn +kPhS2XSItGBYcgcLryQXnOtO9Kyc3IYaF1vr0cOfne23QKRGWKdoi0H5BU5Nvdyg +1BUuwUQY3fNPapCgs19xAkAomiU6YtPdIahCYudRgE1dSxfPb2vHBXCUVZsIUvmG +qAOSSBwsu2ZJWhr6+WscQ6PNDTo0A7UtWVhqpZveyCsd +-----END RSA PRIVATE KEY----- diff --git a/app/SabrehavenServer/public_custom_rsa.pem b/app/SabrehavenServer/public_custom_rsa.pem new file mode 100644 index 0000000..716d0ba --- /dev/null +++ b/app/SabrehavenServer/public_custom_rsa.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC37FCfKlJJWTRGIlY1bwJWKI2t +90TRtiSi5Gx856yVI7YYVVEW/2DGP2H7HH6YMaTPN7wPge/Fi4fcr/x1b3BKv4x4 +jnFjrDpwA7C1dF12c6D2Ls1I5b9R7Fs0jXjMea5q9yV78rtx41qJso8kfc3IEFFf +jA96vJV0OYc4EvpoSwIDAQAB +-----END PUBLIC KEY----- diff --git a/app/SabrehavenServer/sabrehaven.sql b/app/SabrehavenServer/sabrehaven.sql new file mode 100644 index 0000000..0101986 --- /dev/null +++ b/app/SabrehavenServer/sabrehaven.sql @@ -0,0 +1,1688 @@ +-- phpMyAdmin SQL Dump +-- version 4.8.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: 127.0.0.1 +-- Generation Time: Jan 17, 2019 at 01:39 AM +-- Server version: 10.1.35-MariaDB +-- PHP Version: 7.2.9 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `sabrehaven` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `accounts` +-- + +CREATE TABLE `accounts` ( + `id` int(11) NOT NULL, + `name` int(11) NOT NULL, + `password` char(40) NOT NULL, + `type` int(11) NOT NULL DEFAULT '1', + `premdays` int(11) NOT NULL DEFAULT '0', + `lastday` int(10) UNSIGNED NOT NULL DEFAULT '0', + `email` varchar(255) NOT NULL DEFAULT '', + `creation` int(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_bans` +-- + +CREATE TABLE `account_bans` ( + `account_id` int(11) NOT NULL, + `reason` varchar(255) NOT NULL, + `banned_at` bigint(20) NOT NULL, + `expires_at` bigint(20) NOT NULL, + `banned_by` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_ban_history` +-- + +CREATE TABLE `account_ban_history` ( + `id` int(10) UNSIGNED NOT NULL, + `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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_viplist` +-- + +CREATE TABLE `account_viplist` ( + `account_id` int(11) NOT NULL COMMENT 'id of account whose viplist entry it is', + `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry', + `description` varchar(128) NOT NULL DEFAULT '', + `icon` tinyint(2) UNSIGNED NOT NULL DEFAULT '0', + `notify` tinyint(1) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guilds` +-- + +CREATE TABLE `guilds` ( + `id` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + `ownerid` int(11) NOT NULL, + `creationdata` int(11) NOT NULL, + `motd` varchar(255) NOT NULL DEFAULT '', + `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Triggers `guilds` +-- +DELIMITER $$ +CREATE TRIGGER `oncreate_guilds` AFTER INSERT ON `guilds` FOR EACH ROW BEGIN + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('the Leader', 3, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Vice-Leader', 2, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Member', 1, NEW.`id`); +END +$$ +DELIMITER ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guildwar_kills` +-- + +CREATE TABLE `guildwar_kills` ( + `id` int(11) NOT NULL, + `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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_invites` +-- + +CREATE TABLE `guild_invites` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `guild_id` int(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_membership` +-- + +CREATE TABLE `guild_membership` ( + `player_id` int(11) NOT NULL, + `guild_id` int(11) NOT NULL, + `rank_id` int(11) NOT NULL, + `nick` varchar(15) NOT NULL DEFAULT '' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_ranks` +-- + +CREATE TABLE `guild_ranks` ( + `id` int(11) NOT NULL, + `guild_id` int(11) NOT NULL COMMENT 'guild', + `name` varchar(255) NOT NULL COMMENT 'rank name', + `level` int(11) NOT NULL COMMENT 'rank level - leader, vice, member, maybe something else' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_wars` +-- + +CREATE TABLE `guild_wars` ( + `id` int(11) NOT NULL, + `guild1` int(11) NOT NULL DEFAULT '0', + `guild2` int(11) NOT NULL DEFAULT '0', + `status` tinyint(2) NOT NULL DEFAULT '0', + `frag_limit` int(11) NOT NULL DEFAULT '0', + `declaration_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `bounty` bigint(20) UNSIGNED NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `houses` +-- + +CREATE TABLE `houses` ( + `id` int(11) NOT NULL, + `owner` int(11) NOT NULL, + `paid` int(10) UNSIGNED NOT NULL DEFAULT '0', + `warnings` int(11) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + `rent` int(11) NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `bid` int(11) NOT NULL DEFAULT '0', + `bid_end` int(11) NOT NULL DEFAULT '0', + `last_bid` int(11) NOT NULL DEFAULT '0', + `highest_bidder` int(11) NOT NULL DEFAULT '0', + `size` int(11) NOT NULL DEFAULT '0', + `beds` int(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `houses` +-- + +INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `rent`, `town_id`, `bid`, `bid_end`, `last_bid`, `highest_bidder`, `size`, `beds`) VALUES +(1, 0, 0, 0, 'Spiritkeep', 36220, 1, 0, 0, 0, 0, 378, 23), +(2, 0, 0, 0, 'Snake Tower', 57440, 1, 0, 0, 0, 0, 616, 21), +(3, 0, 0, 0, 'Halls of the Adventurers', 29060, 1, 0, 0, 0, 0, 304, 18), +(4, 0, 0, 0, 'Dark Mansion', 34090, 1, 0, 0, 0, 0, 361, 17), +(5, 0, 0, 0, 'Bloodhall', 29040, 1, 0, 0, 0, 0, 306, 16), +(6, 0, 0, 0, 'Sunset Homes, Flat 01', 1040, 1, 0, 0, 0, 0, 13, 1), +(7, 0, 0, 0, 'Sunset Homes, Flat 02', 1040, 1, 0, 0, 0, 0, 13, 1), +(8, 0, 0, 0, 'Sunset Homes, Flat 03', 1040, 1, 0, 0, 0, 0, 13, 1), +(9, 0, 0, 0, 'Sunset Homes, Flat 11', 1040, 1, 0, 0, 0, 0, 13, 1), +(10, 0, 0, 0, 'Sunset Homes, Flat 12', 1040, 1, 0, 0, 0, 0, 13, 1), +(11, 0, 0, 0, 'Sunset Homes, Flat 13', 1620, 1, 0, 0, 0, 0, 19, 2), +(12, 0, 0, 0, 'Sunset Homes, Flat 14', 1040, 1, 0, 0, 0, 0, 13, 1), +(13, 0, 0, 0, 'Sunset Homes, Flat 21', 1040, 1, 0, 0, 0, 0, 13, 1), +(14, 0, 0, 0, 'Sunset Homes, Flat 22', 1040, 1, 0, 0, 0, 0, 13, 1), +(15, 0, 0, 0, 'Sunset Homes, Flat 23', 1620, 1, 0, 0, 0, 0, 19, 2), +(16, 0, 0, 0, 'Sunset Homes, Flat 24', 1040, 1, 0, 0, 0, 0, 13, 1), +(17, 0, 0, 0, 'Beach Home Apartments, Flat 01', 1430, 1, 0, 0, 0, 0, 13, 1), +(18, 0, 0, 0, 'Beach Home Apartments, Flat 02', 1430, 1, 0, 0, 0, 0, 13, 1), +(19, 0, 0, 0, 'Beach Home Apartments, Flat 03', 1430, 1, 0, 0, 0, 0, 13, 1), +(20, 0, 0, 0, 'Beach Home Apartments, Flat 04', 1430, 1, 0, 0, 0, 0, 13, 1), +(21, 0, 0, 0, 'Beach Home Apartments, Flat 05', 1430, 1, 0, 0, 0, 0, 13, 1), +(22, 0, 0, 0, 'Beach Home Apartments, Flat 06', 2190, 1, 0, 0, 0, 0, 19, 2), +(23, 0, 0, 0, 'Beach Home Apartments, Flat 11', 1430, 1, 0, 0, 0, 0, 13, 1), +(24, 0, 0, 0, 'Beach Home Apartments, Flat 12', 1760, 1, 0, 0, 0, 0, 16, 1), +(25, 0, 0, 0, 'Beach Home Apartments, Flat 13', 1760, 1, 0, 0, 0, 0, 16, 1), +(26, 0, 0, 0, 'Beach Home Apartments, Flat 14', 770, 1, 0, 0, 0, 0, 7, 1), +(27, 0, 0, 0, 'Beach Home Apartments, Flat 15', 770, 1, 0, 0, 0, 0, 7, 1), +(28, 0, 0, 0, 'Beach Home Apartments, Flat 16', 2190, 1, 0, 0, 0, 0, 19, 2), +(29, 0, 0, 0, 'Alai Flats, Flat 01', 1530, 1, 0, 0, 0, 0, 17, 1), +(30, 0, 0, 0, 'Alai Flats, Flat 02', 1530, 1, 0, 0, 0, 0, 17, 1), +(31, 0, 0, 0, 'Alai Flats, Flat 03', 1530, 1, 0, 0, 0, 0, 17, 1), +(32, 0, 0, 0, 'Alai Flats, Flat 04', 1530, 1, 0, 0, 0, 0, 17, 1), +(33, 0, 0, 0, 'Alai Flats, Flat 05', 2350, 1, 0, 0, 0, 0, 25, 2), +(34, 0, 0, 0, 'Alai Flats, Flat 06', 2350, 1, 0, 0, 0, 0, 25, 2), +(35, 0, 0, 0, 'Alai Flats, Flat 07', 1530, 1, 0, 0, 0, 0, 17, 1), +(36, 0, 0, 0, 'Alai Flats, Flat 08', 1530, 1, 0, 0, 0, 0, 17, 1), +(37, 0, 0, 0, 'Alai Flats, Flat 11', 1530, 1, 0, 0, 0, 0, 17, 1), +(38, 0, 0, 0, 'Alai Flats, Flat 12', 1530, 1, 0, 0, 0, 0, 17, 1), +(39, 0, 0, 0, 'Alai Flats, Flat 13', 1530, 1, 0, 0, 0, 0, 17, 1), +(40, 0, 0, 0, 'Alai Flats, Flat 14', 1800, 1, 0, 0, 0, 0, 20, 1), +(41, 0, 0, 0, 'Alai Flats, Flat 15', 2800, 1, 0, 0, 0, 0, 30, 2), +(42, 0, 0, 0, 'Alai Flats, Flat 16', 2800, 1, 0, 0, 0, 0, 30, 2), +(43, 0, 0, 0, 'Alai Flats, Flat 17', 1800, 1, 0, 0, 0, 0, 20, 1), +(44, 0, 0, 0, 'Alai Flats, Flat 18', 1800, 1, 0, 0, 0, 0, 20, 1), +(45, 0, 0, 0, 'Alai Flats, Flat 22', 1530, 1, 0, 0, 0, 0, 17, 1), +(46, 0, 0, 0, 'Alai Flats, Flat 21', 1530, 1, 0, 0, 0, 0, 17, 1), +(47, 0, 0, 0, 'Alai Flats, Flat 23', 1530, 1, 0, 0, 0, 0, 17, 1), +(48, 0, 0, 0, 'Alai Flats, Flat 24', 1800, 1, 0, 0, 0, 0, 20, 1), +(49, 0, 0, 0, 'Alai Flats, Flat 25', 2800, 1, 0, 0, 0, 0, 30, 2), +(50, 0, 0, 0, 'Alai Flats, Flat 26', 2800, 1, 0, 0, 0, 0, 30, 2), +(51, 0, 0, 0, 'Alai Flats, Flat 27', 1800, 1, 0, 0, 0, 0, 20, 1), +(52, 0, 0, 0, 'Alai Flats, Flat 28', 1800, 1, 0, 0, 0, 0, 20, 1), +(53, 0, 0, 0, 'Upper Swamp Lane 2', 9180, 1, 0, 0, 0, 0, 74, 4), +(54, 0, 0, 0, 'Upper Swamp Lane 4', 9180, 1, 0, 0, 0, 0, 74, 4), +(55, 0, 0, 0, 'Lower Swamp Lane 1', 9180, 1, 0, 0, 0, 0, 74, 4), +(56, 0, 0, 0, 'Lower Swamp Lane 3', 9180, 1, 0, 0, 0, 0, 74, 4), +(57, 0, 0, 0, 'Upper Swamp Lane 8', 16040, 1, 0, 0, 0, 0, 132, 3), +(58, 0, 0, 0, 'Southern Thais Guildhall', 43020, 1, 0, 0, 0, 0, 346, 16), +(59, 0, 0, 0, 'Upper Swamp Lane 10', 3920, 1, 0, 0, 0, 0, 31, 3), +(60, 0, 0, 0, 'Upper Swamp Lane 12', 7400, 1, 0, 0, 0, 0, 60, 3), +(61, 0, 0, 0, 'Sorcerer\'s Avenue 1a', 2410, 1, 0, 0, 0, 0, 21, 2), +(62, 0, 0, 0, 'Sorcerer\'s Avenue 1b', 1970, 1, 0, 0, 0, 0, 17, 2), +(63, 0, 0, 0, 'Sorcerer\'s Avenue 1c', 2410, 1, 0, 0, 0, 0, 21, 2), +(64, 0, 0, 0, 'Sorcerer\'s Avenue 5', 5390, 1, 0, 0, 0, 0, 49, 1), +(65, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2a', 1430, 1, 0, 0, 0, 0, 13, 1), +(66, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2b', 1430, 1, 0, 0, 0, 0, 13, 1), +(67, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2c', 1430, 1, 0, 0, 0, 0, 13, 1), +(68, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2d', 1430, 1, 0, 0, 0, 0, 13, 1), +(69, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2e', 1430, 1, 0, 0, 0, 0, 13, 1), +(70, 0, 0, 0, 'Sorcerer\'s Avenue Labs 2f', 1430, 1, 0, 0, 0, 0, 13, 1), +(71, 0, 0, 0, 'Thais Clanhall', 15940, 1, 0, 0, 0, 0, 188, 10), +(72, 0, 0, 0, 'Harbour Street 4', 1870, 1, 0, 0, 0, 0, 17, 1), +(73, 0, 0, 0, 'Thais Hostel', 11660, 1, 0, 0, 0, 0, 117, 24), +(74, 0, 0, 0, 'Farm Lane, Basement (Shop)', 1890, 1, 0, 0, 0, 0, 21, 0), +(75, 0, 0, 0, 'Farm Lane, 1st floor (Shop)', 1890, 1, 0, 0, 0, 0, 21, 0), +(76, 0, 0, 0, 'Farm Lane, 2nd Floor (Shop)', 1890, 1, 0, 0, 0, 0, 21, 0), +(77, 0, 0, 0, 'Warriors Guildhall', 28450, 1, 0, 0, 0, 0, 305, 11), +(78, 0, 0, 0, 'Main Street 9, 1st floor (Shop)', 2880, 1, 0, 0, 0, 0, 32, 0), +(79, 0, 0, 0, 'Main Street 9a, 2nd floor (Shop)', 1530, 1, 0, 0, 0, 0, 17, 0), +(80, 0, 0, 0, 'Main Street 9b, 2nd floor (Shop)', 2520, 1, 0, 0, 0, 0, 28, 0), +(81, 0, 0, 0, 'Mill Avenue 1 (Shop)', 2600, 1, 0, 0, 0, 0, 26, 1), +(82, 0, 0, 0, 'Mill Avenue 2 (Shop)', 4600, 1, 0, 0, 0, 0, 45, 2), +(83, 0, 0, 0, 'Mill Avenue 3', 2700, 1, 0, 0, 0, 0, 26, 2), +(84, 0, 0, 0, 'Mill Avenue 4', 2700, 1, 0, 0, 0, 0, 26, 2), +(85, 0, 0, 0, 'Mill Avenue 5', 6200, 1, 0, 0, 0, 0, 59, 4), +(86, 0, 0, 0, 'The City Wall 5a', 1170, 1, 0, 0, 0, 0, 13, 1), +(87, 0, 0, 0, 'The City Wall 5b', 1170, 1, 0, 0, 0, 0, 13, 1), +(88, 0, 0, 0, 'The City Wall 5c', 1170, 1, 0, 0, 0, 0, 13, 1), +(89, 0, 0, 0, 'The City Wall 5d', 1170, 1, 0, 0, 0, 0, 13, 1), +(90, 0, 0, 0, 'The City Wall 5e', 1170, 1, 0, 0, 0, 0, 13, 1), +(91, 0, 0, 0, 'The City Wall 5f', 1170, 1, 0, 0, 0, 0, 13, 1), +(92, 0, 0, 0, 'The City Wall 7a', 1170, 1, 0, 0, 0, 0, 13, 1), +(93, 0, 0, 0, 'The City Wall 7b', 1170, 1, 0, 0, 0, 0, 13, 1), +(94, 0, 0, 0, 'The City Wall 7c', 1630, 1, 0, 0, 0, 0, 17, 2), +(95, 0, 0, 0, 'The City Wall 7d', 1630, 1, 0, 0, 0, 0, 17, 2), +(96, 0, 0, 0, 'The City Wall 7e', 1630, 1, 0, 0, 0, 0, 17, 2), +(97, 0, 0, 0, 'The City Wall 7f', 1630, 1, 0, 0, 0, 0, 17, 2), +(98, 0, 0, 0, 'The City Wall 7g', 1170, 1, 0, 0, 0, 0, 13, 1), +(99, 0, 0, 0, 'The City Wall 7h', 1170, 1, 0, 0, 0, 0, 13, 1), +(100, 0, 0, 0, 'The City Wall 9', 1810, 1, 0, 0, 0, 0, 19, 2), +(101, 0, 0, 0, 'The City Wall 3a', 1990, 1, 0, 0, 0, 0, 21, 2), +(102, 0, 0, 0, 'The City Wall 3b', 1990, 1, 0, 0, 0, 0, 21, 2), +(103, 0, 0, 0, 'The City Wall 3c', 1990, 1, 0, 0, 0, 0, 21, 2), +(104, 0, 0, 0, 'The City Wall 3d', 1990, 1, 0, 0, 0, 0, 21, 2), +(105, 0, 0, 0, 'The City Wall 3e', 1990, 1, 0, 0, 0, 0, 21, 2), +(106, 0, 0, 0, 'The City Wall 3f', 1990, 1, 0, 0, 0, 0, 21, 2), +(107, 0, 0, 0, 'The City Wall 1a', 2440, 1, 0, 0, 0, 0, 26, 2), +(108, 0, 0, 0, 'The City Wall 1b', 2440, 1, 0, 0, 0, 0, 26, 2), +(109, 0, 0, 0, 'Harbour Place 2 (Shop)', 2600, 1, 0, 0, 0, 0, 26, 1), +(110, 0, 0, 0, 'Harbour Place 1 (Shop)', 2200, 1, 0, 0, 0, 0, 22, 0), +(111, 0, 0, 0, 'Mercenary Tower', 81410, 1, 0, 0, 0, 0, 607, 26), +(112, 0, 0, 0, 'Guildhall of the Red Rose', 54050, 1, 0, 0, 0, 0, 405, 15), +(113, 0, 0, 0, 'Fibula Village 1', 1690, 1, 0, 0, 0, 0, 13, 1), +(114, 0, 0, 0, 'Fibula Village 2', 1690, 1, 0, 0, 0, 0, 13, 1), +(115, 0, 0, 0, 'Fibula Village 3', 7320, 1, 0, 0, 0, 0, 54, 4), +(116, 0, 0, 0, 'Fibula Village 4', 3480, 1, 0, 0, 0, 0, 26, 2), +(117, 0, 0, 0, 'Fibula Village 5', 3480, 1, 0, 0, 0, 0, 26, 2), +(118, 0, 0, 0, 'Fibula Village, Tower Flat', 10110, 1, 0, 0, 0, 0, 77, 2), +(119, 0, 0, 0, 'Fibula Village, Bar', 10370, 1, 0, 0, 0, 0, 79, 2), +(120, 0, 0, 0, 'Fibula Clanhall', 21960, 1, 0, 0, 0, 0, 162, 10), +(121, 0, 0, 0, 'Fibula Village, Villa', 22380, 1, 0, 0, 0, 0, 242, 7), +(122, 0, 0, 0, 'The Tibianic', 66900, 1, 0, 0, 0, 0, 540, 22), +(123, 0, 0, 0, 'Castle of Greenshore', 36380, 1, 0, 0, 0, 0, 294, 12), +(124, 0, 0, 0, 'Greenshore Village, Villa', 20580, 1, 0, 0, 0, 0, 169, 4), +(125, 0, 0, 0, 'Greenshore Village, Shop', 3600, 1, 0, 0, 0, 0, 30, 1), +(126, 0, 0, 0, 'Greenshore Village 1', 4640, 1, 0, 0, 0, 0, 37, 3), +(127, 0, 0, 0, 'Greenshore Village 2', 1560, 1, 0, 0, 0, 0, 13, 1), +(128, 0, 0, 0, 'Greenshore Village 3', 1560, 1, 0, 0, 0, 0, 13, 1), +(129, 0, 0, 0, 'Greenshore Village 4', 1560, 1, 0, 0, 0, 0, 13, 1), +(130, 0, 0, 0, 'Greenshore Village 5', 1560, 1, 0, 0, 0, 0, 13, 1), +(131, 0, 0, 0, 'Greenshore Village 6', 8620, 1, 0, 0, 0, 0, 71, 2), +(132, 0, 0, 0, 'Greenshore Village 7', 2520, 1, 0, 0, 0, 0, 21, 1), +(133, 0, 0, 0, 'Greenshore Clanhall', 20700, 1, 0, 0, 0, 0, 165, 10), +(134, 0, 0, 0, 'Moonkeep', 24540, 2, 0, 0, 0, 0, 288, 16), +(135, 0, 0, 0, 'House of Recreation', 39460, 2, 0, 0, 0, 0, 412, 16), +(136, 0, 0, 0, 'Nordic Stronghold', 34700, 2, 0, 0, 0, 0, 410, 21), +(137, 0, 0, 0, 'Druids Retreat A', 2580, 2, 0, 0, 0, 0, 31, 2), +(138, 0, 0, 0, 'Druids Retreat B', 2420, 2, 0, 0, 0, 0, 29, 2), +(139, 0, 0, 0, 'Druids Retreat C', 1860, 2, 0, 0, 0, 0, 22, 2), +(140, 0, 0, 0, 'Druids Retreat D', 2260, 2, 0, 0, 0, 0, 27, 2), +(141, 0, 0, 0, 'Central Plaza 3', 1200, 2, 0, 0, 0, 0, 12, 0), +(142, 0, 0, 0, 'Central Plaza 2', 1200, 2, 0, 0, 0, 0, 12, 0), +(143, 0, 0, 0, 'Central Plaza 1', 1200, 2, 0, 0, 0, 0, 12, 0), +(144, 0, 0, 0, 'Park Lane 1a', 2340, 2, 0, 0, 0, 0, 28, 2), +(145, 0, 0, 0, 'Park Lane 1b', 2660, 2, 0, 0, 0, 0, 32, 2), +(146, 0, 0, 0, 'Park Lane 3b', 2100, 2, 0, 0, 0, 0, 25, 2), +(147, 0, 0, 0, 'Park Lane 3a', 2340, 2, 0, 0, 0, 0, 28, 2), +(148, 0, 0, 0, 'Park Lane 4', 1860, 2, 0, 0, 0, 0, 22, 2), +(149, 0, 0, 0, 'Park Lane 2', 1860, 2, 0, 0, 0, 0, 22, 2), +(150, 0, 0, 0, 'Theater Avenue 6a', 1540, 2, 0, 0, 0, 0, 16, 2), +(151, 0, 0, 0, 'Theater Avenue 6b', 1540, 2, 0, 0, 0, 0, 16, 2), +(152, 0, 0, 0, 'Theater Avenue 6c', 450, 2, 0, 0, 0, 0, 5, 1), +(153, 0, 0, 0, 'Theater Avenue 6d', 450, 2, 0, 0, 0, 0, 5, 1), +(154, 0, 0, 0, 'Theater Avenue 6e', 1540, 2, 0, 0, 0, 0, 16, 2), +(155, 0, 0, 0, 'Theater Avenue 6f', 1540, 2, 0, 0, 0, 0, 16, 2), +(156, 0, 0, 0, 'Theater Avenue 5a', 900, 2, 0, 0, 0, 0, 10, 1), +(157, 0, 0, 0, 'Theater Avenue 5b', 900, 2, 0, 0, 0, 0, 10, 1), +(158, 0, 0, 0, 'Theater Avenue 5c', 900, 2, 0, 0, 0, 0, 10, 1), +(159, 0, 0, 0, 'Theater Avenue 5d', 900, 2, 0, 0, 0, 0, 10, 1), +(160, 0, 0, 0, 'Theater Avenue 8a', 2440, 2, 0, 0, 0, 0, 26, 2), +(161, 0, 0, 0, 'Theater Avenue 8b', 2540, 2, 0, 0, 0, 0, 26, 3), +(162, 0, 0, 0, 'Theater Avenue 7, Flat 01', 630, 2, 0, 0, 0, 0, 7, 1), +(163, 0, 0, 0, 'Theater Avenue 7, Flat 02', 810, 2, 0, 0, 0, 0, 9, 1), +(164, 0, 0, 0, 'Theater Avenue 7, Flat 03', 810, 2, 0, 0, 0, 0, 9, 1), +(165, 0, 0, 0, 'Theater Avenue 7, Flat 04', 990, 2, 0, 0, 0, 0, 11, 1), +(166, 0, 0, 0, 'Theater Avenue 7, Flat 05', 810, 2, 0, 0, 0, 0, 9, 1), +(167, 0, 0, 0, 'Theater Avenue 7, Flat 06', 630, 2, 0, 0, 0, 0, 7, 1), +(168, 0, 0, 0, 'Theater Avenue 7, Flat 11', 990, 2, 0, 0, 0, 0, 11, 1), +(169, 0, 0, 0, 'Theater Avenue 7, Flat 12', 810, 2, 0, 0, 0, 0, 9, 1), +(170, 0, 0, 0, 'Theater Avenue 7, Flat 13', 810, 2, 0, 0, 0, 0, 9, 1), +(171, 0, 0, 0, 'Theater Avenue 7, Flat 14', 990, 2, 0, 0, 0, 0, 11, 1), +(172, 0, 0, 0, 'Theater Avenue 7, Flat 15', 810, 2, 0, 0, 0, 0, 9, 1), +(173, 0, 0, 0, 'Theater Avenue 7, Flat 16', 810, 2, 0, 0, 0, 0, 9, 1), +(174, 0, 0, 0, 'Theater Avenue 10', 2080, 2, 0, 0, 0, 0, 22, 2), +(175, 0, 0, 0, 'Theater Avenue 12', 1810, 2, 0, 0, 0, 0, 19, 2), +(176, 0, 0, 0, 'Theater Avenue 14 (Shop)', 4230, 2, 0, 0, 0, 0, 47, 1), +(177, 0, 0, 0, 'Theater Avenue 11a', 2710, 2, 0, 0, 0, 0, 29, 2), +(178, 0, 0, 0, 'Theater Avenue 11b', 1170, 2, 0, 0, 0, 0, 13, 1), +(179, 0, 0, 0, 'Theater Avenue 11c', 1170, 2, 0, 0, 0, 0, 13, 1), +(180, 0, 0, 0, 'Magician\'s Alley 1', 2000, 2, 0, 0, 0, 0, 19, 2), +(181, 0, 0, 0, 'Magician\'s Alley 1a', 1300, 2, 0, 0, 0, 0, 12, 2), +(182, 0, 0, 0, 'Magician\'s Alley 1b', 1400, 2, 0, 0, 0, 0, 13, 2), +(183, 0, 0, 0, 'Magician\'s Alley 1c', 1000, 2, 0, 0, 0, 0, 10, 1), +(184, 0, 0, 0, 'Magician\'s Alley 1d', 900, 2, 0, 0, 0, 0, 9, 1), +(185, 0, 0, 0, 'Magician\'s Alley 5a', 700, 2, 0, 0, 0, 0, 7, 1), +(186, 0, 0, 0, 'Magician\'s Alley 5b', 1000, 2, 0, 0, 0, 0, 10, 1), +(187, 0, 0, 0, 'Magician\'s Alley 5c', 2200, 2, 0, 0, 0, 0, 21, 2), +(188, 0, 0, 0, 'Magician\'s Alley 5d', 1000, 2, 0, 0, 0, 0, 10, 1), +(189, 0, 0, 0, 'Magician\'s Alley 5e', 1000, 2, 0, 0, 0, 0, 10, 1), +(190, 0, 0, 0, 'Magician\'s Alley 5f', 2200, 2, 0, 0, 0, 0, 21, 2), +(191, 0, 0, 0, 'Magician\'s Alley 4', 5200, 2, 0, 0, 0, 0, 49, 4), +(192, 0, 0, 0, 'Magician\'s Alley 8', 2700, 2, 0, 0, 0, 0, 26, 2), +(193, 0, 0, 0, 'Carlin Clanhall', 22700, 2, 0, 0, 0, 0, 218, 10), +(194, 0, 0, 0, 'Northern Street 1a', 1780, 2, 0, 0, 0, 0, 21, 2), +(195, 0, 0, 0, 'Northern Street 1b', 1780, 2, 0, 0, 0, 0, 21, 2), +(196, 0, 0, 0, 'Northern Street 1c', 1380, 2, 0, 0, 0, 0, 16, 2), +(197, 0, 0, 0, 'Northern Street 3a', 1380, 2, 0, 0, 0, 0, 16, 2), +(198, 0, 0, 0, 'Northern Street 3b', 1460, 2, 0, 0, 0, 0, 17, 2), +(199, 0, 0, 0, 'Northern Street 5', 3860, 2, 0, 0, 0, 0, 47, 2), +(200, 0, 0, 0, 'Northern Street 7', 3300, 2, 0, 0, 0, 0, 40, 2), +(201, 0, 0, 0, 'Harbour Lane 1 (Shop)', 2080, 2, 0, 0, 0, 0, 26, 0), +(202, 0, 0, 0, 'Harbour Lane 3', 6920, 2, 0, 0, 0, 0, 84, 3), +(203, 0, 0, 0, 'Harbour Lane 2a (Shop)', 1360, 2, 0, 0, 0, 0, 17, 0), +(204, 0, 0, 0, 'Harbour Lane 2b (Shop)', 1360, 2, 0, 0, 0, 0, 17, 0), +(205, 0, 0, 0, 'Harbour Flats, Flat 11', 1040, 2, 0, 0, 0, 0, 13, 1), +(206, 0, 0, 0, 'Harbour Flats, Flat 12', 800, 2, 0, 0, 0, 0, 10, 1), +(207, 0, 0, 0, 'Harbour Flats, Flat 13', 1040, 2, 0, 0, 0, 0, 13, 1), +(208, 0, 0, 0, 'Harbour Flats, Flat 14', 800, 2, 0, 0, 0, 0, 10, 1), +(209, 0, 0, 0, 'Harbour Flats, Flat 15', 720, 2, 0, 0, 0, 0, 9, 1), +(210, 0, 0, 0, 'Harbour Flats, Flat 16', 800, 2, 0, 0, 0, 0, 10, 1), +(211, 0, 0, 0, 'Harbour Flats, Flat 17', 720, 2, 0, 0, 0, 0, 9, 1), +(212, 0, 0, 0, 'Harbour Flats, Flat 18', 800, 2, 0, 0, 0, 0, 10, 1), +(213, 0, 0, 0, 'Harbour Flats, Flat 21', 1620, 2, 0, 0, 0, 0, 19, 2), +(214, 0, 0, 0, 'Harbour Flats, Flat 22', 1860, 2, 0, 0, 0, 0, 22, 2), +(215, 0, 0, 0, 'Harbour Flats, Flat 23', 800, 2, 0, 0, 0, 0, 10, 1), +(216, 0, 0, 0, 'East Lane 1a', 4420, 2, 0, 0, 0, 0, 54, 2), +(217, 0, 0, 0, 'East Lane 1b', 3300, 2, 0, 0, 0, 0, 40, 2), +(218, 0, 0, 0, 'East Lane 2', 9060, 2, 0, 0, 0, 0, 112, 2), +(219, 0, 0, 0, 'Suntower', 19360, 2, 0, 0, 0, 0, 232, 9), +(220, 0, 0, 0, 'Lonely Sea Side Hostel', 23660, 2, 0, 0, 0, 0, 287, 8), +(221, 0, 0, 0, 'Northport Village 1', 2850, 2, 0, 0, 0, 0, 25, 2), +(222, 0, 0, 0, 'Northport Village 2', 2850, 2, 0, 0, 0, 0, 25, 2), +(223, 0, 0, 0, 'Northport Village 3', 10770, 2, 0, 0, 0, 0, 97, 2), +(224, 0, 0, 0, 'Northport Village 4', 5160, 2, 0, 0, 0, 0, 46, 2), +(225, 0, 0, 0, 'Seawatch', 48220, 2, 0, 0, 0, 0, 422, 19), +(226, 0, 0, 0, 'Northport Village 5', 3510, 2, 0, 0, 0, 0, 31, 2), +(227, 0, 0, 0, 'Northport Village 6', 4170, 2, 0, 0, 0, 0, 37, 2), +(228, 0, 0, 0, 'Northport Clanhall', 18720, 2, 0, 0, 0, 0, 162, 10), +(229, 0, 0, 0, 'Senja Village 1a', 1530, 2, 0, 0, 0, 0, 17, 1), +(230, 0, 0, 0, 'Senja Village 1b', 3160, 2, 0, 0, 0, 0, 34, 2), +(231, 0, 0, 0, 'Senja Village 2', 1530, 2, 0, 0, 0, 0, 17, 1), +(232, 0, 0, 0, 'Senja Village 3', 3430, 2, 0, 0, 0, 0, 37, 2), +(233, 0, 0, 0, 'Senja Village 4', 1530, 2, 0, 0, 0, 0, 17, 1), +(234, 0, 0, 0, 'Senja Village 5', 2350, 2, 0, 0, 0, 0, 25, 2), +(235, 0, 0, 0, 'Senja Village 6a', 1530, 2, 0, 0, 0, 0, 17, 1), +(236, 0, 0, 0, 'Senja Village 6b', 1530, 2, 0, 0, 0, 0, 17, 1), +(237, 0, 0, 0, 'Senja Village 7', 1630, 2, 0, 0, 0, 0, 17, 2), +(238, 0, 0, 0, 'Senja Village 8', 3250, 2, 0, 0, 0, 0, 35, 2), +(239, 0, 0, 0, 'Senja Village 9', 5050, 2, 0, 0, 0, 0, 55, 2), +(240, 0, 0, 0, 'Senja Village 10', 2970, 2, 0, 0, 0, 0, 33, 1), +(241, 0, 0, 0, 'Senja Village 11', 5140, 2, 0, 0, 0, 0, 56, 2), +(242, 0, 0, 0, 'Senja Clanhall', 20250, 2, 0, 0, 0, 0, 215, 10), +(243, 0, 0, 0, 'Wolftower', 40900, 3, 0, 0, 0, 0, 387, 23), +(244, 0, 0, 0, 'Hill Hideout', 26500, 3, 0, 0, 0, 0, 251, 15), +(245, 0, 0, 0, 'Riverspring', 37100, 3, 0, 0, 0, 0, 353, 19), +(246, 0, 0, 0, 'The Farms 1', 4820, 3, 0, 0, 0, 0, 42, 3), +(247, 0, 0, 0, 'The Farms 2', 2960, 3, 0, 0, 0, 0, 26, 2), +(248, 0, 0, 0, 'The Farms 3', 2960, 3, 0, 0, 0, 0, 26, 2), +(249, 0, 0, 0, 'The Farms 4', 2960, 3, 0, 0, 0, 0, 26, 2), +(250, 0, 0, 0, 'The Farms 5', 2960, 3, 0, 0, 0, 0, 26, 2), +(251, 0, 0, 0, 'The Farms 6, Fishing Hut', 2410, 3, 0, 0, 0, 0, 21, 2), +(252, 0, 0, 0, 'Nobility Quarter 1', 3530, 3, 0, 0, 0, 0, 37, 3), +(253, 0, 0, 0, 'Nobility Quarter 2', 3530, 3, 0, 0, 0, 0, 37, 3), +(254, 0, 0, 0, 'Nobility Quarter 3', 3530, 3, 0, 0, 0, 0, 37, 3), +(255, 0, 0, 0, 'Nobility Quarter 4', 1530, 3, 0, 0, 0, 0, 17, 1), +(256, 0, 0, 0, 'Nobility Quarter 5', 1530, 3, 0, 0, 0, 0, 17, 1), +(257, 0, 0, 0, 'Nobility Quarter 6', 1530, 3, 0, 0, 0, 0, 17, 1), +(258, 0, 0, 0, 'Nobility Quarter 7', 1530, 3, 0, 0, 0, 0, 17, 1), +(259, 0, 0, 0, 'Nobility Quarter 8', 1530, 3, 0, 0, 0, 0, 17, 1), +(260, 0, 0, 0, 'Nobility Quarter 9', 1530, 3, 0, 0, 0, 0, 17, 1), +(261, 0, 0, 0, 'Upper Barracks 1', 420, 3, 0, 0, 0, 0, 7, 1), +(262, 0, 0, 0, 'Upper Barracks 2', 420, 3, 0, 0, 0, 0, 7, 1), +(263, 0, 0, 0, 'Upper Barracks 3', 420, 3, 0, 0, 0, 0, 7, 1), +(264, 0, 0, 0, 'Upper Barracks 4', 420, 3, 0, 0, 0, 0, 7, 1), +(265, 0, 0, 0, 'Upper Barracks 5', 420, 3, 0, 0, 0, 0, 7, 1), +(266, 0, 0, 0, 'Upper Barracks 6', 420, 3, 0, 0, 0, 0, 7, 1), +(267, 0, 0, 0, 'Upper Barracks 7', 420, 3, 0, 0, 0, 0, 7, 1), +(268, 0, 0, 0, 'Upper Barracks 8', 420, 3, 0, 0, 0, 0, 7, 1), +(269, 0, 0, 0, 'Upper Barracks 9', 420, 3, 0, 0, 0, 0, 7, 1), +(270, 0, 0, 0, 'Upper Barracks 10', 420, 3, 0, 0, 0, 0, 7, 1), +(271, 0, 0, 0, 'Upper Barracks 11', 420, 3, 0, 0, 0, 0, 7, 1), +(272, 0, 0, 0, 'Upper Barracks 12', 420, 3, 0, 0, 0, 0, 7, 1), +(273, 0, 0, 0, 'Upper Barracks 13', 1060, 3, 0, 0, 0, 0, 16, 2), +(274, 0, 0, 0, 'The Market 1 (Shop)', 1300, 3, 0, 0, 0, 0, 13, 0), +(275, 0, 0, 0, 'The Market 2 (Shop)', 2200, 3, 0, 0, 0, 0, 22, 0), +(276, 0, 0, 0, 'The Market 3 (Shop)', 2900, 3, 0, 0, 0, 0, 29, 0), +(277, 0, 0, 0, 'The Market 4 (Shop)', 3600, 3, 0, 0, 0, 0, 36, 0), +(278, 0, 0, 0, 'Lower Barracks 1', 600, 3, 0, 0, 0, 0, 10, 1), +(279, 0, 0, 0, 'Lower Barracks 2', 600, 3, 0, 0, 0, 0, 10, 1), +(280, 0, 0, 0, 'Lower Barracks 3', 600, 3, 0, 0, 0, 0, 10, 1), +(281, 0, 0, 0, 'Lower Barracks 4', 600, 3, 0, 0, 0, 0, 10, 1), +(282, 0, 0, 0, 'Lower Barracks 5', 600, 3, 0, 0, 0, 0, 10, 1), +(283, 0, 0, 0, 'Lower Barracks 6', 600, 3, 0, 0, 0, 0, 10, 1), +(284, 0, 0, 0, 'Lower Barracks 7', 600, 3, 0, 0, 0, 0, 10, 1), +(285, 0, 0, 0, 'Lower Barracks 8', 600, 3, 0, 0, 0, 0, 10, 1), +(286, 0, 0, 0, 'Lower Barracks 9', 600, 3, 0, 0, 0, 0, 10, 1), +(287, 0, 0, 0, 'Lower Barracks 10', 600, 3, 0, 0, 0, 0, 10, 1), +(288, 0, 0, 0, 'Lower Barracks 11', 600, 3, 0, 0, 0, 0, 10, 1), +(289, 0, 0, 0, 'Lower Barracks 12', 600, 3, 0, 0, 0, 0, 10, 1), +(290, 0, 0, 0, 'Lower Barracks 13', 600, 3, 0, 0, 0, 0, 10, 1), +(291, 0, 0, 0, 'Lower Barracks 14', 600, 3, 0, 0, 0, 0, 10, 1), +(292, 0, 0, 0, 'Lower Barracks 15', 600, 3, 0, 0, 0, 0, 10, 1), +(293, 0, 0, 0, 'Lower Barracks 16', 600, 3, 0, 0, 0, 0, 10, 1), +(294, 0, 0, 0, 'Lower Barracks 17', 600, 3, 0, 0, 0, 0, 10, 1), +(295, 0, 0, 0, 'Lower Barracks 18', 600, 3, 0, 0, 0, 0, 10, 1), +(296, 0, 0, 0, 'Lower Barracks 19', 600, 3, 0, 0, 0, 0, 10, 1), +(297, 0, 0, 0, 'Lower Barracks 20', 600, 3, 0, 0, 0, 0, 10, 1), +(298, 0, 0, 0, 'Lower Barracks 21', 600, 3, 0, 0, 0, 0, 10, 1), +(299, 0, 0, 0, 'Lower Barracks 22', 600, 3, 0, 0, 0, 0, 10, 1), +(300, 0, 0, 0, 'Lower Barracks 23', 600, 3, 0, 0, 0, 0, 10, 1), +(301, 0, 0, 0, 'Lower Barracks 24', 600, 3, 0, 0, 0, 0, 10, 1), +(302, 0, 0, 0, 'Tunnel Gardens 1', 3440, 3, 0, 0, 0, 0, 27, 3), +(303, 0, 0, 0, 'Tunnel Gardens 2 ', 3440, 3, 0, 0, 0, 0, 27, 3), +(304, 0, 0, 0, 'Tunnel Gardens 3', 3800, 3, 0, 0, 0, 0, 30, 3), +(305, 0, 0, 0, 'Tunnel Gardens 4', 3800, 3, 0, 0, 0, 0, 30, 3), +(306, 0, 0, 0, 'Tunnel Gardens 5', 2620, 3, 0, 0, 0, 0, 21, 2), +(307, 0, 0, 0, 'Tunnel Gardens 6', 2620, 3, 0, 0, 0, 0, 21, 2), +(308, 0, 0, 0, 'Tunnel Gardens 7', 2620, 3, 0, 0, 0, 0, 21, 2), +(309, 0, 0, 0, 'Tunnel Gardens 8', 2620, 3, 0, 0, 0, 0, 21, 2), +(310, 0, 0, 0, 'Tunnel Gardens 9', 1900, 3, 0, 0, 0, 0, 15, 2), +(311, 0, 0, 0, 'Tunnel Gardens 10', 1900, 3, 0, 0, 0, 0, 15, 2), +(312, 0, 0, 0, 'Tunnel Gardens 11', 2020, 3, 0, 0, 0, 0, 16, 2), +(313, 0, 0, 0, 'Tunnel Gardens 12', 2020, 3, 0, 0, 0, 0, 16, 2), +(314, 0, 0, 0, 'Marble Guildhall', 32020, 3, 0, 0, 0, 0, 338, 17), +(315, 0, 0, 0, 'Iron Guildhall', 29420, 3, 0, 0, 0, 0, 308, 18), +(316, 0, 0, 0, 'Granite Guildhall', 34090, 3, 0, 0, 0, 0, 361, 17), +(317, 0, 0, 0, 'Outlaw Camp 1', 3220, 3, 0, 0, 0, 0, 39, 2), +(318, 0, 0, 0, 'Outlaw Camp 2', 560, 3, 0, 0, 0, 0, 7, 1), +(319, 0, 0, 0, 'Outlaw Camp 3', 1380, 3, 0, 0, 0, 0, 16, 2), +(320, 0, 0, 0, 'Outlaw Camp 4', 400, 3, 0, 0, 0, 0, 5, 1), +(321, 0, 0, 0, 'Outlaw Camp 5', 400, 3, 0, 0, 0, 0, 5, 1), +(322, 0, 0, 0, 'Outlaw Camp 6', 400, 3, 0, 0, 0, 0, 5, 1), +(323, 0, 0, 0, 'Outlaw Camp 7', 1460, 3, 0, 0, 0, 0, 17, 2), +(324, 0, 0, 0, 'Outlaw Camp 8', 560, 3, 0, 0, 0, 0, 7, 1), +(325, 0, 0, 0, 'Outlaw Camp 9', 400, 3, 0, 0, 0, 0, 5, 1), +(326, 0, 0, 0, 'Outlaw Camp 10', 400, 3, 0, 0, 0, 0, 5, 1), +(327, 0, 0, 0, 'Outlaw Camp 11', 400, 3, 0, 0, 0, 0, 5, 1), +(328, 0, 0, 0, 'Outlaw Camp 12 (Shop)', 560, 3, 0, 0, 0, 0, 7, 0), +(329, 0, 0, 0, 'Outlaw Camp 13 (Shop)', 560, 3, 0, 0, 0, 0, 7, 0), +(330, 0, 0, 0, 'Outlaw Camp 14 (Shop)', 1280, 3, 0, 0, 0, 0, 16, 0), +(331, 0, 0, 0, 'Outlaw Castle', 15200, 3, 0, 0, 0, 0, 180, 9), +(332, 0, 0, 0, 'Blessed Shield Guildhall', 15380, 7, 0, 0, 0, 0, 162, 9), +(333, 0, 0, 0, 'Steel Home', 26490, 7, 0, 0, 0, 0, 281, 13), +(334, 0, 0, 0, 'Swamp Watch', 21080, 7, 0, 0, 0, 0, 222, 12), +(335, 0, 0, 0, 'Golden Axe Guildhall', 20070, 7, 0, 0, 0, 0, 213, 10), +(336, 0, 0, 0, 'Valorous Venore', 28070, 7, 0, 0, 0, 0, 303, 9), +(337, 0, 0, 0, 'Dagger Alley 1', 5230, 7, 0, 0, 0, 0, 57, 2), +(338, 0, 0, 0, 'Iron Alley 1', 6600, 7, 0, 0, 0, 0, 70, 4), +(339, 0, 0, 0, 'Iron Alley 2', 6600, 7, 0, 0, 0, 0, 70, 4), +(340, 0, 0, 0, 'Dream Street 1 (Shop)', 8560, 7, 0, 0, 0, 0, 94, 2), +(341, 0, 0, 0, 'Dream Street 2', 6580, 7, 0, 0, 0, 0, 72, 2), +(342, 0, 0, 0, 'Dream Street 3', 5320, 7, 0, 0, 0, 0, 58, 2), +(343, 0, 0, 0, 'Dream Street 4', 7230, 7, 0, 0, 0, 0, 77, 4), +(344, 0, 0, 0, 'Elm Street 1', 5320, 7, 0, 0, 0, 0, 58, 2), +(345, 0, 0, 0, 'Elm Street 2', 5230, 7, 0, 0, 0, 0, 57, 2), +(346, 0, 0, 0, 'Elm Street 3', 5510, 7, 0, 0, 0, 0, 59, 3), +(347, 0, 0, 0, 'Elm Street 4', 5140, 7, 0, 0, 0, 0, 56, 2), +(348, 0, 0, 0, 'Seagull Walk 1', 10090, 7, 0, 0, 0, 0, 111, 2), +(349, 0, 0, 0, 'Seagull Walk 2', 5330, 7, 0, 0, 0, 0, 57, 3), +(350, 0, 0, 0, 'Lucky Lane 1 (Shop)', 13620, 7, 0, 0, 0, 0, 148, 4), +(351, 0, 0, 0, 'Paupers Palace, Flat 01', 810, 7, 0, 0, 0, 0, 9, 1), +(352, 0, 0, 0, 'Paupers Palace, Flat 02', 900, 7, 0, 0, 0, 0, 10, 1), +(353, 0, 0, 0, 'Paupers Palace, Flat 03', 810, 7, 0, 0, 0, 0, 9, 1), +(354, 0, 0, 0, 'Paupers Palace, Flat 04', 900, 7, 0, 0, 0, 0, 10, 1), +(355, 0, 0, 0, 'Paupers Palace, Flat 05', 630, 7, 0, 0, 0, 0, 7, 1), +(356, 0, 0, 0, 'Paupers Palace, Flat 06', 900, 7, 0, 0, 0, 0, 10, 1), +(357, 0, 0, 0, 'Paupers Palace, Flat 07', 1270, 7, 0, 0, 0, 0, 13, 2), +(358, 0, 0, 0, 'Paupers Palace, Flat 11', 630, 7, 0, 0, 0, 0, 7, 1), +(359, 0, 0, 0, 'Paupers Palace, Flat 12', 1270, 7, 0, 0, 0, 0, 13, 2), +(360, 0, 0, 0, 'Paupers Palace, Flat 13', 900, 7, 0, 0, 0, 0, 10, 1), +(361, 0, 0, 0, 'Paupers Palace, Flat 14', 1170, 7, 0, 0, 0, 0, 13, 1), +(362, 0, 0, 0, 'Paupers Palace, Flat 15', 900, 7, 0, 0, 0, 0, 10, 1), +(363, 0, 0, 0, 'Paupers Palace, Flat 16', 1170, 7, 0, 0, 0, 0, 13, 1), +(364, 0, 0, 0, 'Paupers Palace, Flat 17', 900, 7, 0, 0, 0, 0, 10, 1), +(365, 0, 0, 0, 'Paupers Palace, Flat 18', 630, 7, 0, 0, 0, 0, 7, 1), +(366, 0, 0, 0, 'Paupers Palace, Flat 21', 630, 7, 0, 0, 0, 0, 7, 1), +(367, 0, 0, 0, 'Paupers Palace, Flat 22', 900, 7, 0, 0, 0, 0, 10, 1), +(368, 0, 0, 0, 'Paupers Palace, Flat 23', 1170, 7, 0, 0, 0, 0, 13, 1), +(369, 0, 0, 0, 'Paupers Palace, Flat 24', 900, 7, 0, 0, 0, 0, 10, 1), +(370, 0, 0, 0, 'Paupers Palace, Flat 25', 1170, 7, 0, 0, 0, 0, 13, 1), +(371, 0, 0, 0, 'Paupers Palace, Flat 26', 900, 7, 0, 0, 0, 0, 10, 1), +(372, 0, 0, 0, 'Paupers Palace, Flat 27', 1270, 7, 0, 0, 0, 0, 13, 2), +(373, 0, 0, 0, 'Paupers Palace, Flat 28', 630, 7, 0, 0, 0, 0, 7, 1), +(374, 0, 0, 0, 'Paupers Palace, Flat 31', 1710, 7, 0, 0, 0, 0, 19, 1), +(375, 0, 0, 0, 'Paupers Palace, Flat 32', 2170, 7, 0, 0, 0, 0, 23, 2), +(376, 0, 0, 0, 'Paupers Palace, Flat 33', 1530, 7, 0, 0, 0, 0, 17, 1), +(377, 0, 0, 0, 'Paupers Palace, Flat 34', 3250, 7, 0, 0, 0, 0, 35, 2), +(378, 0, 0, 0, 'Salvation Street 1 (Shop)', 12180, 7, 0, 0, 0, 0, 132, 4), +(379, 0, 0, 0, 'Salvation Street 2', 7480, 7, 0, 0, 0, 0, 82, 2), +(380, 0, 0, 0, 'Salvation Street 3', 7480, 7, 0, 0, 0, 0, 82, 2), +(381, 0, 0, 0, 'Mystic Lane 1', 5690, 7, 0, 0, 0, 0, 61, 3), +(382, 0, 0, 0, 'Mystic Lane 2', 5860, 7, 0, 0, 0, 0, 64, 2), +(383, 0, 0, 0, 'Silver Street 1', 5130, 7, 0, 0, 0, 0, 57, 1), +(384, 0, 0, 0, 'Silver Street 2', 3960, 7, 0, 0, 0, 0, 44, 1), +(385, 0, 0, 0, 'Silver Street 3', 3960, 7, 0, 0, 0, 0, 44, 1), +(386, 0, 0, 0, 'Silver Street 4', 6490, 7, 0, 0, 0, 0, 71, 2), +(387, 0, 0, 0, 'Loot Lane 1 (Shop)', 8930, 7, 0, 0, 0, 0, 97, 3), +(388, 0, 0, 0, 'Old Lighthouse', 7120, 7, 0, 0, 0, 0, 78, 2), +(389, 0, 0, 0, 'Market Street 1', 13160, 7, 0, 0, 0, 0, 144, 3), +(390, 0, 0, 0, 'Market Street 2', 9650, 7, 0, 0, 0, 0, 105, 3), +(391, 0, 0, 0, 'Market Street 3', 6850, 7, 0, 0, 0, 0, 75, 2), +(392, 0, 0, 0, 'Market Street 4 (Shop)', 10010, 7, 0, 0, 0, 0, 109, 3), +(393, 0, 0, 0, 'Market Street 5 (Shop)', 12450, 7, 0, 0, 0, 0, 135, 4), +(394, 0, 0, 0, 'Market Street 6', 10570, 7, 0, 0, 0, 0, 113, 5), +(395, 0, 0, 0, 'Market Street 7', 4510, 7, 0, 0, 0, 0, 49, 2), +(396, 0, 0, 0, 'Shadow Towers', 41900, 4, 0, 0, 0, 0, 402, 18), +(397, 0, 0, 0, 'The Hideout', 39700, 4, 0, 0, 0, 0, 378, 20), +(398, 0, 0, 0, 'Underwood 1', 2890, 4, 0, 0, 0, 0, 31, 2), +(399, 0, 0, 0, 'Underwood 2', 2890, 4, 0, 0, 0, 0, 31, 2), +(400, 0, 0, 0, 'Underwood 3', 3170, 4, 0, 0, 0, 0, 33, 3), +(401, 0, 0, 0, 'Underwood 4', 4170, 4, 0, 0, 0, 0, 43, 4), +(402, 0, 0, 0, 'Underwood 5', 2540, 4, 0, 0, 0, 0, 26, 3), +(403, 0, 0, 0, 'Underwood 6', 2990, 4, 0, 0, 0, 0, 31, 3), +(404, 0, 0, 0, 'Underwood 7', 2720, 4, 0, 0, 0, 0, 28, 3), +(405, 0, 0, 0, 'Underwood 8', 1630, 4, 0, 0, 0, 0, 17, 2), +(406, 0, 0, 0, 'Underwood 9', 1170, 4, 0, 0, 0, 0, 13, 1), +(407, 0, 0, 0, 'Underwood 10', 1170, 4, 0, 0, 0, 0, 13, 1), +(408, 0, 0, 0, 'Ab\'Dendriel Clanhall', 28800, 4, 0, 0, 0, 0, 310, 10), +(409, 0, 0, 0, 'Castle of the Winds', 46070, 4, 0, 0, 0, 0, 493, 18), +(410, 0, 0, 0, 'Great Willow 1a', 1000, 4, 0, 0, 0, 0, 10, 1), +(411, 0, 0, 0, 'Great Willow 1b', 1300, 4, 0, 0, 0, 0, 13, 1), +(412, 0, 0, 0, 'Great Willow 1c', 1300, 4, 0, 0, 0, 0, 13, 1), +(413, 0, 0, 0, 'Great Willow 2a', 1300, 4, 0, 0, 0, 0, 13, 1), +(414, 0, 0, 0, 'Great Willow 2b', 900, 4, 0, 0, 0, 0, 9, 1), +(415, 0, 0, 0, 'Great Willow 2c', 1300, 4, 0, 0, 0, 0, 13, 1), +(416, 0, 0, 0, 'Great Willow 2d', 900, 4, 0, 0, 0, 0, 9, 1), +(417, 0, 0, 0, 'Great Willow 3a', 1300, 4, 0, 0, 0, 0, 13, 1), +(418, 0, 0, 0, 'Great Willow 3b', 900, 4, 0, 0, 0, 0, 9, 1), +(419, 0, 0, 0, 'Great Willow 3c', 1300, 4, 0, 0, 0, 0, 13, 1), +(420, 0, 0, 0, 'Great Willow 3d', 900, 4, 0, 0, 0, 0, 9, 1), +(421, 0, 0, 0, 'Great Willow 4a', 1800, 4, 0, 0, 0, 0, 17, 2), +(422, 0, 0, 0, 'Great Willow 4b', 1800, 4, 0, 0, 0, 0, 17, 2), +(423, 0, 0, 0, 'Great Willow 4c', 1800, 4, 0, 0, 0, 0, 17, 2), +(424, 0, 0, 0, 'Great Willow 4d', 1500, 4, 0, 0, 0, 0, 15, 1), +(425, 0, 0, 0, 'Mangrove 1', 3300, 4, 0, 0, 0, 0, 31, 3), +(426, 0, 0, 0, 'Mangrove 2', 2600, 4, 0, 0, 0, 0, 25, 2), +(427, 0, 0, 0, 'Mangrove 3', 2200, 4, 0, 0, 0, 0, 21, 2), +(428, 0, 0, 0, 'Mangrove 4', 1800, 4, 0, 0, 0, 0, 17, 2), +(429, 0, 0, 0, 'Treetop 1', 1300, 4, 0, 0, 0, 0, 13, 1), +(430, 0, 0, 0, 'Treetop 2', 1300, 4, 0, 0, 0, 0, 13, 1), +(431, 0, 0, 0, 'Treetop 3 (Shop)', 2500, 4, 0, 0, 0, 0, 25, 1), +(432, 0, 0, 0, 'Treetop 4 (Shop)', 2500, 4, 0, 0, 0, 0, 25, 1), +(433, 0, 0, 0, 'Treetop 5 (Shop)', 2700, 4, 0, 0, 0, 0, 27, 1), +(434, 0, 0, 0, 'Treetop 6', 900, 4, 0, 0, 0, 0, 9, 1), +(435, 0, 0, 0, 'Treetop 7', 1600, 4, 0, 0, 0, 0, 16, 1), +(436, 0, 0, 0, 'Treetop 8', 1600, 4, 0, 0, 0, 0, 16, 1), +(437, 0, 0, 0, 'Treetop 9', 2200, 4, 0, 0, 0, 0, 21, 2), +(438, 0, 0, 0, 'Treetop 10', 2200, 4, 0, 0, 0, 0, 21, 2), +(439, 0, 0, 0, 'Treetop 11', 1700, 4, 0, 0, 0, 0, 16, 2), +(440, 0, 0, 0, 'Treetop 12 (Shop)', 2700, 4, 0, 0, 0, 0, 27, 1), +(441, 0, 0, 0, 'Treetop 13', 2700, 4, 0, 0, 0, 0, 26, 2), +(442, 0, 0, 0, 'Coastwood 1', 1860, 4, 0, 0, 0, 0, 16, 2), +(443, 0, 0, 0, 'Coastwood 2', 1860, 4, 0, 0, 0, 0, 16, 2), +(444, 0, 0, 0, 'Coastwood 3', 2520, 4, 0, 0, 0, 0, 22, 2), +(445, 0, 0, 0, 'Coastwood 4', 2190, 4, 0, 0, 0, 0, 19, 2), +(446, 0, 0, 0, 'Coastwood 5', 2960, 4, 0, 0, 0, 0, 26, 2), +(447, 0, 0, 0, 'Coastwood 6 (Shop)', 3190, 4, 0, 0, 0, 0, 29, 1), +(448, 0, 0, 0, 'Coastwood 7', 1320, 4, 0, 0, 0, 0, 12, 1), +(449, 0, 0, 0, 'Coastwood 8', 2410, 4, 0, 0, 0, 0, 21, 2), +(450, 0, 0, 0, 'Coastwood 9', 1870, 4, 0, 0, 0, 0, 17, 1), +(451, 0, 0, 0, 'Coastwood 10', 3060, 4, 0, 0, 0, 0, 26, 3), +(452, 0, 0, 0, 'Shadow Caves 1', 600, 4, 0, 0, 0, 0, 10, 1), +(453, 0, 0, 0, 'Shadow Caves 2', 600, 4, 0, 0, 0, 0, 10, 1), +(454, 0, 0, 0, 'Shadow Caves 3', 600, 4, 0, 0, 0, 0, 10, 1), +(455, 0, 0, 0, 'Shadow Caves 4', 600, 4, 0, 0, 0, 0, 10, 1), +(456, 0, 0, 0, 'Shadow Caves 11', 600, 4, 0, 0, 0, 0, 10, 1), +(457, 0, 0, 0, 'Shadow Caves 12', 600, 4, 0, 0, 0, 0, 10, 1), +(458, 0, 0, 0, 'Shadow Caves 13', 600, 4, 0, 0, 0, 0, 10, 1), +(459, 0, 0, 0, 'Shadow Caves 14', 600, 4, 0, 0, 0, 0, 10, 1), +(460, 0, 0, 0, 'Shadow Caves 15', 600, 4, 0, 0, 0, 0, 10, 1), +(461, 0, 0, 0, 'Shadow Caves 16', 600, 4, 0, 0, 0, 0, 10, 1), +(462, 0, 0, 0, 'Shadow Caves 17', 600, 4, 0, 0, 0, 0, 10, 1), +(463, 0, 0, 0, 'Shadow Caves 18', 600, 4, 0, 0, 0, 0, 10, 1), +(464, 0, 0, 0, 'Shadow Caves 21', 600, 4, 0, 0, 0, 0, 10, 1), +(465, 0, 0, 0, 'Shadow Caves 22', 600, 4, 0, 0, 0, 0, 10, 1), +(466, 0, 0, 0, 'Shadow Caves 23', 600, 4, 0, 0, 0, 0, 10, 1), +(467, 0, 0, 0, 'Shadow Caves 24', 600, 4, 0, 0, 0, 0, 10, 1), +(468, 0, 0, 0, 'Shadow Caves 25', 600, 4, 0, 0, 0, 0, 10, 1), +(469, 0, 0, 0, 'Shadow Caves 26', 600, 4, 0, 0, 0, 0, 10, 1), +(470, 0, 0, 0, 'Shadow Caves 27', 600, 4, 0, 0, 0, 0, 10, 1), +(471, 0, 0, 0, 'Shadow Caves 28', 600, 4, 0, 0, 0, 0, 10, 1), +(472, 0, 0, 0, 'Haggler\'s Hangout 1', 2700, 9, 0, 0, 0, 0, 26, 2), +(473, 0, 0, 0, 'Haggler\'s Hangout 2', 2600, 9, 0, 0, 0, 0, 26, 1), +(474, 0, 0, 0, 'Haggler\'s Hangout 3', 14800, 9, 0, 0, 0, 0, 145, 4), +(475, 0, 0, 0, 'Haggler\'s Hangout 4a', 3700, 9, 0, 0, 0, 0, 37, 1), +(476, 0, 0, 0, 'Haggler\'s Hangout 4b', 3100, 9, 0, 0, 0, 0, 31, 1), +(477, 0, 0, 0, 'Haggler\'s Hangout 5', 3100, 9, 0, 0, 0, 0, 31, 1), +(478, 0, 0, 0, 'Haggler\'s Hangout 6', 12600, 9, 0, 0, 0, 0, 123, 4), +(479, 0, 0, 0, 'Banana Bay 1', 900, 9, 0, 0, 0, 0, 10, 1), +(480, 0, 0, 0, 'Banana Bay 2', 1530, 9, 0, 0, 0, 0, 17, 1), +(481, 0, 0, 0, 'Banana Bay 3', 900, 9, 0, 0, 0, 0, 10, 1), +(482, 0, 0, 0, 'Banana Bay 4', 900, 9, 0, 0, 0, 0, 10, 1), +(483, 0, 0, 0, 'Crocodile Bridge 1', 1990, 9, 0, 0, 0, 0, 21, 2), +(484, 0, 0, 0, 'Crocodile Bridge 2', 1630, 9, 0, 0, 0, 0, 17, 2), +(485, 0, 0, 0, 'Crocodile Bridge 3', 2440, 9, 0, 0, 0, 0, 26, 2), +(486, 0, 0, 0, 'Crocodile Bridge 4', 9210, 9, 0, 0, 0, 0, 99, 4), +(487, 0, 0, 0, 'Crocodile Bridge 5', 7840, 9, 0, 0, 0, 0, 86, 2), +(488, 0, 0, 0, 'Woodway 1', 1530, 9, 0, 0, 0, 0, 17, 1), +(489, 0, 0, 0, 'Woodway 2', 1170, 9, 0, 0, 0, 0, 13, 1), +(490, 0, 0, 0, 'Woodway 3', 2980, 9, 0, 0, 0, 0, 32, 2), +(491, 0, 0, 0, 'Woodway 4', 810, 9, 0, 0, 0, 0, 9, 1), +(492, 0, 0, 0, 'Flamingo Flats 1', 1270, 9, 0, 0, 0, 0, 13, 2), +(493, 0, 0, 0, 'Flamingo Flats 2', 1990, 9, 0, 0, 0, 0, 21, 2), +(494, 0, 0, 0, 'Flamingo Flats 3', 1270, 9, 0, 0, 0, 0, 13, 2), +(495, 0, 0, 0, 'Flamingo Flats 4', 1630, 9, 0, 0, 0, 0, 17, 2), +(496, 0, 0, 0, 'Flamingo Flats 5', 3690, 9, 0, 0, 0, 0, 41, 1), +(497, 0, 0, 0, 'Bamboo Garden 1', 3080, 9, 0, 0, 0, 0, 32, 3), +(498, 0, 0, 0, 'Bamboo Garden 2', 1990, 9, 0, 0, 0, 0, 21, 2), +(499, 0, 0, 0, 'Bamboo Garden 3', 2980, 9, 0, 0, 0, 0, 32, 2), +(500, 0, 0, 0, 'Coconut Quay 1', 3430, 9, 0, 0, 0, 0, 37, 2), +(501, 0, 0, 0, 'Coconut Quay 2', 1990, 9, 0, 0, 0, 0, 21, 2), +(502, 0, 0, 0, 'Coconut Quay 3', 3990, 9, 0, 0, 0, 0, 41, 4), +(503, 0, 0, 0, 'Coconut Quay 4', 4070, 9, 0, 0, 0, 0, 43, 3), +(504, 0, 0, 0, 'River Homes 1', 6770, 9, 0, 0, 0, 0, 73, 3), +(505, 0, 0, 0, 'River Homes 2a', 2440, 9, 0, 0, 0, 0, 26, 2), +(506, 0, 0, 0, 'River Homes 2b', 2990, 9, 0, 0, 0, 0, 31, 3), +(507, 0, 0, 0, 'River Homes 3', 9510, 9, 0, 0, 0, 0, 99, 7), +(508, 0, 0, 0, 'Jungle Edge 1', 4790, 9, 0, 0, 0, 0, 51, 3), +(509, 0, 0, 0, 'Jungle Edge 2', 6140, 9, 0, 0, 0, 0, 66, 3), +(510, 0, 0, 0, 'Jungle Edge 3', 1630, 9, 0, 0, 0, 0, 17, 2), +(511, 0, 0, 0, 'Jungle Edge 4', 1630, 9, 0, 0, 0, 0, 17, 2), +(512, 0, 0, 0, 'Jungle Edge 5', 1630, 9, 0, 0, 0, 0, 17, 2), +(513, 0, 0, 0, 'Jungle Edge 6', 900, 9, 0, 0, 0, 0, 10, 1), +(514, 0, 0, 0, 'Shark Manor', 16160, 9, 0, 0, 0, 0, 164, 15), +(515, 0, 0, 0, 'Bamboo Fortress', 42040, 9, 0, 0, 0, 0, 446, 20), +(516, 0, 0, 0, 'The Treehouse', 46040, 9, 0, 0, 0, 0, 548, 23), +(517, 0, 0, 0, 'Castle Shop 1', 3780, 5, 0, 0, 0, 0, 42, 1), +(518, 0, 0, 0, 'Castle Shop 2', 3780, 5, 0, 0, 0, 0, 42, 1), +(519, 0, 0, 0, 'Castle Shop 3', 3780, 5, 0, 0, 0, 0, 42, 1), +(520, 0, 0, 0, 'Castle, 3rd Floor, Flat 01', 1170, 5, 0, 0, 0, 0, 13, 1), +(521, 0, 0, 0, 'Castle, 3rd Floor, Flat 02', 1530, 5, 0, 0, 0, 0, 17, 1), +(522, 0, 0, 0, 'Castle, 3rd Floor, Flat 03', 1170, 5, 0, 0, 0, 0, 13, 1), +(523, 0, 0, 0, 'Castle, 3rd Floor, Flat 04', 1170, 5, 0, 0, 0, 0, 13, 1), +(524, 0, 0, 0, 'Castle, 3rd Floor, Flat 05', 1530, 5, 0, 0, 0, 0, 17, 1), +(525, 0, 0, 0, 'Castle, 3rd Floor, Flat 06', 1990, 5, 0, 0, 0, 0, 21, 2), +(526, 0, 0, 0, 'Castle, 3rd Floor, Flat 07', 1440, 5, 0, 0, 0, 0, 16, 1), +(527, 0, 0, 0, 'Castle, 4th Floor, Flat 01', 1170, 5, 0, 0, 0, 0, 13, 1), +(528, 0, 0, 0, 'Castle, 4th Floor, Flat 02', 1530, 5, 0, 0, 0, 0, 17, 1), +(529, 0, 0, 0, 'Castle, 4th Floor, Flat 03', 1170, 5, 0, 0, 0, 0, 13, 1), +(530, 0, 0, 0, 'Castle, 4th Floor, Flat 04', 1170, 5, 0, 0, 0, 0, 13, 1), +(531, 0, 0, 0, 'Castle, 4th Floor, Flat 05', 1530, 5, 0, 0, 0, 0, 17, 1), +(532, 0, 0, 0, 'Castle, 4th Floor, Flat 06', 1890, 5, 0, 0, 0, 0, 21, 1), +(533, 0, 0, 0, 'Castle, 4th Floor, Flat 07', 1440, 5, 0, 0, 0, 0, 16, 1), +(534, 0, 0, 0, 'Castle, 4th Floor, Flat 08', 1890, 5, 0, 0, 0, 0, 21, 1), +(535, 0, 0, 0, 'Castle, 4th Floor, Flat 09', 1440, 5, 0, 0, 0, 0, 16, 1), +(536, 0, 0, 0, 'Castle, Basement, Flat 01', 1170, 5, 0, 0, 0, 0, 13, 1), +(537, 0, 0, 0, 'Castle, Basement, Flat 02', 1170, 5, 0, 0, 0, 0, 13, 1), +(538, 0, 0, 0, 'Castle, Basement, Flat 03', 1170, 5, 0, 0, 0, 0, 13, 1), +(539, 0, 0, 0, 'Castle, Basement, Flat 04', 1170, 5, 0, 0, 0, 0, 13, 1), +(540, 0, 0, 0, 'Castle, Basement, Flat 05', 1170, 5, 0, 0, 0, 0, 13, 1), +(541, 0, 0, 0, 'Castle, Basement, Flat 06', 1170, 5, 0, 0, 0, 0, 13, 1), +(542, 0, 0, 0, 'Castle, Basement, Flat 07', 1170, 5, 0, 0, 0, 0, 13, 1), +(543, 0, 0, 0, 'Castle, Basement, Flat 08', 1170, 5, 0, 0, 0, 0, 13, 1), +(544, 0, 0, 0, 'Castle, Basement, Flat 09', 1170, 5, 0, 0, 0, 0, 13, 1), +(545, 0, 0, 0, 'Castle Street 1', 5600, 5, 0, 0, 0, 0, 60, 3), +(546, 0, 0, 0, 'Castle Street 2', 2890, 5, 0, 0, 0, 0, 31, 2), +(547, 0, 0, 0, 'Castle Street 3', 3430, 5, 0, 0, 0, 0, 37, 2), +(548, 0, 0, 0, 'Castle Street 4', 3430, 5, 0, 0, 0, 0, 37, 2), +(549, 0, 0, 0, 'Castle Street 5', 3430, 5, 0, 0, 0, 0, 37, 2), +(550, 0, 0, 0, 'Edron Flats, Flat 01', 800, 5, 0, 0, 0, 0, 10, 1), +(551, 0, 0, 0, 'Edron Flats, Flat 02', 1620, 5, 0, 0, 0, 0, 19, 2), +(552, 0, 0, 0, 'Edron Flats, Flat 03', 800, 5, 0, 0, 0, 0, 10, 1), +(553, 0, 0, 0, 'Edron Flats, Flat 04', 800, 5, 0, 0, 0, 0, 10, 1), +(554, 0, 0, 0, 'Edron Flats, Flat 05', 800, 5, 0, 0, 0, 0, 10, 1), +(555, 0, 0, 0, 'Edron Flats, Flat 06', 800, 5, 0, 0, 0, 0, 10, 1), +(556, 0, 0, 0, 'Edron Flats, Flat 07', 800, 5, 0, 0, 0, 0, 10, 1), +(557, 0, 0, 0, 'Edron Flats, Flat 08', 800, 5, 0, 0, 0, 0, 10, 1), +(558, 0, 0, 0, 'Edron Flats, Flat 11', 800, 5, 0, 0, 0, 0, 10, 1), +(559, 0, 0, 0, 'Edron Flats, Flat 12', 800, 5, 0, 0, 0, 0, 10, 1), +(560, 0, 0, 0, 'Edron Flats, Flat 13', 800, 5, 0, 0, 0, 0, 10, 1), +(561, 0, 0, 0, 'Edron Flats, Flat 14', 800, 5, 0, 0, 0, 0, 10, 1), +(562, 0, 0, 0, 'Edron Flats, Flat 15', 800, 5, 0, 0, 0, 0, 10, 1), +(563, 0, 0, 0, 'Edron Flats, Flat 16', 800, 5, 0, 0, 0, 0, 10, 1), +(564, 0, 0, 0, 'Edron Flats, Flat 17', 800, 5, 0, 0, 0, 0, 10, 1), +(565, 0, 0, 0, 'Edron Flats, Flat 18', 800, 5, 0, 0, 0, 0, 10, 1), +(566, 0, 0, 0, 'Edron Flats, Flat 21', 1620, 5, 0, 0, 0, 0, 19, 2), +(567, 0, 0, 0, 'Edron Flats, Flat 22', 800, 5, 0, 0, 0, 0, 10, 1), +(568, 0, 0, 0, 'Edron Flats, Flat 23', 800, 5, 0, 0, 0, 0, 10, 1), +(569, 0, 0, 0, 'Edron Flats, Flat 24', 800, 5, 0, 0, 0, 0, 10, 1), +(570, 0, 0, 0, 'Edron Flats, Flat 25', 800, 5, 0, 0, 0, 0, 10, 1), +(571, 0, 0, 0, 'Edron Flats, Flat 26', 800, 5, 0, 0, 0, 0, 10, 1), +(572, 0, 0, 0, 'Edron Flats, Flat 27', 800, 5, 0, 0, 0, 0, 10, 1), +(573, 0, 0, 0, 'Edron Flats, Flat 28', 800, 5, 0, 0, 0, 0, 10, 1), +(574, 0, 0, 0, 'Edron Flats, Basement Flat 1', 2980, 5, 0, 0, 0, 0, 36, 2), +(575, 0, 0, 0, 'Edron Flats, Basement Flat 2', 2980, 5, 0, 0, 0, 0, 36, 2), +(576, 0, 0, 0, 'Central Circle 1', 5940, 5, 0, 0, 0, 0, 73, 2), +(577, 0, 0, 0, 'Central Circle 2', 6500, 5, 0, 0, 0, 0, 80, 2), +(578, 0, 0, 0, 'Central Circle 3', 7920, 5, 0, 0, 0, 0, 94, 5), +(579, 0, 0, 0, 'Central Circle 4', 7920, 5, 0, 0, 0, 0, 94, 5), +(580, 0, 0, 0, 'Central Circle 5', 7920, 5, 0, 0, 0, 0, 94, 5), +(581, 0, 0, 0, 'Central Circle 6 (Shop)', 7860, 5, 0, 0, 0, 0, 97, 2), +(582, 0, 0, 0, 'Central Circle 7 (Shop)', 7860, 5, 0, 0, 0, 0, 97, 2), +(583, 0, 0, 0, 'Central Circle 8 (Shop)', 7860, 5, 0, 0, 0, 0, 97, 2), +(584, 0, 0, 0, 'Central Circle 9a', 1780, 5, 0, 0, 0, 0, 21, 2), +(585, 0, 0, 0, 'Central Circle 9b', 1780, 5, 0, 0, 0, 0, 21, 2), +(586, 0, 0, 0, 'Wood Avenue 1', 3430, 5, 0, 0, 0, 0, 37, 2), +(587, 0, 0, 0, 'Wood Avenue 2', 3430, 5, 0, 0, 0, 0, 37, 2), +(588, 0, 0, 0, 'Wood Avenue 3', 3430, 5, 0, 0, 0, 0, 37, 2), +(589, 0, 0, 0, 'Wood Avenue 4', 3430, 5, 0, 0, 0, 0, 37, 2), +(590, 0, 0, 0, 'Wood Avenue 5', 3430, 5, 0, 0, 0, 0, 37, 2), +(591, 0, 0, 0, 'Wood Avenue 6a', 2800, 5, 0, 0, 0, 0, 30, 2), +(592, 0, 0, 0, 'Wood Avenue 6b', 2800, 5, 0, 0, 0, 0, 30, 2), +(593, 0, 0, 0, 'Wood Avenue 7', 11720, 5, 0, 0, 0, 0, 128, 3), +(594, 0, 0, 0, 'Wood Avenue 8', 11720, 5, 0, 0, 0, 0, 128, 3), +(595, 0, 0, 0, 'Wood Avenue 9a', 2980, 5, 0, 0, 0, 0, 32, 2), +(596, 0, 0, 0, 'Wood Avenue 9b', 2890, 5, 0, 0, 0, 0, 31, 2), +(597, 0, 0, 0, 'Wood Avenue 10a', 2980, 5, 0, 0, 0, 0, 32, 2), +(598, 0, 0, 0, 'Wood Avenue 10b', 2990, 5, 0, 0, 0, 0, 31, 3), +(599, 0, 0, 0, 'Wood Avenue 11', 13910, 5, 0, 0, 0, 0, 149, 6), +(600, 0, 0, 0, 'Wood Avenue 4a', 2890, 5, 0, 0, 0, 0, 31, 2), +(601, 0, 0, 0, 'Wood Avenue 4b', 2890, 5, 0, 0, 0, 0, 31, 2), +(602, 0, 0, 0, 'Wood Avenue 4c', 3430, 5, 0, 0, 0, 0, 37, 2), +(603, 0, 0, 0, 'Sky Lane, Guild 1', 40090, 5, 0, 0, 0, 0, 421, 23), +(604, 0, 0, 0, 'Sky Lane, Guild 2', 37300, 5, 0, 0, 0, 0, 400, 14), +(605, 0, 0, 0, 'Sky Lane, Guild 3', 32930, 5, 0, 0, 0, 0, 347, 18), +(606, 0, 0, 0, 'Sky Lane, Sea Tower', 9050, 5, 0, 0, 0, 0, 95, 6), +(607, 0, 0, 0, 'Magic Academy, Guild', 22750, 5, 0, 0, 0, 0, 195, 14), +(608, 0, 0, 0, 'Magic Academy, Shop', 3190, 5, 0, 0, 0, 0, 29, 1), +(609, 0, 0, 0, 'Magic Academy, Flat 1', 2730, 5, 0, 0, 0, 0, 23, 3), +(610, 0, 0, 0, 'Magic Academy, Flat 2', 2960, 5, 0, 0, 0, 0, 26, 2), +(611, 0, 0, 0, 'Magic Academy, Flat 3', 2860, 5, 0, 0, 0, 0, 26, 1), +(612, 0, 0, 0, 'Magic Academy, Flat 4', 2960, 5, 0, 0, 0, 0, 26, 2), +(613, 0, 0, 0, 'Magic Academy, Flat 5', 2860, 5, 0, 0, 0, 0, 26, 1), +(614, 0, 0, 0, 'Stonehome Village 1', 3460, 5, 0, 0, 0, 0, 42, 2), +(615, 0, 0, 0, 'Stonehome Village 2', 1280, 5, 0, 0, 0, 0, 16, 1), +(616, 0, 0, 0, 'Stonehome Village 3', 1360, 5, 0, 0, 0, 0, 17, 1), +(617, 0, 0, 0, 'Stonehome Village 4', 1780, 5, 0, 0, 0, 0, 21, 2), +(618, 0, 0, 0, 'Stonehome Village 5', 2180, 5, 0, 0, 0, 0, 26, 2), +(619, 0, 0, 0, 'Stonehome Village 6', 2500, 5, 0, 0, 0, 0, 30, 2), +(620, 0, 0, 0, 'Stonehome Village 7', 2180, 5, 0, 0, 0, 0, 26, 2), +(621, 0, 0, 0, 'Stonehome Village 8', 1360, 5, 0, 0, 0, 0, 17, 1), +(622, 0, 0, 0, 'Stonehome Village 9', 1360, 5, 0, 0, 0, 0, 17, 1), +(623, 0, 0, 0, 'Stonehome Flats, Flat 01', 800, 5, 0, 0, 0, 0, 10, 1), +(624, 0, 0, 0, 'Stonehome Flats, Flat 02', 1380, 5, 0, 0, 0, 0, 16, 2), +(625, 0, 0, 0, 'Stonehome Flats, Flat 03', 800, 5, 0, 0, 0, 0, 10, 1), +(626, 0, 0, 0, 'Stonehome Flats, Flat 04', 800, 5, 0, 0, 0, 0, 10, 1), +(627, 0, 0, 0, 'Stonehome Flats, Flat 05', 800, 5, 0, 0, 0, 0, 10, 1), +(628, 0, 0, 0, 'Stonehome Flats, Flat 06', 800, 5, 0, 0, 0, 0, 10, 1), +(629, 0, 0, 0, 'Stonehome Flats, Flat 11', 1380, 5, 0, 0, 0, 0, 16, 2), +(630, 0, 0, 0, 'Stonehome Flats, Flat 12', 1380, 5, 0, 0, 0, 0, 16, 2), +(631, 0, 0, 0, 'Stonehome Flats, Flat 13', 800, 5, 0, 0, 0, 0, 10, 1), +(632, 0, 0, 0, 'Stonehome Flats, Flat 14', 800, 5, 0, 0, 0, 0, 10, 1), +(633, 0, 0, 0, 'Stonehome Flats, Flat 15', 800, 5, 0, 0, 0, 0, 10, 1), +(634, 0, 0, 0, 'Stonehome Flats, Flat 16', 800, 5, 0, 0, 0, 0, 10, 1), +(635, 0, 0, 0, 'Stonehome Clanhall', 16260, 5, 0, 0, 0, 0, 192, 10), +(636, 0, 0, 0, 'Cormaya Flats, Flat 01', 900, 5, 0, 0, 0, 0, 10, 1), +(637, 0, 0, 0, 'Cormaya Flats, Flat 02', 900, 5, 0, 0, 0, 0, 10, 1), +(638, 0, 0, 0, 'Cormaya Flats, Flat 03', 1540, 5, 0, 0, 0, 0, 16, 2), +(639, 0, 0, 0, 'Cormaya Flats, Flat 04', 1540, 5, 0, 0, 0, 0, 16, 2), +(640, 0, 0, 0, 'Cormaya Flats, Flat 05', 900, 5, 0, 0, 0, 0, 10, 1), +(641, 0, 0, 0, 'Cormaya Flats, Flat 06', 900, 5, 0, 0, 0, 0, 10, 1), +(642, 0, 0, 0, 'Cormaya Flats, Flat 11', 900, 5, 0, 0, 0, 0, 10, 1), +(643, 0, 0, 0, 'Cormaya Flats, Flat 12', 900, 5, 0, 0, 0, 0, 10, 1), +(644, 0, 0, 0, 'Cormaya Flats, Flat 13', 1540, 5, 0, 0, 0, 0, 16, 2), +(645, 0, 0, 0, 'Cormaya Flats, Flat 14', 1540, 5, 0, 0, 0, 0, 16, 2), +(646, 0, 0, 0, 'Cormaya Flats, Flat 15', 900, 5, 0, 0, 0, 0, 10, 1), +(647, 0, 0, 0, 'Cormaya Flats, Flat 16', 900, 5, 0, 0, 0, 0, 10, 1), +(648, 0, 0, 0, 'Cormaya 1', 2440, 5, 0, 0, 0, 0, 26, 2), +(649, 0, 0, 0, 'Cormaya 2', 7220, 5, 0, 0, 0, 0, 78, 3), +(650, 0, 0, 0, 'Cormaya 3', 3970, 5, 0, 0, 0, 0, 43, 2), +(651, 0, 0, 0, 'Cormaya 4', 3340, 5, 0, 0, 0, 0, 36, 2), +(652, 0, 0, 0, 'Cormaya 5', 11000, 5, 0, 0, 0, 0, 120, 3), +(653, 0, 0, 0, 'Cormaya 6', 4690, 5, 0, 0, 0, 0, 51, 2), +(654, 0, 0, 0, 'Cormaya 7', 4690, 5, 0, 0, 0, 0, 51, 2), +(655, 0, 0, 0, 'Cormaya 8', 5320, 5, 0, 0, 0, 0, 58, 2), +(656, 0, 0, 0, 'Cormaya 9a', 2350, 5, 0, 0, 0, 0, 25, 2), +(657, 0, 0, 0, 'Cormaya 9b', 5140, 5, 0, 0, 0, 0, 56, 2), +(658, 0, 0, 0, 'Cormaya 9c', 2350, 5, 0, 0, 0, 0, 25, 2), +(659, 0, 0, 0, 'Cormaya 9d', 5140, 5, 0, 0, 0, 0, 56, 2), +(660, 0, 0, 0, 'Cormaya 10', 7400, 5, 0, 0, 0, 0, 80, 3), +(661, 0, 0, 0, 'Cormaya 11', 3970, 5, 0, 0, 0, 0, 43, 2), +(662, 0, 0, 0, 'Castle of the White Dragon', 48150, 5, 0, 0, 0, 0, 515, 19), +(663, 0, 0, 0, 'Chameken I', 1700, 8, 0, 0, 0, 0, 17, 1), +(664, 0, 0, 0, 'Chameken II', 1700, 8, 0, 0, 0, 0, 17, 1), +(665, 0, 0, 0, 'Thanah I a', 1700, 8, 0, 0, 0, 0, 17, 1), +(666, 0, 0, 0, 'Thanah I b', 5800, 8, 0, 0, 0, 0, 56, 3), +(667, 0, 0, 0, 'Thanah I c', 6300, 8, 0, 0, 0, 0, 61, 3), +(668, 0, 0, 0, 'Thanah I d', 5500, 8, 0, 0, 0, 0, 52, 4), +(669, 0, 0, 0, 'Thanah II a', 1700, 8, 0, 0, 0, 0, 17, 1), +(670, 0, 0, 0, 'Thanah II b', 900, 8, 0, 0, 0, 0, 9, 1), +(671, 0, 0, 0, 'Thanah II c', 900, 8, 0, 0, 0, 0, 9, 1), +(672, 0, 0, 0, 'Thanah II d', 700, 8, 0, 0, 0, 0, 7, 1), +(673, 0, 0, 0, 'Thanah II e', 700, 8, 0, 0, 0, 0, 7, 1), +(674, 0, 0, 0, 'Thanah II f', 5500, 8, 0, 0, 0, 0, 53, 3), +(675, 0, 0, 0, 'Thanah II g', 3200, 8, 0, 0, 0, 0, 31, 2), +(676, 0, 0, 0, 'Thanah II h', 2700, 8, 0, 0, 0, 0, 26, 2), +(677, 0, 0, 0, 'Thrarhor I a (Shop)', 2100, 8, 0, 0, 0, 0, 21, 1), +(678, 0, 0, 0, 'Thrarhor I b (Shop)', 2100, 8, 0, 0, 0, 0, 21, 1), +(679, 0, 0, 0, 'Thrarhor I c (Shop)', 2100, 8, 0, 0, 0, 0, 21, 1), +(680, 0, 0, 0, 'Thrarhor I d (Shop)', 2100, 8, 0, 0, 0, 0, 21, 1), +(681, 0, 0, 0, 'Botham I a', 1900, 8, 0, 0, 0, 0, 19, 1), +(682, 0, 0, 0, 'Botham I b', 5800, 8, 0, 0, 0, 0, 56, 3), +(683, 0, 0, 0, 'Botham I c', 3300, 8, 0, 0, 0, 0, 32, 2), +(684, 0, 0, 0, 'Botham I d', 5900, 8, 0, 0, 0, 0, 57, 3), +(685, 0, 0, 0, 'Botham I e', 3200, 8, 0, 0, 0, 0, 31, 2), +(686, 0, 0, 0, 'Botham II a', 1700, 8, 0, 0, 0, 0, 17, 1), +(687, 0, 0, 0, 'Botham II b', 3100, 8, 0, 0, 0, 0, 30, 2), +(688, 0, 0, 0, 'Botham II c', 2400, 8, 0, 0, 0, 0, 23, 2), +(689, 0, 0, 0, 'Botham II d', 3800, 8, 0, 0, 0, 0, 37, 2), +(690, 0, 0, 0, 'Botham II e', 3200, 8, 0, 0, 0, 0, 31, 2), +(691, 0, 0, 0, 'Botham II f', 3200, 8, 0, 0, 0, 0, 31, 2), +(692, 0, 0, 0, 'Botham II g', 2700, 8, 0, 0, 0, 0, 26, 2), +(693, 0, 0, 0, 'Botham III a', 2700, 8, 0, 0, 0, 0, 26, 2), +(694, 0, 0, 0, 'Botham III b', 1800, 8, 0, 0, 0, 0, 17, 2), +(695, 0, 0, 0, 'Botham III c', 1700, 8, 0, 0, 0, 0, 17, 1), +(696, 0, 0, 0, 'Botham III d', 1700, 8, 0, 0, 0, 0, 17, 1), +(697, 0, 0, 0, 'Botham III e', 1700, 8, 0, 0, 0, 0, 17, 1), +(698, 0, 0, 0, 'Botham III f', 4500, 8, 0, 0, 0, 0, 43, 3), +(699, 0, 0, 0, 'Botham III g', 3200, 8, 0, 0, 0, 0, 31, 2), +(700, 0, 0, 0, 'Botham III h', 7300, 8, 0, 0, 0, 0, 71, 3), +(701, 0, 0, 0, 'Botham IV a', 2700, 8, 0, 0, 0, 0, 26, 2), +(702, 0, 0, 0, 'Botham IV b', 1700, 8, 0, 0, 0, 0, 17, 1), +(703, 0, 0, 0, 'Botham IV c', 1700, 8, 0, 0, 0, 0, 17, 1), +(704, 0, 0, 0, 'Botham IV d', 1700, 8, 0, 0, 0, 0, 17, 1), +(705, 0, 0, 0, 'Botham IV e', 1700, 8, 0, 0, 0, 0, 17, 1), +(706, 0, 0, 0, 'Botham IV f', 3300, 8, 0, 0, 0, 0, 32, 2), +(707, 0, 0, 0, 'Botham IV g', 3200, 8, 0, 0, 0, 0, 31, 2), +(708, 0, 0, 0, 'Botham IV h', 3700, 8, 0, 0, 0, 0, 37, 1), +(709, 0, 0, 0, 'Botham IV i', 3400, 8, 0, 0, 0, 0, 32, 3), +(710, 0, 0, 0, 'Ramen Tah', 13800, 8, 0, 0, 0, 0, 123, 16), +(711, 0, 0, 0, 'Charsirakh I a', 560, 8, 0, 0, 0, 0, 7, 1), +(712, 0, 0, 0, 'Charsirakh I b', 3060, 8, 0, 0, 0, 0, 37, 2), +(713, 0, 0, 0, 'Charsirakh II', 2180, 8, 0, 0, 0, 0, 26, 2), +(714, 0, 0, 0, 'Charsirakh III', 1360, 8, 0, 0, 0, 0, 17, 1), +(715, 0, 0, 0, 'Othehothep I a', 560, 8, 0, 0, 0, 0, 7, 1), +(716, 0, 0, 0, 'Othehothep I b', 2660, 8, 0, 0, 0, 0, 32, 2), +(717, 0, 0, 0, 'Othehothep I c', 3240, 8, 0, 0, 0, 0, 38, 3), +(718, 0, 0, 0, 'Othehothep I d', 3740, 8, 0, 0, 0, 0, 43, 4), +(719, 0, 0, 0, 'Othehothep II a', 800, 8, 0, 0, 0, 0, 10, 1), +(720, 0, 0, 0, 'Othehothep II b', 3640, 8, 0, 0, 0, 0, 43, 3), +(721, 0, 0, 0, 'Othehothep II c', 1680, 8, 0, 0, 0, 0, 21, 1), +(722, 0, 0, 0, 'Othehothep II d', 1680, 8, 0, 0, 0, 0, 21, 1), +(723, 0, 0, 0, 'Othehothep II e', 2580, 8, 0, 0, 0, 0, 31, 2), +(724, 0, 0, 0, 'Othehothep II f', 2580, 8, 0, 0, 0, 0, 31, 2), +(725, 0, 0, 0, 'Othehothep III a', 560, 8, 0, 0, 0, 0, 7, 1), +(726, 0, 0, 0, 'Othehothep III b', 2580, 8, 0, 0, 0, 0, 31, 2), +(727, 0, 0, 0, 'Othehothep III c', 1780, 8, 0, 0, 0, 0, 21, 2), +(728, 0, 0, 0, 'Othehothep III d', 2080, 8, 0, 0, 0, 0, 26, 1), +(729, 0, 0, 0, 'Othehothep III e', 1680, 8, 0, 0, 0, 0, 21, 1), +(730, 0, 0, 0, 'Othehothep III f', 1360, 8, 0, 0, 0, 0, 17, 1), +(731, 0, 0, 0, 'Harrah I', 10580, 8, 0, 0, 0, 0, 121, 10), +(732, 0, 0, 0, 'Murkhol I a', 880, 8, 0, 0, 0, 0, 11, 1), +(733, 0, 0, 0, 'Murkhol I b', 880, 8, 0, 0, 0, 0, 11, 1), +(734, 0, 0, 0, 'Murkhol I c', 880, 8, 0, 0, 0, 0, 11, 1), +(735, 0, 0, 0, 'Murkhol I d', 880, 8, 0, 0, 0, 0, 11, 1), +(736, 0, 0, 0, 'Oskahl I a', 3060, 8, 0, 0, 0, 0, 37, 2), +(737, 0, 0, 0, 'Oskahl I b', 1680, 8, 0, 0, 0, 0, 21, 1), +(738, 0, 0, 0, 'Oskahl I c', 1360, 8, 0, 0, 0, 0, 17, 1), +(739, 0, 0, 0, 'Oskahl I d', 2180, 8, 0, 0, 0, 0, 26, 2), +(740, 0, 0, 0, 'Oskahl I e', 1680, 8, 0, 0, 0, 0, 21, 1), +(741, 0, 0, 0, 'Oskahl I f', 1680, 8, 0, 0, 0, 0, 21, 1), +(742, 0, 0, 0, 'Oskahl I g', 2180, 8, 0, 0, 0, 0, 26, 2), +(743, 0, 0, 0, 'Oskahl I h', 3320, 8, 0, 0, 0, 0, 39, 3), +(744, 0, 0, 0, 'Oskahl I i', 1680, 8, 0, 0, 0, 0, 21, 1), +(745, 0, 0, 0, 'Oskahl I j', 1360, 8, 0, 0, 0, 0, 17, 1), +(746, 0, 0, 0, 'Mothrem I', 2180, 8, 0, 0, 0, 0, 26, 2), +(747, 0, 0, 0, 'Arakmehn I', 2440, 8, 0, 0, 0, 0, 28, 3), +(748, 0, 0, 0, 'Arakmehn II', 2080, 8, 0, 0, 0, 0, 26, 1), +(749, 0, 0, 0, 'Arakmehn III', 2180, 8, 0, 0, 0, 0, 26, 2), +(750, 0, 0, 0, 'Arakmehn IV', 2340, 8, 0, 0, 0, 0, 28, 2), +(751, 0, 0, 0, 'Unklath I a', 2180, 8, 0, 0, 0, 0, 26, 2), +(752, 0, 0, 0, 'Unklath I b', 2820, 8, 0, 0, 0, 0, 34, 2), +(753, 0, 0, 0, 'Unklath I c', 2820, 8, 0, 0, 0, 0, 34, 2), +(754, 0, 0, 0, 'Unklath I d', 3160, 8, 0, 0, 0, 0, 37, 3), +(755, 0, 0, 0, 'Unklath I e', 3060, 8, 0, 0, 0, 0, 37, 2), +(756, 0, 0, 0, 'Unklath I f', 3060, 8, 0, 0, 0, 0, 37, 2), +(757, 0, 0, 0, 'Unklath I g', 2960, 8, 0, 0, 0, 0, 37, 1), +(758, 0, 0, 0, 'Unklath II a', 2080, 8, 0, 0, 0, 0, 26, 1), +(759, 0, 0, 0, 'Unklath II b', 1360, 8, 0, 0, 0, 0, 17, 1), +(760, 0, 0, 0, 'Unklath II c', 1360, 8, 0, 0, 0, 0, 17, 1), +(761, 0, 0, 0, 'Unklath II d', 3060, 8, 0, 0, 0, 0, 37, 2), +(762, 0, 0, 0, 'Rathal I a', 2180, 8, 0, 0, 0, 0, 26, 2), +(763, 0, 0, 0, 'Rathal I b', 1360, 8, 0, 0, 0, 0, 17, 1), +(764, 0, 0, 0, 'Rathal I c', 1360, 8, 0, 0, 0, 0, 17, 1), +(765, 0, 0, 0, 'Rathal I d', 1460, 8, 0, 0, 0, 0, 17, 2), +(766, 0, 0, 0, 'Rathal I e', 1460, 8, 0, 0, 0, 0, 17, 2), +(767, 0, 0, 0, 'Rathal II a', 2080, 8, 0, 0, 0, 0, 26, 1), +(768, 0, 0, 0, 'Rathal II b', 1360, 8, 0, 0, 0, 0, 17, 1), +(769, 0, 0, 0, 'Rathal II c', 1360, 8, 0, 0, 0, 0, 17, 1), +(770, 0, 0, 0, 'Rathal II d', 2820, 8, 0, 0, 0, 0, 34, 2), +(771, 0, 0, 0, 'Uthemath I a', 800, 8, 0, 0, 0, 0, 10, 1), +(772, 0, 0, 0, 'Uthemath I b', 1600, 8, 0, 0, 0, 0, 20, 1), +(773, 0, 0, 0, 'Uthemath I c', 1700, 8, 0, 0, 0, 0, 20, 2), +(774, 0, 0, 0, 'Uthemath I d', 1680, 8, 0, 0, 0, 0, 21, 1), +(775, 0, 0, 0, 'Uthemath I e', 1780, 8, 0, 0, 0, 0, 21, 2), +(776, 0, 0, 0, 'Uthemath I f', 4680, 8, 0, 0, 0, 0, 56, 3), +(777, 0, 0, 0, 'Uthemath II', 8220, 8, 0, 0, 0, 0, 94, 8), +(778, 0, 0, 0, 'Esuph I', 1360, 8, 0, 0, 0, 0, 17, 1), +(779, 0, 0, 0, 'Esuph II a', 560, 8, 0, 0, 0, 0, 7, 1), +(780, 0, 0, 0, 'Esuph II b', 2660, 8, 0, 0, 0, 0, 32, 2), +(781, 0, 0, 0, 'Esuph III a', 560, 8, 0, 0, 0, 0, 7, 1), +(782, 0, 0, 0, 'Esuph III b', 2580, 8, 0, 0, 0, 0, 31, 2), +(783, 0, 0, 0, 'Esuph IV a', 800, 8, 0, 0, 0, 0, 10, 1), +(784, 0, 0, 0, 'Esuph IV b', 800, 8, 0, 0, 0, 0, 10, 1), +(785, 0, 0, 0, 'Esuph IV c', 800, 8, 0, 0, 0, 0, 10, 1), +(786, 0, 0, 0, 'Esuph IV d', 1600, 8, 0, 0, 0, 0, 20, 1), +(787, 0, 0, 0, 'Horakhal', 17540, 8, 0, 0, 0, 0, 203, 14), +(788, 0, 0, 0, 'Darashia 1, Flat 01', 2100, 6, 0, 0, 0, 0, 25, 2), +(789, 0, 0, 0, 'Darashia 1, Flat 02', 2000, 6, 0, 0, 0, 0, 25, 1), +(790, 0, 0, 0, 'Darashia 1, Flat 03', 5020, 6, 0, 0, 0, 0, 59, 4), +(791, 0, 0, 0, 'Darashia 1, Flat 04', 2000, 6, 0, 0, 0, 0, 25, 1), +(792, 0, 0, 0, 'Darashia 1, Flat 05', 2100, 6, 0, 0, 0, 0, 25, 2), +(793, 0, 0, 0, 'Darashia 1, Flat 11', 2100, 6, 0, 0, 0, 0, 25, 2), +(794, 0, 0, 0, 'Darashia 1, Flat 12', 3460, 6, 0, 0, 0, 0, 42, 2), +(795, 0, 0, 0, 'Darashia 1, Flat 13', 3460, 6, 0, 0, 0, 0, 42, 2), +(796, 0, 0, 0, 'Darashia 1, Flat 14', 5120, 6, 0, 0, 0, 0, 59, 5); +INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `rent`, `town_id`, `bid`, `bid_end`, `last_bid`, `highest_bidder`, `size`, `beds`) VALUES +(797, 0, 0, 0, 'Darashia 2, Flat 01', 2000, 6, 0, 0, 0, 0, 25, 1), +(798, 0, 0, 0, 'Darashia 2, Flat 02', 2000, 6, 0, 0, 0, 0, 25, 1), +(799, 0, 0, 0, 'Darashia 2, Flat 03', 2320, 6, 0, 0, 0, 0, 29, 1), +(800, 0, 0, 0, 'Darashia 2, Flat 04', 1040, 6, 0, 0, 0, 0, 13, 1), +(801, 0, 0, 0, 'Darashia 2, Flat 05', 2420, 6, 0, 0, 0, 0, 29, 2), +(802, 0, 0, 0, 'Darashia 2, Flat 06', 1040, 6, 0, 0, 0, 0, 13, 1), +(803, 0, 0, 0, 'Darashia 2, Flat 07', 2000, 6, 0, 0, 0, 0, 25, 1), +(804, 0, 0, 0, 'Darashia 2, Flat 11', 2000, 6, 0, 0, 0, 0, 25, 1), +(805, 0, 0, 0, 'Darashia 2, Flat 12', 1040, 6, 0, 0, 0, 0, 13, 1), +(806, 0, 0, 0, 'Darashia 2, Flat 13', 2320, 6, 0, 0, 0, 0, 29, 1), +(807, 0, 0, 0, 'Darashia 2, Flat 14', 1040, 6, 0, 0, 0, 0, 13, 1), +(808, 0, 0, 0, 'Darashia 2, Flat 15', 2420, 6, 0, 0, 0, 0, 29, 2), +(809, 0, 0, 0, 'Darashia 2, Flat 16', 1360, 6, 0, 0, 0, 0, 17, 1), +(810, 0, 0, 0, 'Darashia 2, Flat 17', 2000, 6, 0, 0, 0, 0, 25, 1), +(811, 0, 0, 0, 'Darashia 2, Flat 18', 1360, 6, 0, 0, 0, 0, 17, 1), +(812, 0, 0, 0, 'Darashia 3, Flat 01', 2100, 6, 0, 0, 0, 0, 25, 2), +(813, 0, 0, 0, 'Darashia 3, Flat 02', 3140, 6, 0, 0, 0, 0, 38, 2), +(814, 0, 0, 0, 'Darashia 3, Flat 03', 2100, 6, 0, 0, 0, 0, 25, 2), +(815, 0, 0, 0, 'Darashia 3, Flat 04', 3140, 6, 0, 0, 0, 0, 38, 2), +(816, 0, 0, 0, 'Darashia 3, Flat 05', 2000, 6, 0, 0, 0, 0, 25, 1), +(817, 0, 0, 0, 'Darashia 3, Flat 11', 2000, 6, 0, 0, 0, 0, 25, 1), +(818, 0, 0, 0, 'Darashia 3, Flat 12', 4800, 6, 0, 0, 0, 0, 55, 5), +(819, 0, 0, 0, 'Darashia 3, Flat 13', 2100, 6, 0, 0, 0, 0, 25, 2), +(820, 0, 0, 0, 'Darashia 3, Flat 14', 4600, 6, 0, 0, 0, 0, 55, 3), +(821, 0, 0, 0, 'Darashia 4, Flat 01', 2000, 6, 0, 0, 0, 0, 25, 1), +(822, 0, 0, 0, 'Darashia 4, Flat 02', 3460, 6, 0, 0, 0, 0, 42, 2), +(823, 0, 0, 0, 'Darashia 4, Flat 03', 2000, 6, 0, 0, 0, 0, 25, 1), +(824, 0, 0, 0, 'Darashia 4, Flat 04', 3460, 6, 0, 0, 0, 0, 42, 2), +(825, 0, 0, 0, 'Darashia 4, Flat 05', 2100, 6, 0, 0, 0, 0, 25, 2), +(826, 0, 0, 0, 'Darashia 4, Flat 11', 2000, 6, 0, 0, 0, 0, 25, 1), +(827, 0, 0, 0, 'Darashia 4, Flat 12', 4920, 6, 0, 0, 0, 0, 59, 3), +(828, 0, 0, 0, 'Darashia 4, Flat 13', 3460, 6, 0, 0, 0, 0, 42, 2), +(829, 0, 0, 0, 'Darashia 4, Flat 14', 3460, 6, 0, 0, 0, 0, 42, 2), +(830, 0, 0, 0, 'Darashia 5, Flat 01', 2000, 6, 0, 0, 0, 0, 25, 1), +(831, 0, 0, 0, 'Darashia 5, Flat 02', 3140, 6, 0, 0, 0, 0, 38, 2), +(832, 0, 0, 0, 'Darashia 5, Flat 03', 2000, 6, 0, 0, 0, 0, 25, 1), +(833, 0, 0, 0, 'Darashia 5, Flat 04', 3140, 6, 0, 0, 0, 0, 38, 2), +(834, 0, 0, 0, 'Darashia 5, Flat 05', 2000, 6, 0, 0, 0, 0, 25, 1), +(835, 0, 0, 0, 'Darashia 5, Flat 11', 3460, 6, 0, 0, 0, 0, 42, 2), +(836, 0, 0, 0, 'Darashia 5, Flat 12', 3140, 6, 0, 0, 0, 0, 38, 2), +(837, 0, 0, 0, 'Darashia 5, Flat 13', 3460, 6, 0, 0, 0, 0, 42, 2), +(838, 0, 0, 0, 'Darashia 5, Flat 14', 3140, 6, 0, 0, 0, 0, 38, 2), +(839, 0, 0, 0, 'Darashia 6a', 6130, 6, 0, 0, 0, 0, 67, 2), +(840, 0, 0, 0, 'Darashia 6b', 6760, 6, 0, 0, 0, 0, 74, 2), +(841, 0, 0, 0, 'Darashia 7, Flat 01', 2250, 6, 0, 0, 0, 0, 25, 1), +(842, 0, 0, 0, 'Darashia 7, Flat 02', 2250, 6, 0, 0, 0, 0, 25, 1), +(843, 0, 0, 0, 'Darashia 7, Flat 03', 5610, 6, 0, 0, 0, 0, 59, 4), +(844, 0, 0, 0, 'Darashia 7, Flat 04', 2250, 6, 0, 0, 0, 0, 25, 1), +(845, 0, 0, 0, 'Darashia 7, Flat 05', 2350, 6, 0, 0, 0, 0, 25, 2), +(846, 0, 0, 0, 'Darashia 7, Flat 11', 2250, 6, 0, 0, 0, 0, 25, 1), +(847, 0, 0, 0, 'Darashia 7, Flat 12', 5610, 6, 0, 0, 0, 0, 59, 4), +(848, 0, 0, 0, 'Darashia 7, Flat 13', 2250, 6, 0, 0, 0, 0, 25, 1), +(849, 0, 0, 0, 'Darashia 7, Flat 14', 5610, 6, 0, 0, 0, 0, 59, 4), +(850, 0, 0, 0, 'Darashia 8, Flat 01', 4870, 6, 0, 0, 0, 0, 53, 2), +(851, 0, 0, 0, 'Darashia 8, Flat 02', 6670, 6, 0, 0, 0, 0, 73, 2), +(852, 0, 0, 0, 'Darashia 8, Flat 03', 9200, 6, 0, 0, 0, 0, 100, 3), +(853, 0, 0, 0, 'Darashia 8, Flat 04', 5590, 6, 0, 0, 0, 0, 61, 2), +(854, 0, 0, 0, 'Darashia 8, Flat 05', 5230, 6, 0, 0, 0, 0, 57, 2), +(855, 0, 0, 0, 'Darashia, Villa', 10470, 6, 0, 0, 0, 0, 113, 4), +(856, 0, 0, 0, 'Darashia, Western Guildhall', 19570, 6, 0, 0, 0, 0, 203, 14), +(857, 0, 0, 0, 'Darashia, Eastern Guildhall', 23820, 6, 0, 0, 0, 0, 248, 16), +(858, 0, 0, 0, 'Darashia 8, Flat 11', 3880, 6, 0, 0, 0, 0, 42, 2), +(859, 0, 0, 0, 'Darashia 8, Flat 12', 3520, 6, 0, 0, 0, 0, 38, 2), +(860, 0, 0, 0, 'Darashia 8, Flat 13', 3880, 6, 0, 0, 0, 0, 42, 2), +(861, 0, 0, 0, 'Darashia 8, Flat 14', 3520, 6, 0, 0, 0, 0, 38, 2); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `house_lists` +-- + +CREATE TABLE `house_lists` ( + `house_id` int(11) NOT NULL, + `listid` int(11) NOT NULL, + `list` text NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `ip_bans` +-- + +CREATE TABLE `ip_bans` ( + `ip` int(10) UNSIGNED NOT NULL, + `reason` varchar(255) NOT NULL, + `banned_at` bigint(20) NOT NULL, + `expires_at` bigint(20) NOT NULL, + `banned_by` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `players` +-- + +CREATE TABLE `players` ( + `id` int(11) NOT NULL, + `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', + `maglevel` int(11) NOT NULL DEFAULT '0', + `mana` int(11) NOT NULL DEFAULT '0', + `manamax` int(11) NOT NULL DEFAULT '0', + `manaspent` int(11) UNSIGNED NOT NULL DEFAULT '0', + `soul` int(10) UNSIGNED NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `posx` int(11) NOT NULL DEFAULT '0', + `posy` int(11) NOT NULL DEFAULT '0', + `posz` int(11) NOT NULL DEFAULT '0', + `conditions` blob NOT NULL, + `cap` int(11) NOT NULL DEFAULT '0', + `sex` int(11) NOT NULL DEFAULT '0', + `lastlogin` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `lastip` int(10) UNSIGNED NOT NULL DEFAULT '0', + `save` tinyint(1) NOT NULL DEFAULT '1', + `skull` tinyint(1) NOT NULL DEFAULT '0', + `skulltime` int(11) NOT NULL DEFAULT '0', + `lastlogout` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `blessings` tinyint(2) NOT NULL DEFAULT '0', + `onlinetime` int(11) NOT NULL DEFAULT '0', + `deletion` bigint(15) NOT NULL DEFAULT '0', + `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `offlinetraining_time` smallint(5) unsigned NOT NULL DEFAULT '43200', + `offlinetraining_skill` int(11) NOT NULL DEFAULT '-1', + `stamina` smallint(5) NOT NULL DEFAULT '3360', + `skill_fist` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_fist_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_club` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_club_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_sword` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_sword_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_axe` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_axe_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_dist` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_dist_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_shielding` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_shielding_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `skill_fishing` int(10) UNSIGNED NOT NULL DEFAULT '10', + `skill_fishing_tries` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `fake_player` tinyint(1) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Triggers `players` +-- +DELIMITER $$ +CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` FOR EACH ROW BEGIN + UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`; +END +$$ +DELIMITER ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `players_online` +-- + +CREATE TABLE `players_online` ( + `player_id` int(11) NOT NULL +) ENGINE=MEMORY DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_deaths` +-- + +CREATE TABLE `player_deaths` ( + `player_id` int(11) NOT NULL, + `time` bigint(20) UNSIGNED NOT NULL DEFAULT '0', + `level` int(11) NOT NULL DEFAULT '1', + `killed_by` varchar(255) NOT NULL, + `is_player` tinyint(1) NOT NULL DEFAULT '1', + `mostdamage_by` varchar(100) NOT NULL, + `mostdamage_is_player` tinyint(1) NOT NULL DEFAULT '0', + `unjustified` tinyint(1) NOT NULL DEFAULT '0', + `mostdamage_unjustified` tinyint(1) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_depotitems` +-- + +CREATE TABLE `player_depotitems` ( + `player_id` int(11) NOT NULL, + `sid` int(11) NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', + `pid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL, + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_items` +-- + +CREATE TABLE `player_items` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `pid` int(11) NOT NULL DEFAULT '0', + `sid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL DEFAULT '0', + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_murders` +-- + +CREATE TABLE `player_murders` ( + `id` bigint(20) NOT NULL, + `player_id` int(11) NOT NULL, + `date` bigint(20) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_namelocks` +-- + +CREATE TABLE `player_namelocks` ( + `player_id` int(11) NOT NULL, + `reason` varchar(255) NOT NULL, + `namelocked_at` bigint(20) NOT NULL, + `namelocked_by` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_spells` +-- + +CREATE TABLE `player_spells` ( + `player_id` int(11) NOT NULL, + `name` varchar(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_storage` +-- + +CREATE TABLE `player_storage` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `key` int(10) UNSIGNED NOT NULL DEFAULT '0', + `value` int(11) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `server_config` +-- + +CREATE TABLE `server_config` ( + `config` varchar(50) NOT NULL, + `value` varchar(256) NOT NULL DEFAULT '' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `server_config` +-- + +INSERT INTO `server_config` (`config`, `value`) VALUES +('motd_hash', '591d7fcbec26bc2982bdf5b0f13fd6bcbbd3aa0c'), +('motd_num', '1'), +('players_record', '2'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `global_storage` +-- + +CREATE TABLE IF NOT EXISTS `global_storage` ( + `key` int(10) unsigned NOT NULL DEFAULT '0', + `value` int(11) DEFAULT NULL, + PRIMARY KEY (`key`) +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + +-- +-- Dumping data for table `global_storage` +-- + +INSERT INTO `global_storage` (`key`, `value`) VALUES +(1, 0); -- key 1 = blooming griffinclaw; value 0 = not started in this month, value 1 = already started once + +-- -------------------------------------------------------- + +-- +-- Table structure for table `tile_store` +-- + +CREATE TABLE `tile_store` ( + `house_id` int(11) NOT NULL, + `data` longblob NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `accounts` +-- +ALTER TABLE `accounts` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `name` (`name`); + +-- +-- Indexes for table `account_bans` +-- +ALTER TABLE `account_bans` + ADD PRIMARY KEY (`account_id`), + ADD KEY `banned_by` (`banned_by`); + +-- +-- Indexes for table `account_ban_history` +-- +ALTER TABLE `account_ban_history` + ADD PRIMARY KEY (`id`), + ADD KEY `account_id` (`account_id`), + ADD KEY `banned_by` (`banned_by`); + +-- +-- Indexes for table `account_viplist` +-- +ALTER TABLE `account_viplist` + ADD UNIQUE KEY `account_player_index` (`account_id`,`player_id`), + ADD KEY `player_id` (`player_id`); + +-- +-- Indexes for table `guilds` +-- +ALTER TABLE `guilds` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `name` (`name`), + ADD UNIQUE KEY `ownerid` (`ownerid`); + +-- +-- Indexes for table `guildwar_kills` +-- +ALTER TABLE `guildwar_kills` + ADD PRIMARY KEY (`id`), + ADD KEY `warid` (`warid`); + +-- +-- Indexes for table `guild_invites` +-- +ALTER TABLE `guild_invites` + ADD PRIMARY KEY (`player_id`,`guild_id`), + ADD KEY `guild_id` (`guild_id`); + +-- +-- Indexes for table `guild_membership` +-- +ALTER TABLE `guild_membership` + ADD PRIMARY KEY (`player_id`), + ADD KEY `guild_id` (`guild_id`), + ADD KEY `rank_id` (`rank_id`); + +-- +-- Indexes for table `guild_ranks` +-- +ALTER TABLE `guild_ranks` + ADD PRIMARY KEY (`id`), + ADD KEY `guild_id` (`guild_id`); + +-- +-- Indexes for table `guild_wars` +-- +ALTER TABLE `guild_wars` + ADD PRIMARY KEY (`id`), + ADD KEY `guild1` (`guild1`), + ADD KEY `guild2` (`guild2`); + +-- +-- Indexes for table `houses` +-- +ALTER TABLE `houses` + ADD PRIMARY KEY (`id`), + ADD KEY `owner` (`owner`), + ADD KEY `town_id` (`town_id`); + +-- +-- Indexes for table `house_lists` +-- +ALTER TABLE `house_lists` + ADD KEY `house_id` (`house_id`); + +-- +-- Indexes for table `ip_bans` +-- +ALTER TABLE `ip_bans` + ADD PRIMARY KEY (`ip`), + ADD KEY `banned_by` (`banned_by`); + +-- +-- Indexes for table `players` +-- +ALTER TABLE `players` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `name` (`name`), + ADD KEY `account_id` (`account_id`), + ADD KEY `vocation` (`vocation`); + +-- +-- Indexes for table `players_online` +-- +ALTER TABLE `players_online` + ADD PRIMARY KEY (`player_id`); + +-- +-- Indexes for table `player_deaths` +-- +ALTER TABLE `player_deaths` + ADD KEY `player_id` (`player_id`), + ADD KEY `killed_by` (`killed_by`), + ADD KEY `mostdamage_by` (`mostdamage_by`); + +-- +-- Indexes for table `player_depotitems` +-- +ALTER TABLE `player_depotitems` + ADD UNIQUE KEY `player_id_2` (`player_id`,`sid`); + +-- +-- Indexes for table `player_items` +-- +ALTER TABLE `player_items` + ADD KEY `player_id` (`player_id`), + ADD KEY `sid` (`sid`); + +-- +-- Indexes for table `player_murders` +-- +ALTER TABLE `player_murders` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `player_namelocks` +-- +ALTER TABLE `player_namelocks` + ADD PRIMARY KEY (`player_id`), + ADD KEY `namelocked_by` (`namelocked_by`); + +-- +-- Indexes for table `player_spells` +-- +ALTER TABLE `player_spells` + ADD KEY `player_id` (`player_id`); + +-- +-- Indexes for table `player_storage` +-- +ALTER TABLE `player_storage` + ADD PRIMARY KEY (`player_id`,`key`); + +-- +-- Indexes for table `server_config` +-- +ALTER TABLE `server_config` + ADD PRIMARY KEY (`config`); + +-- +-- Indexes for table `tile_store` +-- +ALTER TABLE `tile_store` + ADD KEY `house_id` (`house_id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `accounts` +-- +ALTER TABLE `accounts` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `account_ban_history` +-- +ALTER TABLE `account_ban_history` + MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `guilds` +-- +ALTER TABLE `guilds` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `guildwar_kills` +-- +ALTER TABLE `guildwar_kills` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `guild_ranks` +-- +ALTER TABLE `guild_ranks` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `guild_wars` +-- +ALTER TABLE `guild_wars` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT for table `houses` +-- +ALTER TABLE `houses` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=862; + +-- +-- AUTO_INCREMENT for table `players` +-- +ALTER TABLE `players` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- AUTO_INCREMENT for table `player_murders` +-- +ALTER TABLE `player_murders` + MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=30; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `account_bans` +-- +ALTER TABLE `account_bans` + ADD CONSTRAINT `account_bans_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `account_bans_ibfk_2` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `account_ban_history` +-- +ALTER TABLE `account_ban_history` + ADD CONSTRAINT `account_ban_history_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `account_ban_history_ibfk_2` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `account_viplist` +-- +ALTER TABLE `account_viplist` + ADD CONSTRAINT `account_viplist_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `account_viplist_ibfk_2` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guilds` +-- +ALTER TABLE `guilds` + ADD CONSTRAINT `guilds_ibfk_1` FOREIGN KEY (`ownerid`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guild_wars` +-- +ALTER TABLE `guild_wars` + ADD CONSTRAINT `guild1_ibfk_1` FOREIGN KEY (`guild1`) REFERENCES `guilds` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `guild2_ibfk_1` FOREIGN KEY (`guild2`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; +COMMIT; + +-- +-- Constraints for table `guildwar_kills` +-- +ALTER TABLE `guildwar_kills` + ADD CONSTRAINT `guildwar_kills_ibfk_1` FOREIGN KEY (`warid`) REFERENCES `guild_wars` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guild_invites` +-- +ALTER TABLE `guild_invites` + ADD CONSTRAINT `guild_invites_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `guild_invites_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guild_membership` +-- +ALTER TABLE `guild_membership` + ADD CONSTRAINT `guild_membership_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_3` FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `guild_ranks` +-- +ALTER TABLE `guild_ranks` + ADD CONSTRAINT `guild_ranks_ibfk_1` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `house_lists` +-- +ALTER TABLE `house_lists` + ADD CONSTRAINT `house_lists_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `ip_bans` +-- +ALTER TABLE `ip_bans` + ADD CONSTRAINT `ip_bans_ibfk_1` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `players` +-- +ALTER TABLE `players` + ADD CONSTRAINT `players_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_deaths` +-- +ALTER TABLE `player_deaths` + ADD CONSTRAINT `player_deaths_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_depotitems` +-- +ALTER TABLE `player_depotitems` + ADD CONSTRAINT `player_depotitems_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_items` +-- +ALTER TABLE `player_items` + ADD CONSTRAINT `player_items_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_namelocks` +-- +ALTER TABLE `player_namelocks` + ADD CONSTRAINT `player_namelocks_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `player_namelocks_ibfk_2` FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `player_spells` +-- +ALTER TABLE `player_spells` + ADD CONSTRAINT `player_spells_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_storage` +-- +ALTER TABLE `player_storage` + ADD CONSTRAINT `player_storage_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `tile_store` +-- +ALTER TABLE `tile_store` + ADD CONSTRAINT `tile_store_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/app/SabrehavenServer/sabrehaven_znote.sql b/app/SabrehavenServer/sabrehaven_znote.sql new file mode 100644 index 0000000..694fcd2 --- /dev/null +++ b/app/SabrehavenServer/sabrehaven_znote.sql @@ -0,0 +1,17 @@ +ALTER TABLE znote_accounts +ADD twitchuser varchar(256); + +ALTER TABLE znote_accounts +ADD affiliatecode varchar(256); + +ALTER TABLE znote_accounts +ADD affiliated varchar(256); + +CREATE TABLE IF NOT EXISTS `znote_affiliate_logs` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `affiliator_account_id` int(11) NOT NULL, + `affiliated_account_id` int(11) NOT NULL, + `points_given` int(10) NOT NULL, + `paid_money` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; \ No newline at end of file diff --git a/app/SabrehavenServer/src/CMakeLists.txt b/app/SabrehavenServer/src/CMakeLists.txt new file mode 100644 index 0000000..e8cdbf0 --- /dev/null +++ b/app/SabrehavenServer/src/CMakeLists.txt @@ -0,0 +1,73 @@ +set(tfs_SRC + ${CMAKE_CURRENT_LIST_DIR}/otpch.cpp + ${CMAKE_CURRENT_LIST_DIR}/actions.cpp + ${CMAKE_CURRENT_LIST_DIR}/ban.cpp + ${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp + ${CMAKE_CURRENT_LIST_DIR}/bed.cpp + ${CMAKE_CURRENT_LIST_DIR}/behaviourdatabase.cpp + ${CMAKE_CURRENT_LIST_DIR}/chat.cpp + ${CMAKE_CURRENT_LIST_DIR}/combat.cpp + ${CMAKE_CURRENT_LIST_DIR}/condition.cpp + ${CMAKE_CURRENT_LIST_DIR}/configmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/connection.cpp + ${CMAKE_CURRENT_LIST_DIR}/container.cpp + ${CMAKE_CURRENT_LIST_DIR}/creature.cpp + ${CMAKE_CURRENT_LIST_DIR}/creatureevent.cpp + ${CMAKE_CURRENT_LIST_DIR}/cylinder.cpp + ${CMAKE_CURRENT_LIST_DIR}/database.cpp + ${CMAKE_CURRENT_LIST_DIR}/databasemanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/databasetasks.cpp + ${CMAKE_CURRENT_LIST_DIR}/depotlocker.cpp + ${CMAKE_CURRENT_LIST_DIR}/events.cpp + ${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp + ${CMAKE_CURRENT_LIST_DIR}/game.cpp + ${CMAKE_CURRENT_LIST_DIR}/globalevent.cpp + ${CMAKE_CURRENT_LIST_DIR}/guild.cpp + ${CMAKE_CURRENT_LIST_DIR}/groups.cpp + ${CMAKE_CURRENT_LIST_DIR}/house.cpp + ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp + ${CMAKE_CURRENT_LIST_DIR}/ioguild.cpp + ${CMAKE_CURRENT_LIST_DIR}/iologindata.cpp + ${CMAKE_CURRENT_LIST_DIR}/iomap.cpp + ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.cpp + ${CMAKE_CURRENT_LIST_DIR}/item.cpp + ${CMAKE_CURRENT_LIST_DIR}/items.cpp + ${CMAKE_CURRENT_LIST_DIR}/luascript.cpp + ${CMAKE_CURRENT_LIST_DIR}/mailbox.cpp + ${CMAKE_CURRENT_LIST_DIR}/map.cpp + ${CMAKE_CURRENT_LIST_DIR}/monster.cpp + ${CMAKE_CURRENT_LIST_DIR}/monsters.cpp + ${CMAKE_CURRENT_LIST_DIR}/movement.cpp + ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp + ${CMAKE_CURRENT_LIST_DIR}/npc.cpp + ${CMAKE_CURRENT_LIST_DIR}/otserv.cpp + ${CMAKE_CURRENT_LIST_DIR}/outfit.cpp + ${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp + ${CMAKE_CURRENT_LIST_DIR}/party.cpp + ${CMAKE_CURRENT_LIST_DIR}/player.cpp + ${CMAKE_CURRENT_LIST_DIR}/position.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp + ${CMAKE_CURRENT_LIST_DIR}/raids.cpp + ${CMAKE_CURRENT_LIST_DIR}/rsa.cpp + ${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp + ${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/server.cpp + ${CMAKE_CURRENT_LIST_DIR}/spawn.cpp + ${CMAKE_CURRENT_LIST_DIR}/spells.cpp + ${CMAKE_CURRENT_LIST_DIR}/script.cpp + ${CMAKE_CURRENT_LIST_DIR}/talkaction.cpp + ${CMAKE_CURRENT_LIST_DIR}/tasks.cpp + ${CMAKE_CURRENT_LIST_DIR}/teleport.cpp + ${CMAKE_CURRENT_LIST_DIR}/thing.cpp + ${CMAKE_CURRENT_LIST_DIR}/tile.cpp + ${CMAKE_CURRENT_LIST_DIR}/tools.cpp + ${CMAKE_CURRENT_LIST_DIR}/vocation.cpp + ${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp + ${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp + ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp + ${CMAKE_CURRENT_LIST_DIR}/quests.cpp + PARENT_SCOPE) + diff --git a/app/SabrehavenServer/src/account.h b/app/SabrehavenServer/src/account.h new file mode 100644 index 0000000..2090d92 --- /dev/null +++ b/app/SabrehavenServer/src/account.h @@ -0,0 +1,36 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F +#define FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F + +#include "enums.h" + +struct Account { + std::vector characters; + uint32_t name; + time_t lastDay = 0; + uint32_t id = 0; + uint16_t premiumDays = 0; + AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + + Account() = default; +}; + +#endif diff --git a/app/SabrehavenServer/src/actions.cpp b/app/SabrehavenServer/src/actions.cpp new file mode 100644 index 0000000..6e9d108 --- /dev/null +++ b/app/SabrehavenServer/src/actions.cpp @@ -0,0 +1,478 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "actions.h" +#include "bed.h" +#include "configmanager.h" +#include "container.h" +#include "game.h" +#include "pugicast.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Actions* g_actions; +extern ConfigManager g_config; + +Actions::Actions() : + scriptInterface("Action Interface") +{ + scriptInterface.initState(); +} + +Actions::~Actions() +{ + clear(); +} + +inline void Actions::clearMap(ActionUseMap& map) +{ + // Filter out duplicates to avoid double-free + std::unordered_set set; + for (const auto& it : map) { + set.insert(it.second); + } + map.clear(); + + for (Action* action : set) { + delete action; + } +} + +void Actions::clear() +{ + clearMap(useItemMap); + clearMap(actionItemMap); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& Actions::getScriptInterface() +{ + return scriptInterface; +} + +std::string Actions::getScriptBaseName() const +{ + return "actions"; +} + +Event* Actions::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "action") != 0) { + return nullptr; + } + return new Action(&scriptInterface); +} + +bool Actions::registerEvent(Event* event, const pugi::xml_node& node) +{ + Action* action = static_cast(event); //event is guaranteed to be an Action + + pugi::xml_attribute attr; + if ((attr = node.attribute("itemid"))) { + uint16_t id = pugi::cast(attr.value()); + + auto result = useItemMap.emplace(id, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; + } + return result.second; + } else if ((attr = node.attribute("fromid"))) { + pugi::xml_attribute toIdAttribute = node.attribute("toid"); + if (!toIdAttribute) { + std::cout << "[Warning - Actions::registerEvent] Missing toid in fromid: " << attr.as_string() << std::endl; + return false; + } + + uint16_t fromId = pugi::cast(attr.value()); + uint16_t iterId = fromId; + uint16_t toId = pugi::cast(toIdAttribute.value()); + + auto result = useItemMap.emplace(iterId, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + } + + bool success = result.second; + while (++iterId <= toId) { + result = useItemMap.emplace(iterId, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + continue; + } + success = true; + } + return success; + } else if ((attr = node.attribute("actionid"))) { + uint16_t aid = pugi::cast(attr.value()); + + auto result = actionItemMap.emplace(aid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl; + } + return result.second; + } else if ((attr = node.attribute("fromaid"))) { + pugi::xml_attribute toAidAttribute = node.attribute("toaid"); + if (!toAidAttribute) { + std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() << std::endl; + return false; + } + + uint16_t fromAid = pugi::cast(attr.value()); + uint16_t iterAid = fromAid; + uint16_t toAid = pugi::cast(toAidAttribute.value()); + + auto result = actionItemMap.emplace(iterAid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + } + + bool success = result.second; + while (++iterAid <= toAid) { + result = actionItemMap.emplace(iterAid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + continue; + } + success = true; + } + return success; + } + return false; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos) +{ + if (pos.x != 0xFFFF) { + const Position& playerPos = player->getPosition(); + if (playerPos.z != pos.z) { + return playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS; + } + + if (!Position::areInRange<1, 1>(playerPos, pos)) { + return RETURNVALUE_TOOFARAWAY; + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item) +{ + Action* action = getAction(item); + if (action) { + return action->canExecuteAction(player, pos); + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor) +{ + if (toPos.x == 0xFFFF) { + return RETURNVALUE_NOERROR; + } + + const Position& creaturePos = creature->getPosition(); + if (checkFloor && creaturePos.z != toPos.z) { + return creaturePos.z > toPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS; + } + + if (!Position::areInRange<7, 5>(toPos, creaturePos)) { + return RETURNVALUE_TOOFARAWAY; + } + + if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) { + return RETURNVALUE_CANNOTTHROW; + } + + return RETURNVALUE_NOERROR; +} + +Action* Actions::getAction(const Item* item) +{ + if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { + auto it = actionItemMap.find(item->getActionId()); + if (it != actionItemMap.end()) { + return it->second; + } + } + + auto it = useItemMap.find(item->getID()); + if (it != useItemMap.end()) { + return it->second; + } + + //rune items + return g_spells->getRuneSpell(item->getID()); +} + +ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey) +{ + if (Door* door = item->getDoor()) { + if (!door->canUse(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } + + Action* action = getAction(item); + if (action) { + if (action->isScripted()) { + if (action->executeUse(player, item, pos, nullptr, pos, isHotkey)) { + return RETURNVALUE_NOERROR; + } + + if (item->isRemoved()) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } else if (action->function) { + if (action->function(player, item, pos, nullptr, pos, isHotkey)) { + return RETURNVALUE_NOERROR; + } + } + } + + if (BedItem* bed = item->getBed()) { + if (!bed->canUse(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + + if (bed->trySleep(player)) { + player->setBedItem(bed); + if (!bed->sleep(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } + + return RETURNVALUE_NOERROR; + } + + if (Container* container = item->getContainer()) { + if (!item->isChestQuest()) { + Container* openContainer; + + //depot container + if (DepotLocker* depot = container->getDepotLocker()) { + DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true); + myDepotLocker->setParent(depot->getParent()->getTile()); + openContainer = myDepotLocker; + } else { + openContainer = container; + } + + if (g_config.getBoolean(ConfigManager::CORPSE_OWNER_ENABLED)) { + uint32_t corpseOwner = container->getCorpseOwner(); + if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { + return RETURNVALUE_YOUARENOTTHEOWNER; + } + } + + //open/close container + int32_t oldContainerId = player->getContainerID(openContainer); + if (oldContainerId != -1) { + player->onCloseContainer(openContainer); + player->closeContainer(oldContainerId); + } else { + player->addContainer(index, openContainer); + player->onSendContainer(openContainer); + } + + return RETURNVALUE_NOERROR; + } + } + + const ItemType& it = Item::items[item->getID()]; + if (it.canReadText) { + if (it.canWriteText) { + player->setWriteItem(item, it.maxTextLen); + player->sendTextWindow(item, it.maxTextLen, true); + } else { + player->setWriteItem(nullptr); + player->sendTextWindow(item, 0, false); + } + + return RETURNVALUE_NOERROR; + } else if (it.changeUse) { + if (it.transformToOnUse) { + g_game.transformItem(item, it.transformToOnUse); + g_game.startDecay(item); + return RETURNVALUE_NOERROR; + } + } + + return RETURNVALUE_CANNOTUSETHISOBJECT; +} + +bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey) +{ + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + if (isHotkey) { + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); + } + + ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + return true; +} + +bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos, + uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/) +{ + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + Action* action = getAction(item); + if (!action) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return false; + } + + ReturnValue ret = action->canExecuteAction(player, toPos); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + if (isHotkey) { + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } + else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); + } + + if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { + if (!action->hasOwnErrorHandler()) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + } + return false; + } + return true; +} + +void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t count) +{ + std::ostringstream ss; + + const ItemType& it = Item::items[item->getID()]; + if (!it.showCount) { + ss << "Using one of " << item->getName() << "..."; + } + else if (count == 1) { + ss << "Using the last " << item->getName() << "..."; + } + else { + ss << "Using one of " << count << ' ' << item->getPluralName() << "..."; + } + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + +Action::Action(LuaScriptInterface* interface) : + Event(interface), function(nullptr), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {} + +Action::Action(const Action* copy) : + Event(copy), allowFarUse(copy->allowFarUse), checkFloor(copy->checkFloor), checkLineOfSight(copy->checkLineOfSight) {} + +bool Action::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse"); + if (allowFarUseAttr) { + allowFarUse = allowFarUseAttr.as_bool(); + } + + pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls"); + if (blockWallsAttr) { + checkLineOfSight = blockWallsAttr.as_bool(); + } + + pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor"); + if (checkFloorAttr) { + checkFloor = checkFloorAttr.as_bool(); + } + + return true; +} + +std::string Action::getScriptEventName() const +{ + return "onUse"; +} + +ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos) +{ + if (!allowFarUse) { + return g_actions->canUse(player, toPos); + } + else { + return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor); + } +} + +Thing* Action::getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const +{ + if (targetCreature) { + return targetCreature; + } + return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET); +} + +bool Action::executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) +{ + //onUse(player, item, fromPosition, target, toPosition, isHotkey) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushPosition(L, fromPosition); + + LuaScriptInterface::pushThing(L, target); + LuaScriptInterface::pushPosition(L, toPosition); + + LuaScriptInterface::pushBoolean(L, isHotkey); + return scriptInterface->callFunction(6); +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/actions.h b/app/SabrehavenServer/src/actions.h new file mode 100644 index 0000000..6f03677 --- /dev/null +++ b/app/SabrehavenServer/src/actions.h @@ -0,0 +1,142 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 +#define FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 + +#include "baseevents.h" +#include "enums.h" +#include "luascript.h" + +class Action; +using Action_ptr = std::unique_ptr; +using ActionFunction = std::function; + +class Action : public Event +{ + public: + explicit Action(const Action* copy); + explicit Action(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + + //scripting + virtual bool executeUse(Player* player, Item* item, const Position& fromPosition, + Thing* target, const Position& toPosition, bool isHotkey); + // + + bool getAllowFarUse() const { + return allowFarUse; + } + void setAllowFarUse(bool v) { + allowFarUse = v; + } + + bool getCheckLineOfSight() const { + return checkLineOfSight; + } + void setCheckLineOfSight(bool v) { + checkLineOfSight = v; + } + + bool getCheckFloor() const { + return checkFloor; + } + void setCheckFloor(bool v) { + checkFloor = v; + } + + std::vector getItemIdRange() { + return ids; + } + void addItemId(uint16_t id) { + ids.emplace_back(id); + } + + std::vector getUniqueIdRange() { + return uids; + } + void addUniqueId(uint16_t id) { + uids.emplace_back(id); + } + + std::vector getActionIdRange() { + return aids; + } + void addActionId(uint16_t id) { + aids.emplace_back(id); + } + + virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); + virtual bool hasOwnErrorHandler() { + return false; + } + virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const; + + ActionFunction function; + + private: + std::string getScriptEventName() const override; + + bool allowFarUse = false; + bool checkFloor = true; + bool checkLineOfSight = true; + std::vector ids; + std::vector uids; + std::vector aids; +}; + +class Actions final : public BaseEvents +{ + public: + Actions(); + ~Actions(); + + // non-copyable + Actions(const Actions&) = delete; + Actions& operator=(const Actions&) = delete; + + bool useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); + bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature = nullptr); + + ReturnValue canUse(const Player* player, const Position& pos); + ReturnValue canUse(const Player* player, const Position& pos, const Item* item); + ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); + + private: + ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); + static void showUseHotkeyMessage(Player* player, const Item* item, uint32_t count); + + void clear() final; + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + typedef std::map ActionUseMap; + ActionUseMap useItemMap; + ActionUseMap actionItemMap; + + Action* getAction(const Item* item); + void clearMap(ActionUseMap& map); + + LuaScriptInterface scriptInterface; +}; + +#endif diff --git a/app/SabrehavenServer/src/ban.cpp b/app/SabrehavenServer/src/ban.cpp new file mode 100644 index 0000000..547ad29 --- /dev/null +++ b/app/SabrehavenServer/src/ban.cpp @@ -0,0 +1,127 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "ban.h" +#include "database.h" +#include "databasetasks.h" +#include "tools.h" + +bool Ban::acceptConnection(uint32_t clientip) +{ + std::lock_guard lockClass(lock); + + uint64_t currentTime = OTSYS_TIME(); + + auto it = ipConnectMap.find(clientip); + if (it == ipConnectMap.end()) { + ipConnectMap.emplace(clientip, ConnectBlock(currentTime, 0, 1)); + return true; + } + + ConnectBlock& connectBlock = it->second; + if (connectBlock.blockTime > currentTime) { + connectBlock.blockTime += 250; + return false; + } + + int64_t timeDiff = currentTime - connectBlock.lastAttempt; + connectBlock.lastAttempt = currentTime; + if (timeDiff <= 5000) { + if (++connectBlock.count > 5) { + connectBlock.count = 0; + if (timeDiff <= 500) { + connectBlock.blockTime = currentTime + 3000; + return false; + } + } + } else { + connectBlock.count = 1; + } + return true; +} + +bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `account_bans` WHERE `account_id` = " << accountId; + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + int64_t expiresAt = result->getNumber("expires_at"); + if (expiresAt != 0 && time(nullptr) > expiresAt) { + // Move the ban to history if it has expired + query.str(std::string()); + query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db->escapeString(result->getString("reason")) << ',' << result->getNumber("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; + g_databaseTasks.addTask(query.str()); + + query.str(std::string()); + query << "DELETE FROM `account_bans` WHERE `account_id` = " << accountId; + g_databaseTasks.addTask(query.str()); + return false; + } + + banInfo.expiresAt = expiresAt; + banInfo.reason = result->getString("reason"); + banInfo.bannedBy = result->getString("name"); + return true; +} + +bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo) +{ + if (clientip == 0) { + return false; + } + + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `reason`, `expires_at`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `ip_bans` WHERE `ip` = " << clientip; + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + int64_t expiresAt = result->getNumber("expires_at"); + if (expiresAt != 0 && time(nullptr) > expiresAt) { + query.str(std::string()); + query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientip; + g_databaseTasks.addTask(query.str()); + return false; + } + + banInfo.expiresAt = expiresAt; + banInfo.reason = result->getString("reason"); + banInfo.bannedBy = result->getString("name"); + return true; +} + +bool IOBan::isPlayerNamelocked(uint32_t playerId) +{ + std::ostringstream query; + query << "SELECT 1 FROM `player_namelocks` WHERE `player_id` = " << playerId; + return Database::getInstance()->storeQuery(query.str()).get() != nullptr; +} diff --git a/app/SabrehavenServer/src/ban.h b/app/SabrehavenServer/src/ban.h new file mode 100644 index 0000000..86535a6 --- /dev/null +++ b/app/SabrehavenServer/src/ban.h @@ -0,0 +1,58 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 +#define FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 + +struct BanInfo { + std::string bannedBy; + std::string reason; + time_t expiresAt; +}; + +struct ConnectBlock { + constexpr ConnectBlock(uint64_t lastAttempt, uint64_t blockTime, uint32_t count) : + lastAttempt(lastAttempt), blockTime(blockTime), count(count) {} + + uint64_t lastAttempt; + uint64_t blockTime; + uint32_t count; +}; + +typedef std::map IpConnectMap; + +class Ban +{ + public: + bool acceptConnection(uint32_t clientip); + + protected: + IpConnectMap ipConnectMap; + std::recursive_mutex lock; +}; + +class IOBan +{ + public: + static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); + static bool isIpBanned(uint32_t ip, BanInfo& banInfo); + static bool isPlayerNamelocked(uint32_t playerId); +}; + +#endif diff --git a/app/SabrehavenServer/src/baseevents.cpp b/app/SabrehavenServer/src/baseevents.cpp new file mode 100644 index 0000000..c99243e --- /dev/null +++ b/app/SabrehavenServer/src/baseevents.cpp @@ -0,0 +1,165 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "baseevents.h" + +#include "pugicast.h" +#include "tools.h" + +extern LuaEnvironment g_luaEnvironment; + +bool BaseEvents::loadFromXml() +{ + if (loaded) { + std::cout << "[Error - BaseEvents::loadFromXml] It's already loaded." << std::endl; + return false; + } + + std::string scriptsName = getScriptBaseName(); + std::string basePath = "data/" + scriptsName + "/"; + if (getScriptInterface().loadFile(basePath + "lib/" + scriptsName + ".lua") == -1) { + std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + std::string filename = basePath + scriptsName + ".xml"; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - BaseEvents::loadFromXml", filename, result); + return false; + } + + loaded = true; + + for (auto node : doc.child(scriptsName.c_str()).children()) { + Event* event = getEvent(node.name()); + if (!event) { + continue; + } + + if (!event->configureEvent(node)) { + std::cout << "[Warning - BaseEvents::loadFromXml] Failed to configure event" << std::endl; + delete event; + continue; + } + + bool success; + + pugi::xml_attribute scriptAttribute = node.attribute("script"); + if (scriptAttribute) { + std::string scriptFile = "scripts/" + std::string(scriptAttribute.as_string()); + success = event->checkScript(basePath, scriptsName, scriptFile) && event->loadScript(basePath + scriptFile); + } else { + success = event->loadFunction(node.attribute("function")); + } + + if (!success || !registerEvent(event, node)) { + delete event; + } + } + return true; +} + +bool BaseEvents::reload() +{ + loaded = false; + clear(); + return loadFromXml(); +} + +Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {} + +Event::Event(const Event* copy) : + scripted(copy->scripted), scriptId(copy->scriptId), scriptInterface(copy->scriptInterface) {} + +bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const +{ + LuaScriptInterface* testInterface = g_luaEnvironment.getTestInterface(); + testInterface->reInitState(); + + if (testInterface->loadFile(std::string(basePath + "lib/" + scriptsName + ".lua")) == -1) { + std::cout << "[Warning - Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + if (scriptId != 0) { + std::cout << "[Failure - Event::checkScript] scriptid = " << scriptId << std::endl; + return false; + } + + if (testInterface->loadFile(basePath + scriptFile) == -1) { + std::cout << "[Warning - Event::checkScript] Can not load script: " << scriptFile << std::endl; + std::cout << testInterface->getLastLuaError() << std::endl; + return false; + } + + int32_t id = testInterface->getEvent(getScriptEventName()); + if (id == -1) { + std::cout << "[Warning - Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + return true; +} + +bool Event::loadScript(const std::string& scriptFile) +{ + if (!scriptInterface || scriptId != 0) { + std::cout << "Failure: [Event::loadScript] scriptInterface == nullptr. scriptid = " << scriptId << std::endl; + return false; + } + + if (scriptInterface->loadFile(scriptFile) == -1) { + std::cout << "[Warning - Event::loadScript] Can not load script. " << scriptFile << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + return false; + } + + int32_t id = scriptInterface->getEvent(getScriptEventName()); + if (id == -1) { + std::cout << "[Warning - Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + + scripted = true; + scriptId = id; + return true; +} + +bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& name) +{ + if (!interface) { + std::cout << "Failure: [CallBack::loadCallBack] scriptInterface == nullptr" << std::endl; + return false; + } + + scriptInterface = interface; + + int32_t id = scriptInterface->getEvent(name.c_str()); + if (id == -1) { + std::cout << "[Warning - CallBack::loadCallBack] Event " << name << " not found." << std::endl; + return false; + } + + callbackName = name; + scriptId = id; + loaded = true; + return true; +} diff --git a/app/SabrehavenServer/src/baseevents.h b/app/SabrehavenServer/src/baseevents.h new file mode 100644 index 0000000..c5b528f --- /dev/null +++ b/app/SabrehavenServer/src/baseevents.h @@ -0,0 +1,90 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A +#define FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A + +#include "luascript.h" + +class Event +{ + public: + explicit Event(LuaScriptInterface* interface); + explicit Event(const Event* copy); + virtual ~Event() = default; + + virtual bool configureEvent(const pugi::xml_node& node) = 0; + + bool checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const; + bool loadScript(const std::string& scriptFile); + virtual bool loadFunction(const pugi::xml_attribute&) { + return false; + } + + bool isScripted() const { + return scripted; + } + + protected: + virtual std::string getScriptEventName() const = 0; + + bool scripted = false; + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; +}; + +class BaseEvents +{ + public: + constexpr BaseEvents() = default; + virtual ~BaseEvents() = default; + + bool loadFromXml(); + bool reload(); + bool isLoaded() const { + return loaded; + } + + protected: + virtual LuaScriptInterface& getScriptInterface() = 0; + virtual std::string getScriptBaseName() const = 0; + virtual Event* getEvent(const std::string& nodeName) = 0; + virtual bool registerEvent(Event* event, const pugi::xml_node& node) = 0; + virtual void clear() = 0; + + bool loaded = false; +}; + +class CallBack +{ + public: + CallBack() = default; + + bool loadCallBack(LuaScriptInterface* interface, const std::string& name); + + protected: + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; + + bool loaded = false; + + std::string callbackName; +}; + +#endif diff --git a/app/SabrehavenServer/src/bed.cpp b/app/SabrehavenServer/src/bed.cpp new file mode 100644 index 0000000..f233e31 --- /dev/null +++ b/app/SabrehavenServer/src/bed.cpp @@ -0,0 +1,277 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "bed.h" +#include "game.h" +#include "iologindata.h" +#include "scheduler.h" + +extern Game g_game; + +BedItem::BedItem(uint16_t id) : Item(id) +{ + internalRemoveSleeper(); +} + +Attr_ReadValue BedItem::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_SLEEPERGUID: { + uint32_t guid; + if (!propStream.read(guid)) { + return ATTR_READ_ERROR; + } + + if (guid != 0) { + std::string name = IOLoginData::getNameByGuid(guid); + if (!name.empty()) { + setSpecialDescription(name + " is sleeping there."); + g_game.setBedSleeper(this, guid); + sleeperGUID = guid; + } + } + return ATTR_READ_CONTINUE; + } + + case ATTR_SLEEPSTART: { + uint32_t sleep_start; + if (!propStream.read(sleep_start)) { + return ATTR_READ_ERROR; + } + + sleepStart = static_cast(sleep_start); + return ATTR_READ_CONTINUE; + } + + default: + break; + } + return Item::readAttr(attr, propStream); +} + +void BedItem::serializeAttr(PropWriteStream& propWriteStream) const +{ + if (sleeperGUID != 0) { + propWriteStream.write(ATTR_SLEEPERGUID); + propWriteStream.write(sleeperGUID); + } + + if (sleepStart != 0) { + propWriteStream.write(ATTR_SLEEPSTART); + // FIXME: should be stored as 64-bit, but we need to retain backwards compatibility + propWriteStream.write(static_cast(sleepStart)); + } +} + +BedItem* BedItem::getNextBedItem() const +{ + Direction dir = Item::items[id].bedPartnerDir; + Position targetPos = getNextPosition(dir, getPosition()); + + Tile* tile = g_game.map.getTile(targetPos); + if (!tile) { + return nullptr; + } + return tile->getBedItem(); +} + +bool BedItem::canUse(Player* player) +{ + if (!player || !house || !player->isPremium()) { + return false; + } + + if (sleeperGUID == 0) { + return true; + } + + if (house->getHouseAccessLevel(player) == HOUSE_OWNER) { + return true; + } + + Player sleeper(nullptr); + if (!IOLoginData::loadPlayerById(&sleeper, sleeperGUID)) { + return false; + } + + if (house->getHouseAccessLevel(&sleeper) > house->getHouseAccessLevel(player)) { + return false; + } + return true; +} + +bool BedItem::trySleep(Player* player) +{ + if (!house || player->isRemoved()) { + return false; + } + + if (sleeperGUID != 0) { + if (Item::items[id].transformToFree != 0 && house->getOwner() == player->getGUID()) { + wakeUp(nullptr); + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + return true; +} + +bool BedItem::sleep(Player* player) +{ + if (!house) { + return false; + } + + if (sleeperGUID != 0) { + return false; + } + + BedItem* nextBedItem = getNextBedItem(); + + internalSetSleeper(player); + + if (nextBedItem) { + nextBedItem->internalSetSleeper(player); + } + + // update the bedSleepersMap + g_game.setBedSleeper(this, player->getGUID()); + + // make the player walk onto the bed + g_game.map.moveCreature(*player, *getTile()); + + // display poff effect + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + + // kick player after he sees himself walk onto the bed and it change id + uint32_t playerId = player->getID(); + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&Game::kickPlayer, &g_game, playerId, false))); + + // change self and partner's appearance + updateAppearance(player); + + if (nextBedItem) { + nextBedItem->updateAppearance(player); + } + + return true; +} + +void BedItem::wakeUp(Player* player) +{ + if (!house) { + return; + } + + if (sleeperGUID != 0) { + if (!player) { + Player regenPlayer(nullptr); + if (IOLoginData::loadPlayerById(®enPlayer, sleeperGUID)) { + regeneratePlayer(®enPlayer); + IOLoginData::savePlayer(®enPlayer); + } + } else { + regeneratePlayer(player); + g_game.addCreatureHealth(player); + } + } + + // update the bedSleepersMap + g_game.removeBedSleeper(sleeperGUID); + + BedItem* nextBedItem = getNextBedItem(); + + // unset sleep info + internalRemoveSleeper(); + + if (nextBedItem) { + nextBedItem->internalRemoveSleeper(); + } + + // change self and partner's appearance + updateAppearance(nullptr); + + if (nextBedItem) { + nextBedItem->updateAppearance(nullptr); + } +} + +void BedItem::regeneratePlayer(Player* player) const +{ + const uint32_t sleptTime = time(nullptr) - sleepStart; + + /*Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + if (condition) { + uint32_t regen; + if (condition->getTicks() != -1) { + regen = std::min((condition->getTicks() / 1000), sleptTime) / 30; + const int32_t newRegenTicks = condition->getTicks() - (regen * 30000); + if (newRegenTicks <= 0) { + player->removeCondition(condition); + } else { + condition->setTicks(newRegenTicks); + } + } else { + regen = sleptTime / 30; + } + + player->changeHealth(regen, false); + player->changeMana(regen); + }*/ + + const int32_t soulRegen = sleptTime / (60 * 15); + player->changeSoul(soulRegen); +} + +void BedItem::updateAppearance(const Player* player) +{ + const ItemType& it = Item::items[id]; + if (it.type == ITEM_TYPE_BED) { + if (player && it.transformToOnUse != 0) { + const ItemType& newType = Item::items[it.transformToOnUse]; + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToOnUse); + } + } else if (it.transformToFree != 0) { + const ItemType& newType = Item::items[it.transformToFree]; + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToFree); + } + } + } +} + +void BedItem::internalSetSleeper(const Player* player) +{ + std::string desc_str = player->getName() + " is sleeping there."; + + sleeperGUID = player->getGUID(); + sleepStart = time(nullptr); + setSpecialDescription(desc_str); +} + +void BedItem::internalRemoveSleeper() +{ + sleeperGUID = 0; + sleepStart = 0; + setSpecialDescription("Nobody is sleeping there."); +} diff --git a/app/SabrehavenServer/src/bed.h b/app/SabrehavenServer/src/bed.h new file mode 100644 index 0000000..f1f836d --- /dev/null +++ b/app/SabrehavenServer/src/bed.h @@ -0,0 +1,77 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BED_H_84DE19758D424C6C9789189231946BFF +#define FS_BED_H_84DE19758D424C6C9789189231946BFF + +#include "item.h" + +class House; +class Player; + +class BedItem final : public Item +{ + public: + explicit BedItem(uint16_t id); + + BedItem* getBed() final { + return this; + } + const BedItem* getBed() const final { + return this; + } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; + + bool canRemove() const final { + return house == nullptr; + } + + uint32_t getSleeper() const { + return sleeperGUID; + } + + House* getHouse() const { + return house; + } + void setHouse(House* h) { + house = h; + } + + bool canUse(Player* player); + + bool trySleep(Player* player); + bool sleep(Player* player); + void wakeUp(Player* player); + + BedItem* getNextBedItem() const; + + protected: + void updateAppearance(const Player* player); + void regeneratePlayer(Player* player) const; + void internalSetSleeper(const Player* player); + void internalRemoveSleeper(); + + House* house = nullptr; + uint64_t sleepStart; + uint32_t sleeperGUID; +}; + +#endif diff --git a/app/SabrehavenServer/src/behaviourdatabase.cpp b/app/SabrehavenServer/src/behaviourdatabase.cpp new file mode 100644 index 0000000..b3d972f --- /dev/null +++ b/app/SabrehavenServer/src/behaviourdatabase.cpp @@ -0,0 +1,1591 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "otpch.h" + +#include "iologindata.h" +#include "behaviourdatabase.h" +#include "npc.h" +#include "player.h" +#include "game.h" +#include "spells.h" +#include "monster.h" +#include "scheduler.h" + +extern Game g_game; +extern Monsters g_monsters; +extern Spells* g_spells; + +BehaviourDatabase::BehaviourDatabase(Npc * _npc) : npc(_npc) { + topic = 0; + data = -1; + type = 0; + price = 0; + amount = 0; + delay = 1000; +} + +BehaviourDatabase::~BehaviourDatabase() { + for (NpcBehaviour* behaviour : behaviourEntries) { + delete behaviour; + } +} + +bool BehaviourDatabase::loadDatabase(ScriptReader& script) +{ + script.readSymbol('{'); + script.nextToken(); + while (true) { + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token == SPECIAL && script.getSpecial() == '}') { + break; + } + + if (!loadBehaviour(script)) { + return false; + } + } + + return true; +} + +bool BehaviourDatabase::loadBehaviour(ScriptReader& script) +{ + NpcBehaviour* behaviour = new NpcBehaviour(); + + if (!loadConditions(script, behaviour)) { + return false; + } + + if (script.Token != SPECIAL || script.getSpecial() != 'I') { + script.error("'->' expected"); + delete behaviour; + return false; + } + + script.nextToken(); + if (!loadActions(script, behaviour)) { + delete behaviour; + return false; + } + + // set this behaviour priority to condition size + behaviour->priority += behaviour->conditions.size(); + + if (priorityBehaviour) { + priorityBehaviour->priority += behaviour->priority + 1; + priorityBehaviour = nullptr; + } + + // order it correctly + auto it = std::lower_bound(behaviourEntries.begin(), behaviourEntries.end(), behaviour, compareBehaviour); + behaviourEntries.insert(it, behaviour); + + // set previous behaviour (*) functionality + previousBehaviour = behaviour; + return true; +} + +bool BehaviourDatabase::loadConditions(ScriptReader& script, NpcBehaviour* behaviour) +{ + while (true) { + std::unique_ptr condition(new NpcBehaviourCondition); + + bool searchTerm = false; + if (script.Token == IDENTIFIER) { + std::string identifier = script.getIdentifier(); + if (identifier == "address") { + condition->situation = SITUATION_ADDRESS; + behaviour->situation = SITUATION_ADDRESS; + searchTerm = true; + } else if (identifier == "busy") { + condition->situation = SITUATION_BUSY; + behaviour->situation = SITUATION_BUSY; + searchTerm = true; + } else if (identifier == "vanish") { + condition->situation = SITUATION_VANISH; + behaviour->situation = SITUATION_VANISH; + searchTerm = true; + } else if (identifier == "sorcerer") { + condition->type = BEHAVIOUR_TYPE_SORCERER; + searchTerm = true; + } else if (identifier == "knight") { + condition->type = BEHAVIOUR_TYPE_KNIGHT; + searchTerm = true; + } else if (identifier == "paladin") { + condition->type = BEHAVIOUR_TYPE_PALADIN; + searchTerm = true; + } else if (identifier == "druid") { + condition->type = BEHAVIOUR_TYPE_DRUID; + searchTerm = true; + } else if (identifier == "premium") { + condition->type = BEHAVIOUR_TYPE_ISPREMIUM; + searchTerm = true; + } else if (identifier == "realpremium") { + condition->type = BEHAVIOUR_TYPE_ISREALPREMIUM; + searchTerm = true; + } else if (identifier == "pvpenforced") { + condition->type = BEHAVIOUR_TYPE_PVPENFORCED; + searchTerm = true; + } else if (identifier == "female") { + condition->type = BEHAVIOUR_TYPE_FEMALE; + searchTerm = true; + } else if (identifier == "male") { + condition->type = BEHAVIOUR_TYPE_MALE; + searchTerm = true; + } else if (identifier == "pzblock") { + condition->type = BEHAVIOUR_TYPE_PZLOCKED; + searchTerm = true; + } else if (identifier == "pzfree") { + condition->type = BEHAVIOUR_TYPE_PZFREE; + searchTerm = true; + } else if (identifier == "promoted") { + condition->type = BEHAVIOUR_TYPE_PROMOTED; + searchTerm = true; + } + } else if (script.Token == STRING) { + const std::string keyString = asLowerCaseString(script.getString()); + condition->setCondition(BEHAVIOUR_TYPE_STRING, 0, keyString); + behaviour->priority += keyString.length(); + + searchTerm = true; + } else if (script.Token == SPECIAL) { + if (script.getSpecial() == '!') { + condition->setCondition(BEHAVIOUR_TYPE_NOP, 0, ""); + searchTerm = true; + + // set this one for behaviour + priorityBehaviour = behaviour; + } else if (script.getSpecial() == '%') { + condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT, script.readNumber(), ""); + searchTerm = true; + } else if (script.getSpecial() == '$') { + condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, script.readNumber(), ""); + searchTerm = true; + } else if (script.getSpecial() == ',') { + script.nextToken(); + continue; + } else { + break; + } + } + + // relational operation search + if (!searchTerm) { + condition->type = BEHAVIOUR_TYPE_OPERATION; + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + + // relational operators + if (script.Token != SPECIAL) { + script.error("relational operator expected"); + delete nextNode; + return false; + } + + NpcBehaviourOperator_t operatorType; + switch (script.getSpecial()) { + case '<': + operatorType = BEHAVIOUR_OPERATOR_LESSER_THAN; + break; + case '=': + operatorType = BEHAVIOUR_OPERATOR_EQUALS; + break; + case '>': + operatorType = BEHAVIOUR_OPERATOR_GREATER_THAN; + break; + case 'G': + operatorType = BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS; + break; + case 'N': + operatorType = BEHAVIOUR_OPERATOR_NOT_EQUALS; + break; + case 'L': + operatorType = BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS; + break; + default: + script.error("relational operator expected"); + delete nextNode; + return false; + } + + script.nextToken(); + headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = operatorType; + headNode->left = nextNode; + nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + headNode->right = nextNode; + + condition->expression = headNode; + } else { + script.nextToken(); + } + + behaviour->conditions.push_back(condition.release()); + } + + return true; +} + +bool BehaviourDatabase::loadActions(ScriptReader& script, NpcBehaviour* behaviour) +{ + while (true) { + std::unique_ptr action(new NpcBehaviourAction); + NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE; + + if (script.Token == STRING) { + action->type = BEHAVIOUR_TYPE_STRING; + action->string = script.getString(); + } else if (script.Token == IDENTIFIER) { + std::string identifier = script.getIdentifier(); + if (identifier == "idle") { + action->type = BEHAVIOUR_TYPE_IDLE; + } else if (identifier == "nop") { + action->type = BEHAVIOUR_TYPE_NOP; + } else if (identifier == "queue") { + action->type = BEHAVIOUR_TYPE_QUEUE; + } else if (identifier == "createmoney") { + action->type = BEHAVIOUR_TYPE_CREATEMONEY; + } else if (identifier == "deletemoney") { + action->type = BEHAVIOUR_TYPE_DELETEMONEY; + } else if (identifier == "promote") { + action->type = BEHAVIOUR_TYPE_PROMOTE; + } else if (identifier == "topic") { + action->type = BEHAVIOUR_TYPE_TOPIC; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "price") { + action->type = BEHAVIOUR_TYPE_PRICE; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "amount") { + action->type = BEHAVIOUR_TYPE_AMOUNT; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "data") { + action->type = BEHAVIOUR_TYPE_DATA; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "type") { + action->type = BEHAVIOUR_TYPE_ITEM; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "string") { + action->type = BEHAVIOUR_TYPE_TEXT; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "hp") { + action->type = BEHAVIOUR_TYPE_HEALTH; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "withdraw") { + action->type = BEHAVIOUR_TYPE_WITHDRAW; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "guildwithdraw") { + action->type = BEHAVIOUR_TYPE_GUILDWITHDRAW; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "deposit") { + action->type = BEHAVIOUR_TYPE_DEPOSIT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "guilddeposit") { + action->type = BEHAVIOUR_TYPE_GUILDDEPOSIT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "transfer") { + action->type = BEHAVIOUR_TYPE_TRANSFER; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "bless") { + action->type = BEHAVIOUR_TYPE_BLESS; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "effectme") { + action->type = BEHAVIOUR_TYPE_EFFECTME; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "effectopp") { + action->type = BEHAVIOUR_TYPE_EFFECTOPP; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "create") { + action->type = BEHAVIOUR_TYPE_CREATE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "delete") { + action->type = BEHAVIOUR_TYPE_DELETE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "deleteamount") { + action->type = BEHAVIOUR_TYPE_DELETEAMOUNT; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "teachspell") { + action->type = BEHAVIOUR_TYPE_TEACHSPELL; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "town") { + action->type = BEHAVIOUR_TYPE_TOWN; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "profession") { + action->type = BEHAVIOUR_TYPE_PROFESSION; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "experience") { + action->type = BEHAVIOUR_TYPE_EXPERIENCE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "summon") { + action->type = BEHAVIOUR_TYPE_SUMMON; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "burning") { + action->type = BEHAVIOUR_TYPE_BURNING; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "drunk") { + action->type = BEHAVIOUR_TYPE_BURNING; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "setquestvalue") { + action->type = BEHAVIOUR_TYPE_QUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "setexpiringquestvalue") { + action->type = BEHAVIOUR_TYPE_EXPIRINGQUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "addoutfitaddon") { + action->type = BEHAVIOUR_TYPE_ADDOUTFITADDON; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "addoutfit") { + action->type = BEHAVIOUR_TYPE_ADDOUTFIT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "poison") { + action->type = BEHAVIOUR_TYPE_POISON; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "teleport") { + action->type = BEHAVIOUR_TYPE_TELEPORT; + searchType = BEHAVIOUR_PARAMETER_THREE; + } else if (identifier == "createcontainer") { + action->type = BEHAVIOUR_TYPE_CREATECONTAINER; + searchType = BEHAVIOUR_PARAMETER_THREE; + } else { + script.error("illegal action term"); + return false; + } + } else if (script.Token == SPECIAL) { + if (script.getSpecial() == '*') { + if (previousBehaviour == nullptr) { + script.error("no previous pattern"); + return false; + } + + for (NpcBehaviourAction* actionCopy : previousBehaviour->actions) { + behaviour->actions.push_back(actionCopy->clone()); + } + script.nextToken(); + return true; + } + } + + if (searchType == BEHAVIOUR_PARAMETER_ASSIGN) { + script.readSymbol('='); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + } else if (searchType == BEHAVIOUR_PARAMETER_ONE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else if (searchType == BEHAVIOUR_PARAMETER_TWO) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression2 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else if (searchType == BEHAVIOUR_PARAMETER_THREE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression2 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression3 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else { + script.nextToken(); + } + + behaviour->actions.push_back(action.release()); + + if (script.Token == SPECIAL) { + if (script.getSpecial() == ',') { + script.nextToken(); + continue; + } + } + + break; + } + + return true; +} + +NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) +{ + if (script.Token == NUMBER) { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_NUMBER; + node->number = script.getNumber(); + script.nextToken(); + return node; + } + + if (script.Token == STRING) { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_STRING; + node->string = asLowerCaseString(script.getString()); + script.nextToken(); + return node; + } + + if (script.Token == SPECIAL) { + if (script.getSpecial() == '%') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; + node->number = script.readNumber(); + script.nextToken(); + return node; + } + + if (script.getSpecial() == '$') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT; + node->number = script.readNumber(); + script.nextToken(); + return node; + } + + script.error("illegal character"); + return nullptr; + } + + NpcBehaviourNode* node = nullptr; + NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE; + + std::string identifier = script.getIdentifier(); + if (identifier == "topic") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_TOPIC; + } else if (identifier == "price") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_PRICE; + } else if (identifier == "type") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_ITEM; + } else if (identifier == "string") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_TEXT; + } else if (identifier == "data") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_DATA; + } else if (identifier == "amount") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_AMOUNT; + } else if (identifier == "countmoney") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_COUNTMONEY; + } else if (identifier == "hp") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_HEALTH; + } else if (identifier == "burning") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_BURNING; + } else if (identifier == "drunk") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_DRUNK; + } else if (identifier == "level") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_LEVEL; + } else if (identifier == "guildlevel") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_GUILDLEVEL; + } else if (identifier == "poison") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_POISON; + } else if (identifier == "balance") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_BALANCE; + } else if (identifier == "guildbalance") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_GUILDBALANCE; + } else if (identifier == "transfertoplayernamestate") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE; + } else if (identifier == "spellknown") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_SPELLKNOWN; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "spelllevel") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_SPELLLEVEL; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "questvalue") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_QUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "expiringquestvalue") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_EXPIRINGQUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "count") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_COUNT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "experiencestage") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_EXPERIENCESTAGE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "random") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_RANDOM; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "slotitem") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_SLOTITEM; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "clientversion") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_CLIENTVERSION; + } + + if (searchType == BEHAVIOUR_PARAMETER_ONE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->left = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + } + } else if (searchType == BEHAVIOUR_PARAMETER_TWO) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->left = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + } + script.nextToken(); + nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->right = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + } + } + + if (!node) { + script.error("unknown value"); + } + + script.nextToken(); + return node; +} + +NpcBehaviourNode* BehaviourDatabase::readFactor(ScriptReader& script, NpcBehaviourNode* nextNode) +{ + // * operator + while (true) { + if (script.Token != SPECIAL) { + break; + } + + if (script.getSpecial() != '*') { + break; + } + + NpcBehaviourNode* headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = BEHAVIOUR_OPERATOR_MULTIPLY; + headNode->left = nextNode; + + script.nextToken(); + nextNode = readValue(script); + + headNode->right = nextNode; + nextNode = headNode; + } + + // / operator + while (true) { + if (script.Token != SPECIAL) { + break; + } + + if (script.getSpecial() != '/') { + break; + } + + NpcBehaviourNode* headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = BEHAVIOUR_OPERATOR_DIVIDE; + headNode->left = nextNode; + + script.nextToken(); + nextNode = readValue(script); + + headNode->right = nextNode; + nextNode = headNode; + } + + // + - operators + while (true) { + if (script.Token != SPECIAL) { + break; + } + + if (script.getSpecial() != '+' && script.getSpecial() != '-') { + break; + } + + NpcBehaviourNode* headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = BEHAVIOUR_OPERATOR_SUM; + if (script.getSpecial() == '-') { + headNode->number = BEHAVIOUR_OPERATOR_RES; + } + + headNode->left = nextNode; + script.nextToken(); + nextNode = readValue(script); + + headNode->right = nextNode; + nextNode = headNode; + } + + return nextNode; +} + +void BehaviourDatabase::react(BehaviourSituation_t situation, Player* player, const std::string& message) +{ + for (NpcBehaviour* behaviour : behaviourEntries) { + bool fulfilled = true; + + if (situation == SITUATION_ADDRESS && behaviour->situation != SITUATION_ADDRESS) { + continue; + } + + if (situation == SITUATION_BUSY && behaviour->situation != SITUATION_BUSY) { + continue; + } + + if (situation == SITUATION_VANISH && behaviour->situation != SITUATION_VANISH) { + continue; + } + + if (situation == SITUATION_NONE && behaviour->situation != SITUATION_NONE) { + continue; + } + + for (const NpcBehaviourCondition* condition : behaviour->conditions) { + if (!checkCondition(condition, player, message)) { + fulfilled = false; + break; + } + } + + if (!fulfilled) { + continue; + } + + if (player->getID() == npc->focusCreature) { + topic = 0; + } + + reset(); + + if (situation == SITUATION_ADDRESS || npc->focusCreature == player->getID()) { + attendCustomer(player->getID()); + } + + if (situation == SITUATION_VANISH) { + npc->conversationEndTime = 0; + idle(); + } + + for (const NpcBehaviourAction* action : behaviour->actions) { + checkAction(action, player, message); + } + + break; + } +} + +bool BehaviourDatabase::checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message) +{ + switch (condition->type) { + case BEHAVIOUR_TYPE_NOP: break; + case BEHAVIOUR_TYPE_MESSAGE_COUNT: { + int32_t value = searchDigit(message); + if (value < condition->number) { + return false; + } + break; + } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < condition->number) { + return false; + } + break; + } + case BEHAVIOUR_TYPE_STRING: + if (!searchWord(condition->string, message)) { + return false; + } + break; + case BEHAVIOUR_TYPE_SORCERER: + if (player->getVocationId() != 1 && player->getVocationId() != 5) { + return false; + } + break; + case BEHAVIOUR_TYPE_DRUID: + if (player->getVocationId() != 2 && player->getVocationId() != 6) { + return false; + } + break; + case BEHAVIOUR_TYPE_PALADIN: + if (player->getVocationId() != 3 && player->getVocationId() != 7) { + return false; + } + break; + case BEHAVIOUR_TYPE_KNIGHT: + if (player->getVocationId() != 4 && player->getVocationId() != 8) { + return false; + } + break; + case BEHAVIOUR_TYPE_ISPREMIUM: + + break; + case BEHAVIOUR_TYPE_ISREALPREMIUM: + if (!player->isPremium()) { + return false; + } + break; + case BEHAVIOUR_TYPE_PVPENFORCED: + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + return false; + } + break; + case BEHAVIOUR_TYPE_FEMALE: + if (player->getSex() != PLAYERSEX_FEMALE) { + return false; + } + break; + case BEHAVIOUR_TYPE_MALE: + if (player->getSex() != PLAYERSEX_MALE) { + return false; + } + break; + case BEHAVIOUR_TYPE_PZLOCKED: + if (!player->isPzLocked()) { + return false; + } + break; + case BEHAVIOUR_TYPE_PZFREE: + if (player->isPzLocked()) { + return false; + } + break; + case BEHAVIOUR_TYPE_PROMOTED: { + int32_t value = 0; + player->getStorageValue(30018, value); + if (value != 1) { + return false; + } + break; + } + case BEHAVIOUR_TYPE_OPERATION: + return checkOperation(player, condition->expression, message) > 0; + case BEHAVIOUR_TYPE_SPELLKNOWN: { + if (!player->hasLearnedInstantSpell(string)) { + return false; + } + + break; + } + default: + std::cout << "[Warning - BehaviourDatabase::react]: Unhandled node type " << condition->type << std::endl; + return false; + } + + return true; +} + +void BehaviourDatabase::checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message) +{ + switch (action->type) { + case BEHAVIOUR_TYPE_NOP: break; + case BEHAVIOUR_TYPE_STRING: { + delayedEvents.push_back(g_scheduler.addEvent(createSchedulerTask(delay, std::bind(&Npc::doSay, npc, parseResponse(player, action->string))))); + delay += 100 * (message.length() / 5) + 10000; + break; + } + case BEHAVIOUR_TYPE_IDLE: + idle(); + break; + case BEHAVIOUR_TYPE_QUEUE: + queueCustomer(player->getID(), message); + break; + case BEHAVIOUR_TYPE_TOPIC: + topic = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_PRICE: + price = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_DATA: + data = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_ITEM: + type = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_AMOUNT: + amount = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_TEXT: + string = action->expression->string; + break; + case BEHAVIOUR_TYPE_HEALTH: { + int32_t newHealth = evaluate(action->expression, player, message); + player->changeHealth(-player->getHealth() + newHealth); + break; + } + case BEHAVIOUR_TYPE_CREATEMONEY: + g_game.addMoney(player, price); + break; + case BEHAVIOUR_TYPE_DELETEMONEY: + g_game.removeMoney(player, price); + break; + case BEHAVIOUR_TYPE_CREATE: { + int32_t itemId = evaluate(action->expression, player, message); + const ItemType& it = Item::items[itemId]; + + if (it.stackable) { + do { + int32_t count = std::min(100, amount); + amount -= count; + + Item* item = Item::CreateItem(itemId, count); + if (!item) { + break; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } while (amount); + } else { + for (int32_t i = 0; i < std::max(1, amount); i++) { + Item* item = Item::CreateItem(itemId, data == -1 ? 0 : data); + if (!item) { + break; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + + break; + } + case BEHAVIOUR_TYPE_DELETE: { + type = evaluate(action->expression, player, message); + const ItemType& itemType = Item::items[type]; + if (itemType.stackable || !itemType.hasSubType()) { + data = -1; + } + + if (!player->removeItemOfType(type, amount, data, true)) { + player->removeItemOfType(type, amount, data, false); + } + break; + } + case BEHAVIOUR_TYPE_DELETEAMOUNT: { + type = evaluate(action->expression, player, message); + int32_t amount = evaluate(action->expression2, player, message); + const ItemType& itemType = Item::items[type]; + if (itemType.stackable || !itemType.hasSubType()) { + data = -1; + } + + if (!player->removeItemOfType(type, amount, data, true)) { + player->removeItemOfType(type, amount, data, false); + } + break; + } + case BEHAVIOUR_TYPE_EFFECTME: + g_game.addMagicEffect(npc->getPosition(), evaluate(action->expression, player, message)); + break; + case BEHAVIOUR_TYPE_EFFECTOPP: + g_game.addMagicEffect(player->getPosition(), evaluate(action->expression, player, message)); + break; + case BEHAVIOUR_TYPE_BURNING: { + const int32_t cycles = evaluate(action->expression, player, message); + const int32_t count = evaluate(action->expression2, player, message); + + if (cycles == 0) { + player->removeCondition(CONDITION_FIRE, true); + break; + } + + ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + player->addCondition(conditionDamage); + break; + } + case BEHAVIOUR_TYPE_POISON: { + const int32_t cycles = evaluate(action->expression, player, message); + const int32_t count = evaluate(action->expression2, player, message); + + if (cycles == 0) { + player->removeCondition(CONDITION_POISON, true); + break; + } + + ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + player->addCondition(conditionDamage); + break; + } + case BEHAVIOUR_TYPE_TOWN: + player->setTown(g_game.map.towns.getTown(evaluate(action->expression, player, message))); + break; + case BEHAVIOUR_TYPE_TEACHSPELL: + player->learnInstantSpell(string); + break; + case BEHAVIOUR_TYPE_QUESTVALUE: { + int32_t questNumber = evaluate(action->expression, player, message); + int32_t questValue = evaluate(action->expression2, player, message); + player->addStorageValue(questNumber, questValue); + break; + } + case BEHAVIOUR_TYPE_EXPIRINGQUESTVALUE: { + int32_t questNumber = evaluate(action->expression, player, message); + int32_t ticks = evaluate(action->expression2, player, message); + player->addStorageValue(questNumber, OTSYS_TIME_MINUTES() + (ticks / 60 / 1000)); + break; + } + case BEHAVIOUR_TYPE_ADDOUTFITADDON: { + int32_t lookType = evaluate(action->expression, player, message); + int32_t addon = evaluate(action->expression2, player, message); + player->addOutfit(lookType, addon); + break; + } + case BEHAVIOUR_TYPE_ADDOUTFIT: { + int32_t lookType = evaluate(action->expression, player, message); + + player->addOutfit(lookType, 0); + break; + } + case BEHAVIOUR_TYPE_TELEPORT: { + Position pos; + pos.x = evaluate(action->expression, player, message); + pos.y = evaluate(action->expression2, player, message); + pos.z = evaluate(action->expression3, player, message); + g_game.internalTeleport(player, pos); + break; + } + case BEHAVIOUR_TYPE_PROFESSION: { + int32_t newVocation = evaluate(action->expression, player, message); + player->setVocation(newVocation); + break; + } + case BEHAVIOUR_TYPE_PROMOTE: { + int32_t newVocation = player->getVocationId() + 4; + player->setVocation(newVocation); + player->addStorageValue(30018, 1); + break; + } + case BEHAVIOUR_TYPE_SUMMON: { + std::string name = action->expression->string; + + Monster* monster = Monster::createMonster(name); + if (!monster) { + break; + } + + if (!g_game.placeCreature(monster, npc->getPosition(), true, true)) { + delete monster; + } + + break; + } + case BEHAVIOUR_TYPE_EXPERIENCE: { + int32_t experience = evaluate(action->expression, player, message); + player->addExperience(nullptr, experience, false); + break; + } + case BEHAVIOUR_TYPE_WITHDRAW: { + int32_t money = evaluate(action->expression, player, message); + player->setBankBalance(player->getBankBalance() - money); + break; + } + case BEHAVIOUR_TYPE_GUILDWITHDRAW: { + int32_t money = evaluate(action->expression, player, message); + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + break; + } + + if (player->getGuildRank()->level <= 1) { + break; + } + + if (money <= 0) { + break; + } + + + if (IOGuild::getGuildBalance(playerGuild->getId()) < static_cast(money)) { + break; + } + + if (IOGuild::decreaseGuildBankBalance(playerGuild->getId(), money)) { + player->setBankBalance(player->getBankBalance() + money); + } + + break; + } + case BEHAVIOUR_TYPE_DEPOSIT: { + int32_t money = evaluate(action->expression, player, message); + player->setBankBalance(player->getBankBalance() + money); + break; + } + case BEHAVIOUR_TYPE_GUILDDEPOSIT: { + int32_t money = evaluate(action->expression, player, message); + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + break; + } + + if (money <= 0) { + break; + } + + if (player->getBankBalance() < static_cast(money)) { + break; + } + + if (IOGuild::increaseGuildBankBalance(playerGuild->getId(), money)) { + player->setBankBalance(player->getBankBalance() - money); + } + + break; + } + case BEHAVIOUR_TYPE_TRANSFER: { + int32_t money = evaluate(action->expression, player, message); + uint16_t state = 0; + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + state = IOLoginData::canTransferMoneyToByName(string); + } + else { + state = transferToPlayer->getVocationId() == 0 ? 1 : 2; + } + + if (state != 2) { + break; + } + + player->setBankBalance(player->getBankBalance() - money); + + if (!transferToPlayer) { + IOLoginData::increaseBankBalance(string, money); + } + else { + transferToPlayer->setBankBalance(transferToPlayer->getBankBalance() + money); + } + + break; + } + case BEHAVIOUR_TYPE_BLESS: { + uint8_t number = static_cast(evaluate(action->expression, player, message)) - 1; + + if (!player->hasBlessing(number)) { + player->addBlessing(1 << number); + } + break; + } + case BEHAVIOUR_TYPE_CREATECONTAINER: { + int32_t containerId = evaluate(action->expression, player, message); + int32_t itemId = evaluate(action->expression2, player, message); + int32_t data = evaluate(action->expression3, player, message); + + for (int32_t i = 0; i < std::max(1, amount); i++) { + Item* container = Item::CreateItem(containerId); + if (!container) { + std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create container item" << std::endl; + break; + } + + Container* realContainer = container->getContainer(); + for (int32_t c = 0; c < std::max(1, realContainer->capacity()); c++) { + Item* item = Item::CreateItem(itemId, data == -1 ? 0 : data); + if (!item) { + std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create item" << std::endl; + break; + } + + realContainer->internalAddThing(item); + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, container); + if (ret != RETURNVALUE_NOERROR) { + delete container; + break; + } + } + + break; + } + default: + std::cout << "[Warning - BehaviourDatabase::checkAction]: Unhandled node type " << action->type << std::endl; + break; + } +} + +int32_t BehaviourDatabase::evaluate(NpcBehaviourNode* node, Player* player, const std::string& message) +{ + switch (node->type) { + case BEHAVIOUR_TYPE_NUMBER: + return node->number; + case BEHAVIOUR_TYPE_TOPIC: + return topic; + case BEHAVIOUR_TYPE_PRICE: + return price; + case BEHAVIOUR_TYPE_DATA: + return data; + case BEHAVIOUR_TYPE_ITEM: + return type; + case BEHAVIOUR_TYPE_AMOUNT: + return amount; + case BEHAVIOUR_TYPE_HEALTH: + return player->getHealth(); + case BEHAVIOUR_TYPE_COUNT: { + int32_t itemId = evaluate(node->left, player, message); + const ItemType& itemType = Item::items[itemId]; + if (itemType.stackable || !itemType.hasSubType()) { + data = -1; + } + return player->getItemTypeCount(itemId, data); + } + case BEHAVIOUR_TYPE_EXPERIENCESTAGE: { + int32_t level = evaluate(node->left, player, message); + return g_game.getExperienceStage(level); + } + case BEHAVIOUR_TYPE_COUNTMONEY: + return player->getMoney(); + case BEHAVIOUR_TYPE_BURNING: { + Condition* condition = player->getCondition(CONDITION_FIRE); + if (!condition) { + return false; + } + + ConditionDamage* damage = static_cast(condition); + if (damage == nullptr) { + return false; + } + + return damage->getTotalDamage(); + } + case BEHAVIOUR_TYPE_DRUNK: { + Condition* condition = player->getCondition(CONDITION_DRUNK); + if (!condition) { + return false; + } + + return true; + } + case BEHAVIOUR_TYPE_POISON: { + Condition* condition = player->getCondition(CONDITION_POISON); + if (!condition) { + return false; + } + + ConditionDamage* damage = static_cast(condition); + if (damage == nullptr) { + return false; + } + + return damage->getTotalDamage(); + } + case BEHAVIOUR_TYPE_LEVEL: + return player->getLevel(); + case BEHAVIOUR_TYPE_GUILDLEVEL: { + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + return -1; + } + + return player->getGuildRank()->level; + } + case BEHAVIOUR_TYPE_RANDOM: { + int32_t min = evaluate(node->left, player, message); + int32_t max = evaluate(node->right, player, message); + return uniform_random(min, max); + } + case BEHAVIOUR_TYPE_QUESTVALUE: { + int32_t questNumber = evaluate(node->left, player, message); + int32_t questValue; + player->getStorageValue(questNumber, questValue); + return questValue; + } + case BEHAVIOUR_TYPE_SLOTITEM: { + int32_t slot = evaluate(node->left, player, message); + + Thing* thing = player->getThing(slot); + if (!thing) { + return 0; + } + + Item* item = thing->getItem(); + if (!item) { + return 0; + } + + return item->getID(); + } + case BEHAVIOUR_TYPE_EXPIRINGQUESTVALUE: { + int32_t questNumber = evaluate(node->left, player, message); + int32_t questValue; + player->getStorageValue(questNumber, questValue); + return questValue - OTSYS_TIME_MINUTES(); + } + case BEHAVIOUR_TYPE_MESSAGE_COUNT: { + int32_t value = searchDigit(message); + if (value < node->number) { + return false; + } + return value; + } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < node->number) { + return false; + } + return value; + } + case BEHAVIOUR_TYPE_OPERATION: + return checkOperation(player, node, message); + case BEHAVIOUR_TYPE_BALANCE: + return player->getBankBalance(); + case BEHAVIOUR_TYPE_GUILDBALANCE: { + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + return false; + } + + return IOGuild::getGuildBalance(playerGuild->getId()); + } + case BEHAVIOUR_TYPE_CLIENTVERSION: + return g_game.getClientVersion(); + case BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE: { + std::string lowerMessage = asLowerCaseString(message); + if (lowerMessage.find("to ") != std::string::npos) { + string = asCamelCaseString(message.substr(lowerMessage.find("to ") + 3, message.size())); + } + else { + string = asCamelCaseString(message); + } + + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + return IOLoginData::canTransferMoneyToByName(string); + } + + return transferToPlayer->getVocationId() == 0 ? 1 : 2; + } + case BEHAVIOUR_TYPE_SPELLKNOWN: { + if (player->hasLearnedInstantSpell(string)) { + return true; + } + + break; + } + case BEHAVIOUR_TYPE_SPELLLEVEL: { + InstantSpell* spell = g_spells->getInstantSpellByName(string); + if (!spell) { + std::cout << "[Warning - BehaviourDatabase::evaluate]: SpellLevel unknown spell " << node->string << std::endl; + return std::numeric_limits::max(); + } + + return spell->getLevel(); + } + default: + std::cout << "[Warning - BehaviourDatabase::evaluate]: Unhandled node type " << node->type << std::endl; + break; + } + + return false; +} + +int32_t BehaviourDatabase::checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message) +{ + int32_t leftResult = evaluate(node->left, player, message); + int32_t rightResult = evaluate(node->right, player, message); + switch (node->number) { + case BEHAVIOUR_OPERATOR_LESSER_THAN: + return leftResult < rightResult; + case BEHAVIOUR_OPERATOR_EQUALS: + return leftResult == rightResult; + case BEHAVIOUR_OPERATOR_GREATER_THAN: + return leftResult > rightResult; + case BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS: + return leftResult >= rightResult; + case BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS: + return leftResult <= rightResult; + case BEHAVIOUR_OPERATOR_NOT_EQUALS: + return leftResult != rightResult; + case BEHAVIOUR_OPERATOR_MULTIPLY: + return leftResult * rightResult; + case BEHAVIOUR_OPERATOR_DIVIDE: + return leftResult / rightResult; + case BEHAVIOUR_OPERATOR_SUM: + return leftResult + rightResult; + case BEHAVIOUR_OPERATOR_RES: + return leftResult - rightResult; + default: + break; + } + return false; +} + +int32_t BehaviourDatabase::searchDigit(const std::string& message) +{ + int32_t value = searchDigitNoLimit(message); + + if (value > 500) { + value = 500; + } + + return value; +} + +int32_t BehaviourDatabase::searchDigitNoLimit(const std::string& message) +{ + int32_t start = -1; + int32_t end = -1; + int32_t value = 0; + int32_t i = -1; + + for (char c : message) { + i++; + if (start == -1 && IsDigit(c)) { + start = i; + } + else if (start != -1 && !IsDigit(c)) { + end = i; + break; + } + } + + try { + value = std::stoi(message.substr(start, end).c_str()); + } + catch (std::invalid_argument const&) { + return 0; + } + catch (std::out_of_range const&) { + return 0; + } + + return value; +} + +bool BehaviourDatabase::searchWord(const std::string& pattern, const std::string& message) +{ + if (pattern.empty() || message.empty()) { + return false; + } + + size_t len = pattern.length(); + bool wholeWord = false; + + if (pattern[len - 1] == '$') { + len--; + wholeWord = true; + } + + std::string newPattern = pattern.substr(0, len); + std::string actualMessage = asLowerCaseString(message); + + if (actualMessage.find(newPattern) == std::string::npos) { + return false; + } + + if (wholeWord) { + size_t wordPos = actualMessage.find(newPattern); + size_t wordEnd = wordPos + newPattern.length() - 1; + + if (wordEnd + 1 > actualMessage.length()) { + return false; + } + + if (wordEnd + 1 == actualMessage.length()) { + return true; + } + + if (!isspace(actualMessage[wordEnd + 1])) { + return false; + } + } + + return true; +} + +std::string BehaviourDatabase::parseResponse(Player* player, const std::string& message) +{ + std::string response = message; + replaceString(response, "%A", std::to_string(amount)); + replaceString(response, "%D", std::to_string(data)); + replaceString(response, "%N", player->getName()); + replaceString(response, "%P", std::to_string(price)); + replaceString(response, "%S", string); + + int32_t worldTime = g_game.getLightHour(); + int32_t hours = std::floor(worldTime / 60); + int32_t minutes = worldTime % 60; + + std::stringstream ss; + ss << hours << ":"; + if (minutes < 10) { + ss << '0' << minutes; + } else { + ss << minutes; + } + + replaceString(response, "%T", ss.str()); + return response; +} + +void BehaviourDatabase::attendCustomer(uint32_t playerId) +{ + std::lock_guard lock(mutex); + + reset(); + npc->conversationStartTime = OTSYS_TIME(); + npc->conversationEndTime = OTSYS_TIME() + 60000; + npc->focusCreature = playerId; +} + +void BehaviourDatabase::queueCustomer(uint32_t playerId, const std::string& message) +{ + std::lock_guard lock(mutex); + + for (NpcQueueEntry entry : queueList) { + if (entry.playerId == playerId) { + return; + } + } + + NpcQueueEntry customer; + customer.playerId = playerId; + customer.text = message; + queueList.push_back(customer); +} + +void BehaviourDatabase::idle() +{ + std::lock_guard lock(mutex); + + if (queueList.empty()) { + if (OTSYS_TIME() - npc->conversationStartTime <= 3000) { + npc->staticMovementTime = OTSYS_TIME() + 5000; + } + + npc->focusCreature = 0; + } else { + while (!queueList.empty()) { + NpcQueueEntry nextCustomer = queueList.front(); + queueList.pop_front(); + Player* player = g_game.getPlayerByID(nextCustomer.playerId); + if (!player) { + continue; + } else { + if (!Position::areInRange<3, 3>(player->getPosition(), npc->getPosition())) { + continue; + } + + react(SITUATION_ADDRESS, player, nextCustomer.text); + return; + } + } + + npc->focusCreature = 0; + } +} + +void BehaviourDatabase::reset() +{ + delay = 1000; + for (uint32_t eventId : delayedEvents) { + g_scheduler.stopEvent(eventId); + } + delayedEvents.clear(); +} + +bool NpcBehaviourCondition::setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string & _string) +{ + type = _type; + number = _number; + string = _string; + return false; +} diff --git a/app/SabrehavenServer/src/behaviourdatabase.h b/app/SabrehavenServer/src/behaviourdatabase.h new file mode 100644 index 0000000..bec19a2 --- /dev/null +++ b/app/SabrehavenServer/src/behaviourdatabase.h @@ -0,0 +1,311 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF +#define FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF + +#include "script.h" +#include "enums.h" + +enum BehaviourSituation_t +{ + SITUATION_ADDRESS = 1, + SITUATION_BUSY, + SITUATION_VANISH, + SITUATION_NONE, +}; + +enum NpcBehaviourType_t +{ + BEHAVIOUR_TYPE_NOP = 0, // returns true on conditions + BEHAVIOUR_TYPE_STRING, // match string, or NPC say + BEHAVIOUR_TYPE_NUMBER, // return a number + BEHAVIOUR_TYPE_OPERATION, // <, =, >, >=, <=, <> + BEHAVIOUR_TYPE_MESSAGE_COUNT, // get quantity in player message + BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, // get quantity in player message without any max value restriction + BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE, // set player name parsed fro message to string object and return state if it is possible to transfer + BEHAVIOUR_TYPE_IDLE, // idle npc + BEHAVIOUR_TYPE_QUEUE, // queue talking creature + BEHAVIOUR_TYPE_TOPIC, // get/set topic + BEHAVIOUR_TYPE_PRICE, // get/set price + BEHAVIOUR_TYPE_DATA, // get/set data + BEHAVIOUR_TYPE_ITEM, // get/set item + BEHAVIOUR_TYPE_AMOUNT, // get/set amount + BEHAVIOUR_TYPE_TEXT, // get/set string + BEHAVIOUR_TYPE_HEALTH, // get/set health + BEHAVIOUR_TYPE_COUNT, // count amount of items + BEHAVIOUR_TYPE_CREATEMONEY, // create money + BEHAVIOUR_TYPE_COUNTMONEY, // get player total money + BEHAVIOUR_TYPE_DELETEMONEY, // remove money from player + BEHAVIOUR_TYPE_CREATE, // create item + BEHAVIOUR_TYPE_DELETE, // deletes an item + BEHAVIOUR_TYPE_EFFECTME, // effect on NPC + BEHAVIOUR_TYPE_EFFECTOPP, // effect on player + BEHAVIOUR_TYPE_BURNING, // get/set burning + BEHAVIOUR_TYPE_POISON, // get/set poison + BEHAVIOUR_TYPE_SPELLKNOWN, // check if spell is known + BEHAVIOUR_TYPE_SPELLLEVEL, // get spell level + BEHAVIOUR_TYPE_TEACHSPELL, // player learn spell + BEHAVIOUR_TYPE_LEVEL, // get player level + BEHAVIOUR_TYPE_GUILDLEVEL, // get player guild level + BEHAVIOUR_TYPE_RANDOM, // random value + BEHAVIOUR_TYPE_QUESTVALUE, // get/set quest value + BEHAVIOUR_TYPE_TELEPORT, // teleport player to position + BEHAVIOUR_TYPE_SORCERER, // get/set vocation + BEHAVIOUR_TYPE_DRUID, // get/set vocation + BEHAVIOUR_TYPE_KNIGHT, // get/set vocation + BEHAVIOUR_TYPE_PALADIN, // get/set vocation + BEHAVIOUR_TYPE_ISPREMIUM, // is account premium + BEHAVIOUR_TYPE_ISREALPREMIUM, // is REALLY account premium because many isPremium features are for free players also + BEHAVIOUR_TYPE_PVPENFORCED, // get world type pvpenforced + BEHAVIOUR_TYPE_MALE, // is player male + BEHAVIOUR_TYPE_FEMALE, // is player female + BEHAVIOUR_TYPE_PZLOCKED, // is player pz locked + BEHAVIOUR_TYPE_PROMOTED, // check if player promoted + BEHAVIOUR_TYPE_PROFESSION, // get/set profession + BEHAVIOUR_TYPE_PROMOTE, // promote the player + BEHAVIOUR_TYPE_SUMMON, // summons a monster + BEHAVIOUR_TYPE_EXPERIENCE, // grant experience to a player + BEHAVIOUR_TYPE_BALANCE, // return player balance + BEHAVIOUR_TYPE_GUILDBALANCE, // return guild balance + BEHAVIOUR_TYPE_WITHDRAW, // withdraw from player bank balance + BEHAVIOUR_TYPE_GUILDWITHDRAW, // withdraw from guild bank balance + BEHAVIOUR_TYPE_DEPOSIT, // deposit x amount of gold + BEHAVIOUR_TYPE_GUILDDEPOSIT, // deposit x amount of gold to guild + BEHAVIOUR_TYPE_TRANSFER, // transfer x amount of gold + BEHAVIOUR_TYPE_EXPERIENCESTAGE, // get experience staged based on player level + BEHAVIOUR_TYPE_BLESS, // add blessing to player + BEHAVIOUR_TYPE_CREATECONTAINER, // create a container of an item in particular + BEHAVIOUR_TYPE_TOWN, // change player town + BEHAVIOUR_TYPE_DRUNK, // get/set drunk (set not done) + BEHAVIOUR_TYPE_ADDOUTFITADDON, // Add Outfit Addon + BEHAVIOUR_TYPE_ADDOUTFIT, // Add Outfit + BEHAVIOUR_TYPE_DELETEAMOUNT, // deletes an item according specified amount + BEHAVIOUR_TYPE_EXPIRINGQUESTVALUE, // get/set expiring quest value + BEHAVIOUR_TYPE_SLOTITEM, // get slot item + BEHAVIOUR_TYPE_PZFREE, // is player pz not locked + BEHAVIOUR_TYPE_CLIENTVERSION, // get client version +}; + +enum NpcBehaviourOperator_t +{ + BEHAVIOUR_OPERATOR_LESSER_THAN = '<', + BEHAVIOUR_OPERATOR_EQUALS = '=', + BEHAVIOUR_OPERATOR_GREATER_THAN = '>', + BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS = 'G', + BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS = 'L', + BEHAVIOUR_OPERATOR_NOT_EQUALS = 'N', + BEHAVIOUR_OPERATOR_MULTIPLY = '*', + BEHAVIOUR_OPERATOR_DIVIDE = '/', + BEHAVIOUR_OPERATOR_SUM = '+', + BEHAVIOUR_OPERATOR_RES = '-', +}; + +enum NpcBehaviourParameterSearch_t +{ + BEHAVIOUR_PARAMETER_NONE, + BEHAVIOUR_PARAMETER_ASSIGN, + BEHAVIOUR_PARAMETER_ONE, + BEHAVIOUR_PARAMETER_TWO, + BEHAVIOUR_PARAMETER_THREE, +}; + +class Npc; +class Player; + +struct NpcBehaviourNode +{ + NpcBehaviourType_t type; + int32_t number; + std::string string; + NpcBehaviourNode* left; + NpcBehaviourNode* right; + + NpcBehaviourNode() : type(), number(0), left(nullptr), right(nullptr) { } + ~NpcBehaviourNode() { + delete left; + delete right; + } + + NpcBehaviourNode* clone() { + NpcBehaviourNode* copy = new NpcBehaviourNode(); + copy->type = type; + copy->number = number; + copy->string = string; + if (left) { + copy->left = left->clone(); + } + if (right) { + copy->right = right->clone(); + } + return copy; + } +}; + +struct NpcBehaviourCondition +{ + NpcBehaviourType_t type; + BehaviourSituation_t situation; + std::string string; + int32_t number; + NpcBehaviourNode* expression; + + NpcBehaviourCondition() : type(), situation(SITUATION_NONE), string(), number(0), expression(nullptr) {} + ~NpcBehaviourCondition() { + delete expression; + } + + //non-copyable + NpcBehaviourCondition(const NpcBehaviourCondition&) = delete; + NpcBehaviourCondition& operator=(const NpcBehaviourCondition&) = delete; + + bool setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string& _string); +}; + +struct NpcBehaviourAction +{ + NpcBehaviourType_t type; + std::string string; + int32_t number; + NpcBehaviourNode* expression; + NpcBehaviourNode* expression2; + NpcBehaviourNode* expression3; + + NpcBehaviourAction() : + type(), + string(), + number(0), + expression(nullptr), + expression2(nullptr), + expression3(nullptr) {} + ~NpcBehaviourAction() { + delete expression; + delete expression2; + delete expression3; + } + + NpcBehaviourAction* clone() { + NpcBehaviourAction* copy = new NpcBehaviourAction(); + copy->type = type; + copy->string = string; + copy->number = number; + if (expression) { + copy->expression = expression->clone(); + } + if (expression2) { + copy->expression2 = expression2->clone(); + } + if (expression3) { + copy->expression3 = expression3->clone(); + } + return copy; + } +}; + +struct NpcBehaviour +{ + BehaviourSituation_t situation = SITUATION_NONE; + uint32_t priority = 0; + std::vector conditions; + std::vector actions; + + NpcBehaviour() = default; + ~NpcBehaviour() { + for (auto condition : conditions) { + delete condition; + } + + for (auto action : actions) { + delete action; + } + } + + //non-copyable + NpcBehaviour(const NpcBehaviour&) = delete; + NpcBehaviour& operator=(const NpcBehaviour&) = delete; +}; + +struct NpcQueueEntry +{ + uint32_t playerId; + std::string text; +}; + +class BehaviourDatabase +{ + public: + BehaviourDatabase(Npc* _npc); + ~BehaviourDatabase(); + + // non-copyable + BehaviourDatabase(const BehaviourDatabase&) = delete; + BehaviourDatabase& operator=(const BehaviourDatabase&) = delete; + + bool loadDatabase(ScriptReader& script); + bool loadBehaviour(ScriptReader& script); + bool loadConditions(ScriptReader& script, NpcBehaviour* behaviour); + bool loadActions(ScriptReader& script, NpcBehaviour* behaviour); + NpcBehaviourNode* readValue(ScriptReader& script); + NpcBehaviourNode* readFactor(ScriptReader& script, NpcBehaviourNode* nextNode); + + void react(BehaviourSituation_t situation, Player* player, const std::string& message); + + static bool compareBehaviour(const NpcBehaviour* left, const NpcBehaviour* right) { + return left->priority >= right->priority; + } + + private: + + bool checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message); + void checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message); + + int32_t evaluate(NpcBehaviourNode* node, Player* player, const std::string& message = ""); + + int32_t checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message); + int32_t searchDigit(const std::string& message); + int32_t searchDigitNoLimit(const std::string& message); + bool searchWord(const std::string& pattern, const std::string& message); + + std::string parseResponse(Player* player, const std::string& message); + void attendCustomer(uint32_t playerId); + void queueCustomer(uint32_t playerId, const std::string& message); + void idle(); + void reset(); + + int32_t topic; + int32_t data; + int32_t type; + int32_t price; + int32_t amount; + int32_t delay; + + std::string string; + + Npc* npc = nullptr; + NpcBehaviour* previousBehaviour = nullptr; + NpcBehaviour* priorityBehaviour = nullptr; + + std::list queueList; + std::vector delayedEvents; + std::list behaviourEntries; + std::recursive_mutex mutex; + +}; + +#endif diff --git a/app/SabrehavenServer/src/chat.cpp b/app/SabrehavenServer/src/chat.cpp new file mode 100644 index 0000000..4b7b023 --- /dev/null +++ b/app/SabrehavenServer/src/chat.cpp @@ -0,0 +1,602 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "chat.h" +#include "game.h" +#include "pugicast.h" +#include "scheduler.h" + +extern Chat* g_chat; +extern Game g_game; + +bool PrivateChatChannel::isInvited(uint32_t guid) const +{ + if (guid == getOwner()) { + return true; + } + return invites.find(guid) != invites.end(); +} + +bool PrivateChatChannel::removeInvite(uint32_t guid) +{ + return invites.erase(guid) != 0; +} + +void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer) +{ + auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer); + if (!result.second) { + return; + } + + std::ostringstream ss; + ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel."; + invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + ss.str(std::string()); + ss << invitePlayer.getName() << " has been invited."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + +void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer) +{ + if (!removeInvite(excludePlayer.getGUID())) { + return; + } + + removeUser(excludePlayer); + + std::ostringstream ss; + ss << excludePlayer.getName() << " has been excluded."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + excludePlayer.sendClosePrivate(id); +} + +void PrivateChatChannel::closeChannel() const +{ + for (const auto& it : users) { + it.second->sendClosePrivate(id); + } +} + +bool ChatChannel::addUser(Player& player) +{ + if (users.find(player.getID()) != users.end()) { + return false; + } + + if (!executeOnJoinEvent(player)) { + return false; + } + + users[player.getID()] = &player; + return true; +} + +bool ChatChannel::removeUser(const Player& player) +{ + auto iter = users.find(player.getID()); + if (iter == users.end()) { + return false; + } + + users.erase(iter); + + executeOnLeaveEvent(player); + return true; +} + +bool ChatChannel::hasUser(const Player& player) { + return users.find(player.getID()) != users.end(); +} + +void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const +{ + for (const auto& it : users) { + it.second->sendChannelMessage("", message, type, id); + } +} + +bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text) +{ + if (users.find(fromPlayer.getID()) == users.end()) { + return false; + } + + for (const auto& it : users) { + it.second->sendToChannel(&fromPlayer, type, text, id); + } + return true; +} + +bool ChatChannel::executeCanJoinEvent(const Player& player) +{ + if (canJoinEvent == -1) { + return true; + } + + //canJoin(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(canJoinEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(canJoinEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnJoinEvent(const Player& player) +{ + if (onJoinEvent == -1) { + return true; + } + + //onJoin(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onJoinEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onJoinEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnLeaveEvent(const Player& player) +{ + if (onLeaveEvent == -1) { + return true; + } + + //onLeave(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onLeaveEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onLeaveEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message) +{ + if (onSpeakEvent == -1) { + return true; + } + + //onSpeak(player, type, message) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onSpeakEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onSpeakEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, type); + LuaScriptInterface::pushString(L, message); + + bool result = false; + int size0 = lua_gettop(L); + int ret = scriptInterface->protectedCall(L, 3, 1); + if (ret != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else if (lua_gettop(L) > 0) { + if (lua_isboolean(L, -1)) { + result = LuaScriptInterface::getBoolean(L, -1); + } else if (lua_isnumber(L, -1)) { + result = true; + type = LuaScriptInterface::getNumber(L, -1); + } + lua_pop(L, 1); + } + + if ((lua_gettop(L) + 4) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + scriptInterface->resetScriptEnv(); + return result; +} + +Chat::Chat(): + scriptInterface("Chat Interface"), + dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") +{ + scriptInterface.initState(); +} + +bool Chat::load() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml"); + if (!result) { + printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result); + return false; + } + + std::forward_list removedChannels; + for (auto& channelEntry : normalChannels) { + ChatChannel& channel = channelEntry.second; + channel.onSpeakEvent = -1; + channel.canJoinEvent = -1; + channel.onJoinEvent = -1; + channel.onLeaveEvent = -1; + removedChannels.push_front(channelEntry.first); + } + + for (auto channelNode : doc.child("channels").children()) { + ChatChannel channel(pugi::cast(channelNode.attribute("id").value()), channelNode.attribute("name").as_string()); + channel.publicChannel = channelNode.attribute("public").as_bool(); + + pugi::xml_attribute scriptAttribute = channelNode.attribute("script"); + if (scriptAttribute) { + if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { + channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); + channel.canJoinEvent = scriptInterface.getEvent("canJoin"); + channel.onJoinEvent = scriptInterface.getEvent("onJoin"); + channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); + } else { + std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl; + } + } + + removedChannels.remove(channel.id); + normalChannels[channel.id] = channel; + } + + for (uint16_t channelId : removedChannels) { + normalChannels.erase(channelId); + } + return true; +} + +ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId) +{ + if (getChannel(player, channelId)) { + return nullptr; + } + + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (guild) { + auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); + return &ret.first->second; + } + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (party) { + auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party"))); + return &ret.first->second; + } + break; + } + + case CHANNEL_PRIVATE: { + //only 1 private channel for each premium player + if (getPrivateChannel(player)) { + return nullptr; + } + + //find a free private channel slot + for (uint16_t i = 100; i < 10000; ++i) { + auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel"))); + if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map + auto& newChannel = (*ret.first).second; + newChannel.setOwner(player.getGUID()); + return &newChannel; + } + } + break; + } + + default: + break; + } + return nullptr; +} + +bool Chat::deleteChannel(const Player& player, uint16_t channelId) +{ + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (!guild) { + return false; + } + + auto it = guildChannels.find(guild->getId()); + if (it == guildChannels.end()) { + return false; + } + + guildChannels.erase(it); + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (!party) { + return false; + } + + auto it = partyChannels.find(party); + if (it == partyChannels.end()) { + return false; + } + + partyChannels.erase(it); + break; + } + + default: { + auto it = privateChannels.find(channelId); + if (it == privateChannels.end()) { + return false; + } + + it->second.closeChannel(); + + privateChannels.erase(it); + break; + } + } + return true; +} + +ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (channel && channel->addUser(player)) { + return channel; + } + return nullptr; +} + +bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (!channel || !channel->removeUser(player)) { + return false; + } + + if (channel->getOwner() == player.getGUID()) { + deleteChannel(player, channelId); + } + return true; +} + +void Chat::removeUserFromAllChannels(const Player& player) +{ + for (auto& it : normalChannels) { + it.second.removeUser(player); + } + + for (auto& it : partyChannels) { + it.second.removeUser(player); + } + + for (auto& it : guildChannels) { + it.second.removeUser(player); + } + + auto it = privateChannels.begin(); + while (it != privateChannels.end()) { + PrivateChatChannel* channel = &it->second; + channel->removeInvite(player.getGUID()); + channel->removeUser(player); + if (channel->getOwner() == player.getGUID()) { + channel->closeChannel(); + it = privateChannels.erase(it); + } else { + ++it; + } + } +} + +bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (!channel) { + return false; + } + + if (channelId == CHANNEL_GUILD) { + const GuildRank* rank = player.getGuildRank(); + if (rank && rank->level > 1) { + type = TALKTYPE_CHANNEL_O; + } else if (type != TALKTYPE_CHANNEL_Y) { + type = TALKTYPE_CHANNEL_Y; + } + } else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) { + type = TALKTYPE_CHANNEL_Y; + } + + if (!channel->executeOnSpeakEvent(player, type, text)) { + return false; + } + + return channel->talk(player, type, text); +} + +ChannelList Chat::getChannelList(const Player& player) +{ + ChannelList list; + if (player.getGuild()) { + ChatChannel* channel = getChannel(player, CHANNEL_GUILD); + if (channel) { + list.push_back(channel); + } else { + channel = createChannel(player, CHANNEL_GUILD); + if (channel) { + list.push_back(channel); + } + } + } + + if (player.getParty()) { + ChatChannel* channel = getChannel(player, CHANNEL_PARTY); + if (channel) { + list.push_back(channel); + } else { + channel = createChannel(player, CHANNEL_PARTY); + if (channel) { + list.push_back(channel); + } + } + } + + for (const auto& it : normalChannels) { + ChatChannel* channel = getChannel(player, it.first); + if (channel) { + list.push_back(channel); + } + } + + bool hasPrivate = false; + for (auto& it : privateChannels) { + if (PrivateChatChannel* channel = &it.second) { + uint32_t guid = player.getGUID(); + if (channel->isInvited(guid)) { + list.push_back(channel); + } + + if (channel->getOwner() == guid) { + hasPrivate = true; + } + } + } + + if (!hasPrivate) { + list.push_front(&dummyPrivate); + } + return list; +} + +ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId) +{ + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (guild) { + auto it = guildChannels.find(guild->getId()); + if (it != guildChannels.end()) { + return &it->second; + } + } + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (party) { + auto it = partyChannels.find(party); + if (it != partyChannels.end()) { + return &it->second; + } + } + break; + } + + default: { + auto it = normalChannels.find(channelId); + if (it != normalChannels.end()) { + ChatChannel& channel = it->second; + if (!channel.executeCanJoinEvent(player)) { + return nullptr; + } + return &channel; + } else { + auto it2 = privateChannels.find(channelId); + if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) { + return &it2->second; + } + } + break; + } + } + return nullptr; +} + +ChatChannel* Chat::getGuildChannelById(uint32_t guildId) +{ + auto it = guildChannels.find(guildId); + if (it == guildChannels.end()) { + return nullptr; + } + return &it->second; +} + +ChatChannel* Chat::getChannelById(uint16_t channelId) +{ + auto it = normalChannels.find(channelId); + if (it == normalChannels.end()) { + return nullptr; + } + return &it->second; +} + +PrivateChatChannel* Chat::getPrivateChannel(const Player& player) +{ + for (auto& it : privateChannels) { + if (it.second.getOwner() == player.getGUID()) { + return &it.second; + } + } + return nullptr; +} diff --git a/app/SabrehavenServer/src/chat.h b/app/SabrehavenServer/src/chat.h new file mode 100644 index 0000000..5a07cfc --- /dev/null +++ b/app/SabrehavenServer/src/chat.h @@ -0,0 +1,163 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 +#define FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 + +#include "const.h" +#include "luascript.h" + +class Party; +class Player; + +typedef std::map UsersMap; +typedef std::map InvitedMap; + +class ChatChannel +{ + public: + ChatChannel() = default; + ChatChannel(uint16_t channelId, std::string channelName): + name(std::move(channelName)), + id(channelId) {} + + virtual ~ChatChannel() = default; + + bool addUser(Player& player); + bool removeUser(const Player& player); + bool hasUser(const Player& player); + + bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); + void sendToAll(const std::string& message, SpeakClasses type) const; + + const std::string& getName() const { + return name; + } + uint16_t getId() const { + return id; + } + const UsersMap& getUsers() const { + return users; + } + virtual const InvitedMap* getInvitedUsers() const { + return nullptr; + } + + virtual uint32_t getOwner() const { + return 0; + } + + bool isPublicChannel() const { return publicChannel; } + + bool executeOnJoinEvent(const Player& player); + bool executeCanJoinEvent(const Player& player); + bool executeOnLeaveEvent(const Player& player); + bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message); + + protected: + UsersMap users; + + std::string name; + + int32_t canJoinEvent = -1; + int32_t onJoinEvent = -1; + int32_t onLeaveEvent = -1; + int32_t onSpeakEvent = -1; + + uint16_t id; + bool publicChannel = false; + + friend class Chat; +}; + +class PrivateChatChannel final : public ChatChannel +{ + public: + PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} + + uint32_t getOwner() const final { + return owner; + } + void setOwner(uint32_t owner) { + this->owner = owner; + } + + bool isInvited(uint32_t guid) const; + + void invitePlayer(const Player& player, Player& invitePlayer); + void excludePlayer(const Player& player, Player& excludePlayer); + + bool removeInvite(uint32_t guid); + + void closeChannel() const; + + const InvitedMap* getInvitedUsers() const final { + return &invites; + } + + protected: + InvitedMap invites; + uint32_t owner = 0; +}; + +typedef std::list ChannelList; + +class Chat +{ + public: + Chat(); + + // non-copyable + Chat(const Chat&) = delete; + Chat& operator=(const Chat&) = delete; + + bool load(); + + ChatChannel* createChannel(const Player& player, uint16_t channelId); + bool deleteChannel(const Player& player, uint16_t channelId); + + ChatChannel* addUserToChannel(Player& player, uint16_t channelId); + bool removeUserFromChannel(const Player& player, uint16_t channelId); + void removeUserFromAllChannels(const Player& player); + + bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId); + + ChannelList getChannelList(const Player& player); + + ChatChannel* getChannel(const Player& player, uint16_t channelId); + ChatChannel* getChannelById(uint16_t channelId); + ChatChannel* getGuildChannelById(uint32_t guildId); + PrivateChatChannel* getPrivateChannel(const Player& player); + + LuaScriptInterface* getScriptInterface() { + return &scriptInterface; + } + + private: + std::map normalChannels; + std::map privateChannels; + std::map partyChannels; + std::map guildChannels; + + LuaScriptInterface scriptInterface; + + PrivateChatChannel dummyPrivate; +}; + +#endif diff --git a/app/SabrehavenServer/src/combat.cpp b/app/SabrehavenServer/src/combat.cpp new file mode 100644 index 0000000..5b70314 --- /dev/null +++ b/app/SabrehavenServer/src/combat.cpp @@ -0,0 +1,1956 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "combat.h" + +#include "game.h" +#include "configmanager.h" +#include "monster.h" +#include "events.h" + +extern Game g_game; +extern ConfigManager g_config; +extern Events* g_events; + +CombatDamage Combat::getCombatDamage(Creature* creature) const +{ + CombatDamage damage; + damage.origin = params.origin; + damage.type = params.combatType; + if (formulaType == COMBAT_FORMULA_DAMAGE) { + damage.min = static_cast(mina); + damage.max = static_cast(maxa); + } else if (creature) { + int32_t min, max; + if (creature->getCombatValues(min, max)) { + damage.min = min; + damage.max = max; + } else if (Player* player = creature->getPlayer()) { + if (params.valueCallback) { + params.valueCallback->getMinMaxValues(player, damage, params.useCharges); + } + } + } + return damage; +} + +void Combat::getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list& list) +{ + if (targetPos.z >= MAP_MAX_LAYERS) { + return; + } + + if (area) { + area->getList(centerPos, targetPos, list); + } else { + Tile* tile = g_game.map.getTile(targetPos); + if (!tile) { + tile = new StaticTile(targetPos.x, targetPos.y, targetPos.z); + g_game.map.setTile(targetPos, tile); + } + list.push_front(tile); + } +} + +CombatType_t Combat::ConditionToDamageType(ConditionType_t type) +{ + switch (type) { + case CONDITION_FIRE: + return COMBAT_FIREDAMAGE; + + case CONDITION_ENERGY: + return COMBAT_ENERGYDAMAGE; + + case CONDITION_POISON: + return COMBAT_EARTHDAMAGE; + + case CONDITION_DROWN: + return COMBAT_DROWNDAMAGE; + + default: + break; + } + + return COMBAT_NONE; +} + +ConditionType_t Combat::DamageToConditionType(CombatType_t type) +{ + switch (type) { + case COMBAT_FIREDAMAGE: + return CONDITION_FIRE; + + case COMBAT_ENERGYDAMAGE: + return CONDITION_ENERGY; + + case COMBAT_EARTHDAMAGE: + return CONDITION_POISON; + + case COMBAT_DROWNDAMAGE: + return CONDITION_DROWN; + + default: + return CONDITION_NONE; + } +} + +bool Combat::isPlayerCombat(const Creature* target) +{ + if (target->getPlayer()) { + return true; + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + return true; + } + + return false; +} + +ReturnValue Combat::canTargetCreature(Player* player, Creature* target) +{ + if (player == target) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (!player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + //pz-zone + if (player->getZone() == ZONE_PROTECTION) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; + } + + if (target->getZone() == ZONE_PROTECTION) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + + //nopvp-zone + if (isPlayerCombat(target)) { + if (player->getZone() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (target->getZone() == ZONE_NOPVP) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + } + } + + if (player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { + if (target->getPlayer()) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } else { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + } + + if (target->getPlayer()) { + if (isProtected(player, target->getPlayer())) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (player->hasSecureMode() && !Combat::isInPvpZone(player, target) && player->getSkullClient(target->getPlayer()) == SKULL_NONE) { + return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; + } + } + + return Combat::canDoCombat(player, target); +} + +ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) +{ + if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE) && tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && tile->hasProperty(CONST_PROP_UNLAY)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->getTeleportItem()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (caster) { + const Position& casterPosition = caster->getPosition(); + const Position& tilePosition = tile->getPosition(); + if (casterPosition.z < tilePosition.z) { + return RETURNVALUE_FIRSTGODOWNSTAIRS; + } + else if (casterPosition.z > tilePosition.z) { + return RETURNVALUE_FIRSTGOUPSTAIRS; + } + + if (const Player* player = caster->getPlayer()) { + if (player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + return RETURNVALUE_NOERROR; + } + } + } + + //pz-zone + if (aggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE; + } + + return g_events->eventCreatureOnAreaCombat(caster, tile, aggressive); +} + +bool Combat::isInPvpZone(const Creature* attacker, const Creature* target) +{ + return attacker->getZone() == ZONE_PVP && target->getZone() == ZONE_PVP; +} + +bool Combat::isProtected(const Player* attacker, const Player* target) +{ + uint32_t protectionLevel = g_config.getNumber(ConfigManager::PROTECTION_LEVEL); + if (target->getLevel() < protectionLevel || attacker->getLevel() < protectionLevel) { + return true; + } + + if (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE) { + return true; + } + + return false; +} + +ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) +{ + if (!attacker) { + return g_events->eventCreatureOnTargetCombat(attacker, target); + } + + if (const Player* targetPlayer = target->getPlayer()) { + if (targetPlayer->hasFlag(PlayerFlag_CannotBeAttacked)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (isProtected(attackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + //nopvp-zone + const Tile* targetPlayerTile = targetPlayer->getTile(); + if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } + + if (attacker->isSummon()) { + if (const Player* masterAttackerPlayer = attacker->getMaster()->getPlayer()) { + if (masterAttackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (targetPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (isProtected(masterAttackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + } + } + } + else if (target->getMonster()) { + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + + if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } + } + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attacker->getPlayer() || (attacker->isSummon() && attacker->getMaster()->getPlayer())) { + if (target->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + } + } + } + return g_events->eventCreatureOnTargetCombat(attacker, target); +} + +void Combat::setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb) +{ + this->formulaType = formulaType; + this->mina = mina; + this->minb = minb; + this->maxa = maxa; + this->maxb = maxb; +} + +bool Combat::setParam(CombatParam_t param, uint32_t value) +{ + switch (param) { + case COMBAT_PARAM_TYPE: { + params.combatType = static_cast(value); + return true; + } + + case COMBAT_PARAM_EFFECT: { + params.impactEffect = static_cast(value); + return true; + } + + case COMBAT_PARAM_DISTANCEEFFECT: { + params.distanceEffect = static_cast(value); + return true; + } + + case COMBAT_PARAM_BLOCKARMOR: { + params.blockedByArmor = (value != 0); + return true; + } + + case COMBAT_PARAM_BLOCKSHIELD: { + params.blockedByShield = (value != 0); + return true; + } + + case COMBAT_PARAM_TARGETCASTERORTOPMOST: { + params.targetCasterOrTopMost = (value != 0); + return true; + } + + case COMBAT_PARAM_CREATEITEM: { + params.itemId = value; + return true; + } + + case COMBAT_PARAM_AGGRESSIVE: { + params.aggressive = (value != 0); + return true; + } + + case COMBAT_PARAM_DISPEL: { + params.dispelType = static_cast(value); + return true; + } + + case COMBAT_PARAM_USECHARGES: { + params.useCharges = (value != 0); + return true; + } + + case COMBAT_PARAM_DECREASEDAMAGE: { + params.decreaseDamage = static_cast(value); + return true; + } + + case COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE: { + params.maximumDecreasedDamage = static_cast(value); + return true; + } + } + return false; +} + +bool Combat::setCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACK_PARAM_LEVELMAGICVALUE: { + params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_LEVELMAGIC)); + return true; + } + + case CALLBACK_PARAM_SKILLVALUE: { + params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_SKILL)); + return true; + } + + case CALLBACK_PARAM_TARGETTILE: { + params.tileCallback.reset(new TileCallback()); + return true; + } + + case CALLBACK_PARAM_TARGETCREATURE: { + params.targetCallback.reset(new TargetCallback()); + return true; + } + } + return false; +} + +CallBack* Combat::getCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACK_PARAM_LEVELMAGICVALUE: + case CALLBACK_PARAM_SKILLVALUE: { + return params.valueCallback.get(); + } + + case CALLBACK_PARAM_TARGETTILE: { + return params.tileCallback.get(); + } + + case CALLBACK_PARAM_TARGETCREATURE: { + return params.targetCallback.get(); + } + } + return nullptr; +} + +bool Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +{ + assert(data); + CombatDamage damage = *data; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); + } + + if (damage.value < 0 && caster) { + Player* targetPlayer = target->getPlayer(); + if (targetPlayer && caster->getPlayer()) { + damage.value /= 2; + } + } + + if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { + return false; + } + + if (g_game.combatChangeHealth(caster, target, damage)) { + CombatConditionFunc(caster, target, params, &damage); + CombatDispelFunc(caster, target, params, nullptr); + } + + return true; +} + +bool Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +{ + assert(data); + CombatDamage damage = *data; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); + } + + if (damage.value < 0) { + if (caster && caster->getPlayer() && target->getPlayer()) { + damage.value /= 2; + } + } + + if (g_game.combatChangeMana(caster, target, damage)) { + CombatConditionFunc(caster, target, params, nullptr); + CombatDispelFunc(caster, target, params, nullptr); + } + + return true; +} + +bool Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +{ + if (params.origin == ORIGIN_MELEE && data && data->value == 0) { + return false; + } + + for (const auto& condition : params.conditionList) { + if (caster == target || !target->isImmune(condition->getType())) { + Condition* conditionCopy = condition->clone(); + if (caster) { + conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); + } + + //TODO: infight condition until all aggressive conditions has ended + target->addCombatCondition(conditionCopy); + } + } + + return true; +} + +bool Combat::CombatDispelFunc(Creature*, Creature* target, const CombatParams& params, CombatDamage*) +{ + target->removeCombatCondition(params.dispelType); + + return true; +} + +bool Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage*) +{ + CombatConditionFunc(caster, target, params, nullptr); + CombatDispelFunc(caster, target, params, nullptr); + + return true; +} + +void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params) +{ + if (params.itemId != 0) { + uint16_t itemId = params.itemId; + switch (itemId) { + case ITEM_FIREFIELD_PERSISTENT_FULL: + itemId = ITEM_FIREFIELD_PVP_FULL; + break; + + case ITEM_FIREFIELD_PERSISTENT_MEDIUM: + itemId = ITEM_FIREFIELD_PVP_MEDIUM; + break; + + case ITEM_FIREFIELD_PERSISTENT_SMALL: + itemId = ITEM_FIREFIELD_PVP_SMALL; + break; + + case ITEM_ENERGYFIELD_PERSISTENT: + itemId = ITEM_ENERGYFIELD_PVP; + break; + + case ITEM_POISONFIELD_PERSISTENT: + itemId = ITEM_POISONFIELD_PVP; + break; + + case ITEM_MAGICWALL_PERSISTENT: + itemId = ITEM_MAGICWALL; + break; + + case ITEM_WILDGROWTH_PERSISTENT: + itemId = ITEM_WILDGROWTH; + break; + + default: + break; + } + + if (caster) { + Player* casterPlayer; + if (caster->isSummon()) { + casterPlayer = caster->getMaster()->getPlayer(); + } else { + casterPlayer = caster->getPlayer(); + } + + if (casterPlayer) { + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) { + if (itemId == ITEM_FIREFIELD_PVP_FULL) { + itemId = ITEM_FIREFIELD_NOPVP; + } else if (itemId == ITEM_POISONFIELD_PVP) { + itemId = ITEM_POISONFIELD_NOPVP; + } else if (itemId == ITEM_ENERGYFIELD_PVP) { + itemId = ITEM_ENERGYFIELD_NOPVP; + } + } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || itemId == ITEM_ENERGYFIELD_PVP) { + casterPlayer->addInFightTicks(); + } + } + } + + Item* item = Item::CreateItem(itemId); + if (caster) { + item->setOwner(caster->getID()); + } + + ReturnValue ret = g_game.internalAddItem(tile, item); + if (ret == RETURNVALUE_NOERROR) { + g_game.startDecay(item); + } else { + delete item; + } + } + + if (params.tileCallback) { + params.tileCallback->onTileCombat(caster, tile); + } + + if (params.impactEffect != CONST_ME_NONE) { + Game::addMagicEffect(list, tile->getPosition(), params.impactEffect); + } +} + +void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params) +{ + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), pos, params.distanceEffect); + } +} + +void Combat::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +{ + if (effect != CONST_ANI_NONE) { + g_game.addDistanceEffect(fromPos, toPos, effect); + } +} + +void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data) +{ + std::forward_list tileList; + + if (caster) { + getCombatArea(caster->getPosition(), pos, area, tileList); + } else { + getCombatArea(pos, pos, area, tileList); + } + + SpectatorVec list; + uint32_t maxX = 0; + uint32_t maxY = 0; + + //calculate the max viewable range + for (Tile* tile : tileList) { + const Position& tilePos = tile->getPosition(); + + uint32_t diff = Position::getDistanceX(tilePos, pos); + if (diff > maxX) { + maxX = diff; + } + + diff = Position::getDistanceY(tilePos, pos); + if (diff > maxY) { + maxY = diff; + } + } + + const int32_t rangeX = maxX + Map::maxViewportX; + const int32_t rangeY = maxY + Map::maxViewportY; + g_game.map.getSpectators(list, pos, true, true, rangeX, rangeX, rangeY, rangeY); + + postCombatEffects(caster, pos, params); + + uint16_t decreasedDamage = 0; + const uint16_t maximumDecreasedDamage = params.maximumDecreasedDamage; + + bool firstCreature = true; + + if (params.decreaseDamage && data) { + for (Tile* tile : tileList) { + if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + const Creature* topCreature = tile->getTopCreature(); + for (Creature* creature : *creatures) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == tile) { + if (creature != caster) { + continue; + } + } else if (creature != topCreature) { + continue; + } + } + + if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + if (firstCreature) { + firstCreature = false; + continue; + } + + // only apply to players + if (creature->getPlayer()) { + if (maximumDecreasedDamage && decreasedDamage >= maximumDecreasedDamage) { + break; + } + + decreasedDamage += params.decreaseDamage; + } + } + } + } + } + + // actually decrease total damage output + if (data->value == 0) { + int32_t decreasedMinDamage = std::abs(data->min) * decreasedDamage / 100; + int32_t decreasedMaxDamage = std::abs(data->max) * decreasedDamage / 100; + + if (data->min < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->min += decreasedMinDamage; + data->max += decreasedMaxDamage; + + data->min = std::min(0, data->min); + data->max = std::min(0, data->max); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->min -= decreasedMinDamage; + data->max -= decreasedMaxDamage; + + data->min = std::max(0, data->min); + data->max = std::max(0, data->max); + } + } else { + int32_t decreasedValue = (std::abs(data->value) * decreasedDamage) / 100; + + if (data->value < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->value += decreasedValue; + + data->value = std::min(0, data->value); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->value -= decreasedValue; + + data->value = std::max(0, data->value); + } + } + } + + for (Tile* tile : tileList) { + if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { + continue; + } + + combatTileEffects(list, caster, tile, params); + + if (CreatureVector* creatures = tile->getCreatures()) { + const Creature* topCreature = tile->getTopCreature(); + for (Creature* creature : *creatures) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == tile) { + if (creature != caster) { + continue; + } + } else if (creature != topCreature) { + continue; + } + } + + if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + func(caster, creature, params, data); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, creature); + } + + if (params.targetCasterOrTopMost) { + break; + } + } + } + } + } +} + +void Combat::doCombat(Creature* caster, Creature* target) const +{ + //target combat callback function + if (params.combatType != COMBAT_NONE) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { + doCombatHealth(caster, target, damage, params); + } else { + doCombatMana(caster, target, damage, params); + } + } else { + doCombatDefault(caster, target, params); + } +} + +void Combat::doCombat(Creature* caster, const Position& position) const +{ + //area combat callback function + if (params.combatType != COMBAT_NONE) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { + doCombatHealth(caster, position, area.get(), damage, params); + } else { + doCombatMana(caster, position, area.get(), damage, params); + } + } else { + CombatFunc(caster, position, area.get(), params, CombatNullFunc, nullptr); + } +} + +int32_t Combat::computeDamage(Creature* creature, int32_t strength, int32_t variation) +{ + int32_t damage = strength; + if (variation) { + damage = normal_random(-variation, variation) + strength; + } + + if (creature) { + if (Player* player = creature->getPlayer()) { + int32_t formula = 3 * player->getMagicLevel() + 2 * player->getLevel(); + damage = formula * damage / 100; + } + } + + return damage; +} + +int32_t Combat::getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode) +{ + int32_t damage = attackValue; + + switch (fightMode) { + case FIGHTMODE_ATTACK: + damage += 2 * damage / 10; + break; + case FIGHTMODE_DEFENSE: + damage -= 4 * damage / 10; + break; + default: break; + } + + int32_t formula = (5 * (attackSkill) + 50) * damage; + int32_t randresult = rand() % 100; + int32_t totalDamage = -(ceil(formula * ((rand() % 100 + randresult) / 2) / 10000.)); + return totalDamage; +} + +bool Combat::attack(Creature* attacker, Creature* target) +{ + if (Player* player = attacker->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + if (weapon->getWeaponType() == WEAPON_DISTANCE || weapon->getWeaponType() == WEAPON_WAND) { + return rangeAttack(attacker, target, player->getFightMode()); + } + } + + return closeAttack(attacker, target, player->getFightMode()); + } + + return false; +} + +bool Combat::closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > 1) { + return false; + } + + Item* weapon = nullptr; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (weapon && !Combat::canUseWeapon(player, weapon)) { + return false; + } + } + + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + int32_t defense = target->getDefense(); + + if (OTSYS_TIME() < target->earliestDefendTime) { + defense = 0; + } + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = true; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + int32_t totalDamage = Combat::getTotalDamage(skillValue, attackValue, fightMode); + combatDamage.value = totalDamage; + combatDamage.origin = ORIGIN_MELEE; + + int32_t knightCloseAttackDamageIncreasePercent = g_config.getNumber(ConfigManager::KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT); + if (knightCloseAttackDamageIncreasePercent != -1) { + if (Player* player = attacker->getPlayer()) { + if (player->getVocationId() == 4 || player->getVocationId() == 8) { + combatDamage.value += combatDamage.value * knightCloseAttackDamageIncreasePercent / 100; + } + } + } + + bool hit = Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (Monster* monster = attacker->getMonster()) { + int32_t poison = monster->mType->info.poison; + if (poison) { + int32_t randTest = rand(); + + if (hit || ((-totalDamage > defense) && (randTest == 5 * (randTest / 5)))) { + poison = normal_random(poison / 2, poison); + if (poison) { + ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_POISON, 0, 0)); + condition->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + condition->setParam(CONDITION_PARAM_CYCLE, poison); + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + target->addCombatCondition(condition); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + // skills advancing + if (!player->hasFlag(PlayerFlag_NotGainSkill)) { + if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { + player->addSkillAdvance(static_cast(skill), 1); + } + } + + // weapon + if (weapon) { + if (weapon->getCharges() > 0) { + int32_t charges = weapon->getCharges() - 1; + if (charges <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), charges); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +bool Combat::rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + uint8_t range = 0; + uint8_t hitChance = 0; + uint8_t distanceEffect = 0; + uint8_t specialEffect = 0; + int32_t attackStrength = 0; + int32_t attackVariation = 0; + + Item* weapon = nullptr; + Item* ammunition = nullptr; + + bool moveWeapon = true; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (!weapon) { + return false; + } + + if (!Combat::canUseWeapon(player, weapon)) { + return false; + } + + range = weapon->getShootRange(); + distanceEffect = weapon->getMissileType(); + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + ammunition = player->getAmmunition(); + if (weapon->getAmmoType() != AMMO_NONE) { + if (!ammunition || weapon->getAmmoType() != ammunition->getAmmoType()) { + // redirect to fist fighting + return closeAttack(attacker, target, fightMode); + } + + distanceEffect = ammunition->getMissileType(); + } + } + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > range) { + return false; + } + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = false; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = Combat::getTotalDamage(skillValue, attackValue, fightMode); + combatDamage.origin = ORIGIN_RANGED; + + int32_t paladinRangeAttackDamageIncreasePercent = g_config.getNumber(ConfigManager::PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT); + if (paladinRangeAttackDamageIncreasePercent != -1) { + if (Player* player = attacker->getPlayer()) { + if (player->getVocationId() == 3 || player->getVocationId() == 7) { + combatDamage.value += combatDamage.value * paladinRangeAttackDamageIncreasePercent / 100; + } + } + } + + if (weapon) { + hitChance = 75; // throwables and such + specialEffect = weapon->getWeaponSpecialEffect(); + attackStrength = weapon->getAttackStrength(); + attackVariation = weapon->getAttackVariation(); + if (weapon->getFragility()) { + if (normal_random(0, 99) <= weapon->getFragility()) { + uint16_t count = weapon->getItemCount(); + if (count > 1) { + g_game.transformItem(weapon, weapon->getID(), count - 1); + } else { + g_game.internalRemoveItem(weapon); + } + + moveWeapon = false; + } + } + } + + if (ammunition && weapon->getAmmoType() != AMMO_NONE && weapon->getAmmoType() == ammunition->getAmmoType()) { + hitChance = 90; // bows and crossbows + specialEffect = ammunition->getWeaponSpecialEffect(); + attackStrength = ammunition->getAttackStrength(); + attackVariation = ammunition->getAttackVariation(); + if (normal_random(0, 100) <= ammunition->getFragility()) { + uint16_t count = ammunition->getItemCount(); + if (count > 1) { + g_game.transformItem(ammunition, ammunition->getID(), count - 1); + } else { + g_game.internalRemoveItem(ammunition); + } + } + + moveWeapon = false; + } + + int32_t distance = std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)); + if (distance <= 1) { + distance = 5; + } + + distance *= 15; + + bool hit = false; + + int32_t random = rand(); + if (random % distance <= static_cast(skillValue)) { + hit = random % 100 <= hitChance; + } + + + if (Player* player = attacker->getPlayer()) { + if (player->getAddAttackSkill()) { + switch (player->getLastAttackBlockType()) { + case BLOCK_NONE: { + player->addSkillAdvance(SKILL_DISTANCE, 2); + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + player->addSkillAdvance(SKILL_DISTANCE, 1); + break; + } + + default: break; + } + } + } + + if (specialEffect == 1) { + if (hit) { + const int32_t rounds = ammunition ? ammunition->getAttackStrength() : weapon->getAttackStrength(); + + ConditionDamage* poisonDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + poisonDamage->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + poisonDamage->setParam(CONDITION_PARAM_CYCLE, rounds); + poisonDamage->setParam(CONDITION_PARAM_COUNT, 3); + poisonDamage->setParam(CONDITION_PARAM_MAX_COUNT, 3); + + target->addCombatCondition(poisonDamage); + } + } else if (specialEffect == 2) { + DamageImpact impact; + impact.actor = attacker; + impact.damage.type = COMBAT_PHYSICALDAMAGE; + impact.damage.value = -Combat::computeDamage(attacker, attackStrength, attackVariation); + impact.params.blockedByArmor = true; + impact.params.blockedByShield = false; + circleShapeSpell(attacker, target->getPosition(), 0xFF, 0, 3, &impact, 7); + } + + if (!hit) { + Tile* destTile = target->getTile(); + + if (!Position::areInRange<1, 1, 0>(attacker->getPosition(), target->getPosition())) { + static std::vector> destList{ + { -1, -1 },{ 0, -1 },{ 1, -1 }, + { -1, 0 },{ 0, 0 },{ 1, 0 }, + { -1, 1 },{ 0, 1 },{ 1, 1 } + }; + std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); + + Position destPos = target->getPosition(); + + for (const auto& dir : destList) { + // Blocking tiles or tiles without ground ain't valid targets for spears + Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); + if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { + destTile = tmpTile; + break; + } + } + } + + g_game.addMagicEffect(destTile->getPosition(), CONST_ME_POFF); + g_game.addDistanceEffect(attackerPos, destTile->getPosition(), distanceEffect); + + if (moveWeapon && g_config.getBoolean(ConfigManager::DISTANCE_WEAPONS_DROP_ON_GROUND)) { + g_game.internalMoveItem(weapon->getParent(), destTile, INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + + return true; + } + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (moveWeapon && g_config.getBoolean(ConfigManager::DISTANCE_WEAPONS_DROP_ON_GROUND)) { + g_game.internalMoveItem(weapon->getParent(), target->getTile(), INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + } else if (weapon->getWeaponType() == WEAPON_WAND) { + int32_t variation = normal_random(-weapon->getAttackVariation(), weapon->getAttackVariation()); + + CombatParams combatParams; + combatParams.combatType = weapon->getDamageType(); + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = -(variation + weapon->getAttackStrength()); + combatDamage.origin = ORIGIN_RANGED; + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +void Combat::circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect) +{ + const Position& fromPos = attacker->getPosition(); + if (fromPos.z != toPos.z) { + return; + } + + int32_t distance = std::max(Position::getDistanceX(fromPos, toPos), Position::getDistanceY(fromPos, toPos)); + if (distance > range) { + return; + } + + if (animation && fromPos != toPos) { + g_game.addDistanceEffect(fromPos, toPos, animation); + } + + std::forward_list tiles; + + AreaCombat areaCombat; + areaCombat.setupArea(radius); + + areaCombat.getList(toPos, toPos, tiles); + + for (Tile* tile : tiles) { + if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + for (Creature* creature : *creatures) { + impact->handleCreature(creature); + } + } + + if (effect) { + g_game.addMagicEffect(tile->getPosition(), effect); + } + } +} + +void Combat::getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill) +{ + skill = SKILL_FIST; + + if (Player* player = creature->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + switch (weapon->getWeaponType()) { + case WEAPON_AXE: { + skill = SKILL_AXE; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_SWORD: { + skill = SKILL_SWORD; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_CLUB: { + skill = SKILL_CLUB; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_DISTANCE: { + skill = SKILL_DISTANCE; + attackValue = weapon->getAttack(); + + if (weapon->getAmmoType() != AMMO_NONE) { + Item* ammunition = player->getAmmunition(); + if (ammunition && ammunition->getAmmoType() == weapon->getAmmoType()) { + attackValue += ammunition->getAttack(); + } + } + break; + } + default: + attackValue = 7; + break; + } + + skillValue = player->getSkillLevel(skill); + } else { + attackValue = 7; + skillValue = player->getSkillLevel(skill); + } + } else if (Monster* monster = creature->getMonster()) { + attackValue = monster->mType->info.attack; + skillValue = monster->mType->info.skill; + } +} + +bool Combat::canUseWeapon(Player* player, Item* weapon) +{ + if (player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return true; + } + + if (player->getLevel() < weapon->getMinimumLevel()) { + return false; + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && static_cast(player->getMana()) < weapon->getManaConsumption()) { + return false; + } + + const ItemType& itemType = Item::items[weapon->getID()]; + if (hasBitSet(WIELDINFO_VOCREQ, itemType.wieldInfo)) { + if (!hasBitSet(player->getVocationFlagId(), itemType.vocations)) { + return false; + } + } + + return true; +} + +void Combat::postWeaponEffects(Player* player, Item* weapon) +{ + if (!weapon || player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return; + } + + int32_t manaConsumption = weapon->getManaConsumption(); + if (manaConsumption) { + player->addManaSpent(manaConsumption); + player->changeMana(-manaConsumption); + } +} + +bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + + canCombat = CombatHealthFunc(caster, target, params, &damage); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + } + + return canCombat; +} + +void Combat::doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatHealthFunc, &damage); +} + +void Combat::doCombatMana(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + + CombatManaFunc(caster, target, params, &damage); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + } +} + +void Combat::doCombatMana(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatManaFunc, &damage); +} + +void Combat::doCombatCondition(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatConditionFunc, nullptr); +} + +void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + + CombatConditionFunc(caster, target, params, nullptr); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + } +} + +void Combat::doCombatDispel(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatDispelFunc, nullptr); +} + +void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + CombatDispelFunc(caster, target, params, nullptr); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params) +{ + if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR)) { + SpectatorVec list; + g_game.map.getSpectators(list, target->getPosition(), true, true); + + CombatNullFunc(caster, target, params, nullptr); + combatTileEffects(list, caster, target->getTile(), params); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + /* + if (params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + */ + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +//**********************************************************// + +void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const +{ + //onGetPlayerMinMaxValues(...) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - ValueCallback::getMinMaxValues] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + int parameters = 1; + switch (type) { + case COMBAT_FORMULA_LEVELMAGIC: { + //onGetPlayerMinMaxValues(player, level, maglevel) + lua_pushnumber(L, player->getLevel()); + lua_pushnumber(L, player->getMagicLevel()); + parameters += 2; + break; + } + + case COMBAT_FORMULA_SKILL: { + //onGetPlayerMinMaxValues(player, attackSkill, attackValue, fightMode) + uint32_t attackValue = 7; + uint32_t attackSkill = 0; + uint8_t skill = 0; + + Combat::getAttackValue(player, attackValue, attackSkill, skill); + + Item* weapon = player->getWeapon(); + if (useCharges && weapon) { + const ItemType& itemType = Item::items.getItemType(weapon->getID()); + if (itemType.charges) { + int32_t newCount = std::max(0, weapon->getCharges() - 1); + if (newCount <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), newCount); + } + } + } + + lua_pushnumber(L, attackSkill); + lua_pushnumber(L, attackValue); + lua_pushnumber(L, player->getFightMode()); + parameters += 3; + break; + } + + default: { + std::cout << "ValueCallback::getMinMaxValues - unknown callback type" << std::endl; + scriptInterface->resetScriptEnv(); + return; + } + } + + int size0 = lua_gettop(L); + if (lua_pcall(L, parameters, 2, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else { + damage.min = LuaScriptInterface::getNumber(L, -2); + damage.max = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 2); + } + + if ((lua_gettop(L) + parameters + 1) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + scriptInterface->resetScriptEnv(); +} + +//**********************************************************// + +void TileCallback::onTileCombat(Creature* creature, Tile* tile) const +{ + //onTileCombat(creature, pos) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TileCallback::onTileCombat] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + LuaScriptInterface::pushPosition(L, tile->getPosition()); + + scriptInterface->callFunction(2); +} + +//**********************************************************// + +void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const +{ + //onTargetCombat(creature, target) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TargetCallback::onTargetCombat] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + + if (target) { + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + } else { + lua_pushnil(L); + } + + int size0 = lua_gettop(L); + + if (lua_pcall(L, 2, 0 /*nReturnValues*/, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + + if ((lua_gettop(L) + 2 /*nParams*/ + 1) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + scriptInterface->resetScriptEnv(); +} + +//**********************************************************// + +void AreaCombat::clear() +{ + for (const auto& it : areas) { + delete it.second; + } + areas.clear(); +} + +AreaCombat::AreaCombat(const AreaCombat& rhs) +{ + hasExtArea = rhs.hasExtArea; + for (const auto& it : rhs.areas) { + areas[it.first] = new MatrixArea(*it.second); + } +} + +void AreaCombat::getList(const Position& centerPos, const Position& targetPos, std::forward_list& list) const +{ + const MatrixArea* area = getArea(centerPos, targetPos); + if (!area) { + return; + } + + uint32_t centerY, centerX; + area->getCenter(centerY, centerX); + + Position tmpPos(targetPos.x - centerX, targetPos.y - centerY, targetPos.z); + uint32_t cols = area->getCols(); + for (uint32_t y = 0, rows = area->getRows(); y < rows; ++y) { + for (uint32_t x = 0; x < cols; ++x) { + if (area->getValue(y, x) != 0) { + if (g_game.isSightClear(targetPos, tmpPos, true)) { + Tile* tile = g_game.map.getTile(tmpPos); + if (!tile) { + tile = new StaticTile(tmpPos.x, tmpPos.y, tmpPos.z); + g_game.map.setTile(tmpPos, tile); + } + list.push_front(tile); + } + } + tmpPos.x++; + } + tmpPos.x -= cols; + tmpPos.y++; + } +} + +void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const +{ + uint32_t centerY, centerX; + input->getCenter(centerY, centerX); + + if (op == MATRIXOPERATION_COPY) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + (*output)[y][x] = (*input)[y][x]; + } + } + + output->setCenter(centerY, centerX); + } else if (op == MATRIXOPERATION_MIRROR) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + uint32_t rx = 0; + for (int32_t x = input->getCols(); --x >= 0;) { + (*output)[y][rx++] = (*input)[y][x]; + } + } + + output->setCenter(centerY, (input->getRows() - 1) - centerX); + } else if (op == MATRIXOPERATION_FLIP) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + uint32_t ry = 0; + for (int32_t y = input->getRows(); --y >= 0;) { + (*output)[ry++][x] = (*input)[y][x]; + } + } + + output->setCenter((input->getCols() - 1) - centerY, centerX); + } else { + // rotation + int32_t rotateCenterX = (output->getCols() / 2) - 1; + int32_t rotateCenterY = (output->getRows() / 2) - 1; + int32_t angle; + + switch (op) { + case MATRIXOPERATION_ROTATE90: + angle = 90; + break; + + case MATRIXOPERATION_ROTATE180: + angle = 180; + break; + + case MATRIXOPERATION_ROTATE270: + angle = 270; + break; + + default: + angle = 0; + break; + } + + double angleRad = M_PI * angle / 180.0; + + double a = std::cos(angleRad); + double b = -std::sin(angleRad); + double c = std::sin(angleRad); + double d = std::cos(angleRad); + + const uint32_t rows = input->getRows(); + for (uint32_t x = 0, cols = input->getCols(); x < cols; ++x) { + for (uint32_t y = 0; y < rows; ++y) { + //calculate new coordinates using rotation center + int32_t newX = x - centerX; + int32_t newY = y - centerY; + + //perform rotation + int32_t rotatedX = static_cast(round(newX * a + newY * b)); + int32_t rotatedY = static_cast(round(newX * c + newY * d)); + + //write in the output matrix using rotated coordinates + (*output)[rotatedY + rotateCenterY][rotatedX + rotateCenterX] = (*input)[y][x]; + } + } + + output->setCenter(rotateCenterY, rotateCenterX); + } +} + +MatrixArea* AreaCombat::createArea(const std::list& list, uint32_t rows) +{ + uint32_t cols; + if (rows == 0) { + cols = 0; + } else { + cols = list.size() / rows; + } + + MatrixArea* area = new MatrixArea(rows, cols); + + uint32_t x = 0; + uint32_t y = 0; + + for (uint32_t value : list) { + if (value == 1 || value == 3) { + area->setValue(y, x, true); + } + + if (value == 2 || value == 3) { + area->setCenter(y, x); + } + + ++x; + + if (cols == x) { + x = 0; + ++y; + } + } + return area; +} + +void AreaCombat::setupArea(const std::list& list, uint32_t rows) +{ + MatrixArea* area = createArea(list, rows); + + //NORTH + areas[DIRECTION_NORTH] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //SOUTH + MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, southArea, MATRIXOPERATION_ROTATE180); + areas[DIRECTION_SOUTH] = southArea; + + //EAST + MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); + areas[DIRECTION_EAST] = eastArea; + + //WEST + MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, westArea, MATRIXOPERATION_ROTATE270); + areas[DIRECTION_WEST] = westArea; +} + +void AreaCombat::setupArea(int32_t length, int32_t spread) +{ + std::list list; + + uint32_t rows = length; + int32_t cols = 1; + + if (spread != 0) { + cols = ((length - (length % spread)) / spread) * 2 + 1; + } + + int32_t colSpread = cols; + + for (uint32_t y = 1; y <= rows; ++y) { + int32_t mincol = cols - colSpread + 1; + int32_t maxcol = cols - (cols - colSpread); + + for (int32_t x = 1; x <= cols; ++x) { + if (y == rows && x == ((cols - (cols % 2)) / 2) + 1) { + list.push_back(3); + } else if (x >= mincol && x <= maxcol) { + list.push_back(1); + } else { + list.push_back(0); + } + } + + if (spread > 0 && y % spread == 0) { + --colSpread; + } + } + + setupArea(list, rows); +} + +void AreaCombat::setupArea(int32_t radius) +{ + int32_t area[13][13] = { + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0} + }; + + std::list list; + + for (auto& row : area) { + for (int cell : row) { + if (cell == 1) { + list.push_back(3); + } else if (cell > 0 && cell <= radius) { + list.push_back(1); + } else { + list.push_back(0); + } + } + } + + setupArea(list, 13); +} + +void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) +{ + if (list.empty()) { + return; + } + + hasExtArea = true; + MatrixArea* area = createArea(list, rows); + + //NORTH-WEST + areas[DIRECTION_NORTHWEST] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //NORTH-EAST + MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, neArea, MATRIXOPERATION_MIRROR); + areas[DIRECTION_NORTHEAST] = neArea; + + //SOUTH-WEST + MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, swArea, MATRIXOPERATION_FLIP); + areas[DIRECTION_SOUTHWEST] = swArea; + + //SOUTH-EAST + MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput); + copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); + areas[DIRECTION_SOUTHEAST] = seArea; +} + +//**********************************************************// + +void MagicField::onStepInField(Creature* creature) +{ + //remove magic walls/wild growth + if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || isBlocking()) { + if (!creature->isInGhostMode()) { + g_game.internalRemoveItem(this, 1); + } + + return; + } + + const ItemType& it = items[getID()]; + if (it.conditionDamage) { + Condition* conditionCopy = it.conditionDamage->clone(); + uint32_t ownerId = getOwner(); + if (ownerId) { + bool harmfulField = true; + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + Creature* owner = g_game.getCreatureByID(ownerId); + if (owner) { + if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) { + harmfulField = false; + } + } + } + + Player* targetPlayer = creature->getPlayer(); + if (targetPlayer) { + Player* attackerPlayer = g_game.getPlayerByID(ownerId); + if (attackerPlayer) { + if (Combat::isProtected(attackerPlayer, targetPlayer)) { + harmfulField = false; + } + } + } + + if (!harmfulField || (OTSYS_TIME() - createTime <= 5000) || creature->hasBeenAttacked(ownerId)) { + conditionCopy->setParam(CONDITION_PARAM_OWNER, ownerId); + } + } + + creature->addCondition(conditionCopy); + } +} + +void DamageImpact::handleCreature(Creature* target) +{ + Combat::doCombatHealth(actor, target, damage, params); +} + +void SpeedImpact::handleCreature(Creature* target) +{ + ConditionType_t conditionType = CONDITION_PARALYZE; + if (percent > 0) { + conditionType = CONDITION_HASTE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration)); + condition->setSpeedDelta(percent); + target->addCondition(condition); +} + +void DunkenImpact::handleCreature(Creature* target) +{ + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration); + target->addCondition(condition); +} diff --git a/app/SabrehavenServer/src/combat.h b/app/SabrehavenServer/src/combat.h new file mode 100644 index 0000000..7d69b55 --- /dev/null +++ b/app/SabrehavenServer/src/combat.h @@ -0,0 +1,402 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC +#define FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC + +#include "thing.h" +#include "condition.h" +#include "map.h" +#include "baseevents.h" + +class Condition; +class Creature; +class Item; + +struct Position; + +//for luascript callback +class ValueCallback final : public CallBack +{ + public: + explicit ValueCallback(formulaType_t type): type(type) {} + void getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const; + + protected: + formulaType_t type; +}; + +class TileCallback final : public CallBack +{ + public: + void onTileCombat(Creature* creature, Tile* tile) const; + + protected: + formulaType_t type; +}; + +class TargetCallback final : public CallBack +{ + public: + void onTargetCombat(Creature* creature, Creature* target) const; + + protected: + formulaType_t type; +}; + +struct CombatParams { + std::forward_list> conditionList; + + std::unique_ptr valueCallback; + std::unique_ptr tileCallback; + std::unique_ptr targetCallback; + + uint16_t itemId = 0; + uint16_t decreaseDamage = 0; + uint16_t maximumDecreasedDamage = 0; + + ConditionType_t dispelType = CONDITION_NONE; + CombatType_t combatType = COMBAT_NONE; + CombatOrigin origin = ORIGIN_SPELL; + + uint8_t impactEffect = CONST_ME_NONE; + uint8_t distanceEffect = CONST_ANI_NONE; + + bool blockedByArmor = false; + bool blockedByShield = false; + bool targetCasterOrTopMost = false; + bool aggressive = true; + bool useCharges = false; +}; + +struct Impact +{ + Creature* actor = nullptr; + + virtual void handleCreature(Creature*) { + // + } +}; + +struct DamageImpact : Impact +{ + CombatParams params; + CombatDamage damage; + + void handleCreature(Creature* target) final; +}; + +struct SpeedImpact : Impact +{ + int32_t percent = 0; + int32_t duration = 0; + + void handleCreature(Creature* target) final; +}; + +struct DunkenImpact : Impact +{ + int32_t duration = 0; + + void handleCreature(Creature* target) final; +}; + +typedef bool(*COMBATFUNC)(Creature*, Creature*, const CombatParams&, CombatDamage*); + +class MatrixArea +{ + public: + MatrixArea(uint32_t rows, uint32_t cols): centerX(0), centerY(0), rows(rows), cols(cols) { + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = 0; + } + } + } + + MatrixArea(const MatrixArea& rhs) { + centerX = rhs.centerX; + centerY = rhs.centerY; + rows = rhs.rows; + cols = rhs.cols; + + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = rhs.data_[row][col]; + } + } + } + + ~MatrixArea() { + for (uint32_t row = 0; row < rows; ++row) { + delete[] data_[row]; + } + + delete[] data_; + } + + // non-assignable + MatrixArea& operator=(const MatrixArea&) = delete; + + void setValue(uint32_t row, uint32_t col, bool value) const { + data_[row][col] = value; + } + bool getValue(uint32_t row, uint32_t col) const { + return data_[row][col]; + } + + void setCenter(uint32_t y, uint32_t x) { + centerX = x; + centerY = y; + } + void getCenter(uint32_t& y, uint32_t& x) const { + x = centerX; + y = centerY; + } + + uint32_t getRows() const { + return rows; + } + uint32_t getCols() const { + return cols; + } + + inline const bool* operator[](uint32_t i) const { + return data_[i]; + } + inline bool* operator[](uint32_t i) { + return data_[i]; + } + + protected: + uint32_t centerX; + uint32_t centerY; + + uint32_t rows; + uint32_t cols; + bool** data_; +}; + +class AreaCombat +{ + public: + AreaCombat() = default; + + AreaCombat(const AreaCombat& rhs); + ~AreaCombat() { + clear(); + } + + // non-assignable + AreaCombat& operator=(const AreaCombat&) = delete; + + void getList(const Position& centerPos, const Position& targetPos, std::forward_list& list) const; + + void setupArea(const std::list& list, uint32_t rows); + void setupArea(int32_t length, int32_t spread); + void setupArea(int32_t radius); + void setupExtArea(const std::list& list, uint32_t rows); + void clear(); + + protected: + enum MatrixOperation_t { + MATRIXOPERATION_COPY, + MATRIXOPERATION_MIRROR, + MATRIXOPERATION_FLIP, + MATRIXOPERATION_ROTATE90, + MATRIXOPERATION_ROTATE180, + MATRIXOPERATION_ROTATE270, + }; + + MatrixArea* createArea(const std::list& list, uint32_t rows); + void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const; + + MatrixArea* getArea(const Position& centerPos, const Position& targetPos) const { + int32_t dx = Position::getOffsetX(targetPos, centerPos); + int32_t dy = Position::getOffsetY(targetPos, centerPos); + + Direction dir; + if (dx < 0) { + dir = DIRECTION_WEST; + } else if (dx > 0) { + dir = DIRECTION_EAST; + } else if (dy < 0) { + dir = DIRECTION_NORTH; + } else { + dir = DIRECTION_SOUTH; + } + + if (hasExtArea) { + if (dx < 0 && dy < 0) { + dir = DIRECTION_NORTHWEST; + } else if (dx > 0 && dy < 0) { + dir = DIRECTION_NORTHEAST; + } else if (dx < 0 && dy > 0) { + dir = DIRECTION_SOUTHWEST; + } else if (dx > 0 && dy > 0) { + dir = DIRECTION_SOUTHEAST; + } + } + + auto it = areas.find(dir); + if (it == areas.end()) { + return nullptr; + } + return it->second; + } + + std::map areas; + bool hasExtArea = false; +}; + +class Combat +{ + public: + Combat() = default; + + // non-copyable + Combat(const Combat&) = delete; + Combat& operator=(const Combat&) = delete; + + static int32_t computeDamage(Creature* creature, int32_t strength, int32_t variation); + static int32_t getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode); + + static bool attack(Creature* attacker, Creature* target); + static bool closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode); + static bool rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode); + + static void circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect); + + static void getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill); + + static bool doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + + static void doCombatMana(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doCombatMana(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + + static void doCombatCondition(Creature* caster, Creature* target, const CombatParams& params); + static void doCombatCondition(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params); + + static void doCombatDispel(Creature* caster, Creature* target, const CombatParams& params); + static void doCombatDispel(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params); + + static void getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list& list); + + static bool isInPvpZone(const Creature* attacker, const Creature* target); + static bool isProtected(const Player* attacker, const Player* target); + static bool isPlayerCombat(const Creature* target); + static CombatType_t ConditionToDamageType(ConditionType_t type); + static ConditionType_t DamageToConditionType(CombatType_t type); + static ReturnValue canTargetCreature(Player* attacker, Creature* target); + static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive); + static ReturnValue canDoCombat(Creature* attacker, Creature* target); + static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); + + static void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); + + void doCombat(Creature* caster, Creature* target) const; + void doCombat(Creature* caster, const Position& pos) const; + + bool setCallback(CallBackParam_t key); + CallBack* getCallback(CallBackParam_t key); + + bool setParam(CombatParam_t param, uint32_t value); + void setArea(AreaCombat* area) { + this->area.reset(area); + } + bool hasArea() const { + return area != nullptr; + } + void setCondition(const Condition* condition) { + params.conditionList.emplace_front(condition); + } + void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb); + void postCombatEffects(Creature* caster, const Position& pos) const { + postCombatEffects(caster, pos, params); + } + + void setOrigin(CombatOrigin origin) { + params.origin = origin; + } + + protected: + static bool canUseWeapon(Player* player, Item* weapon); + static void postWeaponEffects(Player* player, Item* weapon); + + static void doCombatDefault(Creature* caster, Creature* target, const CombatParams& params); + + static void CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data); + + static bool CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage); + static bool CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + + static void combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params); + CombatDamage getCombatDamage(Creature* creature) const; + + //configureable + CombatParams params; + + //formula variables + formulaType_t formulaType = COMBAT_FORMULA_UNDEFINED; + double mina = 0.0; + double minb = 0.0; + double maxa = 0.0; + double maxb = 0.0; + + std::unique_ptr area; +}; + +class MagicField final : public Item +{ + public: + explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} + + MagicField* getMagicField() final { + return this; + } + const MagicField* getMagicField() const final { + return this; + } + + bool isReplaceable() const { + return Item::items[getID()].replaceable; + } + CombatType_t getCombatType() const { + const ItemType& it = items[getID()]; + return it.combatType; + } + void onStepInField(Creature* creature); + + private: + int64_t createTime; +}; + +#endif diff --git a/app/SabrehavenServer/src/commands.cpp b/app/SabrehavenServer/src/commands.cpp new file mode 100644 index 0000000..83db35e --- /dev/null +++ b/app/SabrehavenServer/src/commands.cpp @@ -0,0 +1,328 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "commands.h" +#include "player.h" +#include "npc.h" +#include "game.h" +#include "actions.h" +#include "iologindata.h" +#include "configmanager.h" +#include "spells.h" +#include "movement.h" +#include "globalevent.h" +#include "monster.h" +#include "scheduler.h" + +#include "pugicast.h" + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Monsters g_monsters; +extern TalkActions* g_talkActions; +extern MoveEvents* g_moveEvents; +extern Spells* g_spells; +extern Game g_game; +extern CreatureEvents* g_creatureEvents; +extern GlobalEvents* g_globalEvents; +extern Chat* g_chat; +extern LuaEnvironment g_luaEnvironment; + +s_defcommands Commands::defined_commands[] = { + // TODO: move all commands to talkactions + + //admin commands + {"/reload", &Commands::reloadInfo}, + {"/raid", &Commands::forceRaid}, + + // player commands + {"!sellhouse", &Commands::sellHouse} +}; + +Commands::Commands() +{ + // set up command map + for (auto& command : defined_commands) { + commandMap[command.name] = new Command(command.f, 1, ACCOUNT_TYPE_GOD, true); + } +} + +Commands::~Commands() +{ + for (const auto& it : commandMap) { + delete it.second; + } +} + +bool Commands::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/commands.xml"); + if (!result) { + printXMLError("Error - Commands::loadFromXml", "data/XML/commands.xml", result); + return false; + } + + for (auto commandNode : doc.child("commands").children()) { + pugi::xml_attribute cmdAttribute = commandNode.attribute("cmd"); + if (!cmdAttribute) { + std::cout << "[Warning - Commands::loadFromXml] Missing cmd" << std::endl; + continue; + } + + auto it = commandMap.find(cmdAttribute.as_string()); + if (it == commandMap.end()) { + std::cout << "[Warning - Commands::loadFromXml] Unknown command " << cmdAttribute.as_string() << std::endl; + continue; + } + + Command* command = it->second; + + pugi::xml_attribute groupAttribute = commandNode.attribute("group"); + if (groupAttribute) { + command->groupId = pugi::cast(groupAttribute.value()); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing group for command " << it->first << std::endl; + } + + pugi::xml_attribute acctypeAttribute = commandNode.attribute("acctype"); + if (acctypeAttribute) { + command->accountType = static_cast(pugi::cast(acctypeAttribute.value())); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing acctype for command " << it->first << std::endl; + } + + pugi::xml_attribute logAttribute = commandNode.attribute("log"); + if (logAttribute) { + command->log = booleanString(logAttribute.as_string()); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing log for command " << it->first << std::endl; + } + g_game.addCommandTag(it->first.front()); + } + return true; +} + +bool Commands::reload() +{ + for (const auto& it : commandMap) { + Command* command = it.second; + command->groupId = 1; + command->accountType = ACCOUNT_TYPE_GOD; + command->log = true; + } + + g_game.resetCommandTag(); + return loadFromXml(); +} + +bool Commands::exeCommand(Player& player, const std::string& cmd) +{ + std::string str_command; + std::string str_param; + + std::string::size_type loc = cmd.find(' ', 0); + if (loc != std::string::npos) { + str_command = std::string(cmd, 0, loc); + str_param = std::string(cmd, (loc + 1), cmd.size() - loc - 1); + } else { + str_command = cmd; + } + + //find command + auto it = commandMap.find(str_command); + if (it == commandMap.end()) { + return false; + } + + Command* command = it->second; + if (command->groupId > player.getGroup()->id || command->accountType > player.getAccountType()) { + if (player.getGroup()->access) { + player.sendTextMessage(MESSAGE_STATUS_SMALL, "You can not execute this command."); + } + + return false; + } + + //execute command + CommandFunc cfunc = command->f; + (this->*cfunc)(player, str_param); + + if (command->log) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, cmd); + + std::ostringstream logFile; + logFile << "data/logs/" << player.getName() << " commands.log"; + std::ofstream out(logFile.str(), std::ios::app); + if (out.is_open()) { + time_t ticks = time(nullptr); + const tm* now = localtime(&ticks); + char buf[32]; + strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M", now); + + out << '[' << buf << "] " << cmd << std::endl; + out.close(); + } + } + return true; +} + +void Commands::reloadInfo(Player& player, const std::string& param) +{ + std::string tmpParam = asLowerCaseString(param); + if (tmpParam == "action" || tmpParam == "actions") { + g_actions->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded actions."); + } else if (tmpParam == "config" || tmpParam == "configuration") { + g_config.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded config."); + } else if (tmpParam == "command" || tmpParam == "commands") { + reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded commands."); + } else if (tmpParam == "creaturescript" || tmpParam == "creaturescripts") { + g_creatureEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded creature scripts."); + } else if (tmpParam == "monster" || tmpParam == "monsters") { + g_monsters.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded monsters."); + } else if (tmpParam == "move" || tmpParam == "movement" || tmpParam == "movements" + || tmpParam == "moveevents" || tmpParam == "moveevent") { + g_moveEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded movements."); + } else if (tmpParam == "npc" || tmpParam == "npcs") { + Npcs::reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded npcs."); + } else if (tmpParam == "raid" || tmpParam == "raids") { + g_game.raids.reload(); + g_game.raids.startup(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded raids."); + } else if (tmpParam == "spell" || tmpParam == "spells") { + g_spells->reload(); + g_monsters.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded spells."); + } else if (tmpParam == "talk" || tmpParam == "talkaction" || tmpParam == "talkactions") { + g_talkActions->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded talk actions."); + } else if (tmpParam == "items") { + Item::items.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded items."); + } else if (tmpParam == "globalevents" || tmpParam == "globalevent") { + g_globalEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded globalevents."); + } else if (tmpParam == "chat" || tmpParam == "channel" || tmpParam == "chatchannels") { + g_chat->load(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded chatchannels."); + } else if (tmpParam == "global") { + g_luaEnvironment.loadFile("data/global.lua"); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded global.lua."); + } else { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found."); + } + lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); +} + +void Commands::sellHouse(Player& player, const std::string& param) +{ + Player* tradePartner = g_game.getPlayerByName(param); + if (!tradePartner || tradePartner == &player) { + player.sendCancelMessage("Trade player not found."); + return; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player.getPosition())) { + player.sendCancelMessage("Trade player is too far away."); + return; + } + + if (!tradePartner->isPremium()) { + player.sendCancelMessage("Trade player does not have a premium account."); + return; + } + + HouseTile* houseTile = dynamic_cast(player.getTile()); + if (!houseTile) { + player.sendCancelMessage("You must stand in your house to initiate the trade."); + return; + } + + House* house = houseTile->getHouse(); + if (!house || house->getOwner() != player.getGUID()) { + player.sendCancelMessage("You don't own this house."); + return; + } + + if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) { + player.sendCancelMessage("Trade player already owns a house."); + return; + } + + if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) { + player.sendCancelMessage("Trade player is currently the highest bidder of an auctioned house."); + return; + } + + Item* transferItem = house->getTransferItem(); + if (!transferItem) { + player.sendCancelMessage("You can not trade this house."); + return; + } + + transferItem->getParent()->setParent(&player); + + if (!g_game.internalStartTrade(&player, tradePartner, transferItem)) { + house->resetTransferItem(); + } +} + +void Commands::forceRaid(Player& player, const std::string& param) +{ + Raid* raid = g_game.raids.getRaidByName(param); + if (!raid || !raid->isLoaded()) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No such raid exists."); + return; + } + + if (g_game.raids.getRunning()) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Another raid is already being executed."); + return; + } + + g_game.raids.setRunning(raid); + + RaidEvent* event = raid->getNextRaidEvent(); + if (!event) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The raid does not contain any data."); + return; + } + + raid->setState(RAIDSTATE_EXECUTING); + + uint32_t ticks = event->getDelay(); + if (ticks > 0) { + g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, raid, event))); + } else { + g_dispatcher.addTask(createTask(std::bind(&Raid::executeRaidEvent, raid, event))); + } + + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started."); +} diff --git a/app/SabrehavenServer/src/commands.h b/app/SabrehavenServer/src/commands.h new file mode 100644 index 0000000..828ef8a --- /dev/null +++ b/app/SabrehavenServer/src/commands.h @@ -0,0 +1,74 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_COMMANDS_H_C95A575CCADF434699D26CD042690970 +#define FS_COMMANDS_H_C95A575CCADF434699D26CD042690970 + +#include "enums.h" + +class Player; + +struct Command; +struct s_defcommands; + +class Commands +{ + public: + Commands(); + ~Commands(); + + // non-copyable + Commands(const Commands&) = delete; + Commands& operator=(const Commands&) = delete; + + bool loadFromXml(); + bool reload(); + + bool exeCommand(Player& player, const std::string& cmd); + + protected: + //commands + void reloadInfo(Player& player, const std::string& param); + void sellHouse(Player& player, const std::string& param); + void forceRaid(Player& player, const std::string& param); + + //table of commands + static s_defcommands defined_commands[]; + + std::map commandMap; +}; + +typedef void (Commands::*CommandFunc)(Player&, const std::string&); + +struct Command { + Command(CommandFunc f, uint32_t groupId, AccountType_t accountType, bool log) + : f(f), groupId(groupId), accountType(accountType), log(log) {} + + CommandFunc f; + uint32_t groupId; + AccountType_t accountType; + bool log; +}; + +struct s_defcommands { + const char* name; + CommandFunc f; +}; + +#endif diff --git a/app/SabrehavenServer/src/condition.cpp b/app/SabrehavenServer/src/condition.cpp new file mode 100644 index 0000000..bd35633 --- /dev/null +++ b/app/SabrehavenServer/src/condition.cpp @@ -0,0 +1,1336 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "condition.h" +#include "game.h" + +extern Game g_game; + +bool Condition::setParam(ConditionParam_t param, int32_t value) +{ + switch (param) { + case CONDITION_PARAM_TICKS: { + ticks = value; + return true; + } + + case CONDITION_PARAM_SUBID: { + subId = value; + return true; + } + + default: { + return false; + } + } +} + +bool Condition::unserialize(PropStream& propStream) +{ + uint8_t attr_type; + while (propStream.read(attr_type) && attr_type != CONDITIONATTR_END) { + if (!unserializeProp(static_cast(attr_type), propStream)) { + return false; + } + } + return true; +} + +bool Condition::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + switch (attr) { + case CONDITIONATTR_TYPE: { + int32_t value; + if (!propStream.read(value)) { + return false; + } + + conditionType = static_cast(value); + return true; + } + + case CONDITIONATTR_ID: { + int32_t value; + if (!propStream.read(value)) { + return false; + } + + id = static_cast(value); + return true; + } + + case CONDITIONATTR_TICKS: { + return propStream.read(ticks); + } + + case CONDITIONATTR_SUBID: { + return propStream.read(subId); + } + + case CONDITIONATTR_END: + return true; + + default: + return false; + } +} + +void Condition::serialize(PropWriteStream& propWriteStream) +{ + propWriteStream.write(CONDITIONATTR_TYPE); + propWriteStream.write(conditionType); + + propWriteStream.write(CONDITIONATTR_ID); + propWriteStream.write(id); + + propWriteStream.write(CONDITIONATTR_TICKS); + propWriteStream.write(ticks); + + propWriteStream.write(CONDITIONATTR_SUBID); + propWriteStream.write(subId); +} + +void Condition::setTicks(int32_t newTicks) +{ + ticks = newTicks; + endTime = ticks + OTSYS_TIME(); +} + +bool Condition::executeCondition(Creature*, int32_t interval) +{ + if (ticks == -1) { + return true; + } + + //Not using set ticks here since it would reset endTime + ticks = std::max(0, ticks - interval); + return getEndTime() >= OTSYS_TIME(); +} + +Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, uint32_t subId/* = 0*/) +{ + switch (type) { + case CONDITION_POISON: + case CONDITION_FIRE: + case CONDITION_ENERGY: + case CONDITION_DROWN: + return new ConditionDamage(id, type, subId); + + case CONDITION_HASTE: + case CONDITION_PARALYZE: + return new ConditionSpeed(id, type, ticks, subId, param); + + case CONDITION_INVISIBLE: + return new ConditionInvisible(id, type, ticks, subId); + + case CONDITION_OUTFIT: + return new ConditionOutfit(id, type, ticks, subId); + + case CONDITION_LIGHT: + return new ConditionLight(id, type, ticks, subId, param & 0xFF, (param & 0xFF00) >> 8); + + case CONDITION_REGENERATION: + return new ConditionRegeneration(id, type, ticks, subId); + + case CONDITION_SOUL: + return new ConditionSoul(id, type, ticks, subId); + + case CONDITION_ATTRIBUTES: + return new ConditionAttributes(id, type, ticks, subId); + + case CONDITION_INFIGHT: + case CONDITION_DRUNK: + case CONDITION_EXHAUST: + case CONDITION_MUTED: + case CONDITION_CHANNELMUTEDTICKS: + case CONDITION_YELLTICKS: + case CONDITION_PACIFIED: + case CONDITION_MANASHIELD: + case CONDITION_AGGRESSIVE: + return new ConditionGeneric(id, type, ticks, subId); + + default: + return nullptr; + } +} + +Condition* Condition::createCondition(PropStream& propStream) +{ + uint8_t attr; + if (!propStream.read(attr) || attr != CONDITIONATTR_TYPE) { + return nullptr; + } + + uint32_t type; + if (!propStream.read(type)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_ID) { + return nullptr; + } + + uint32_t id; + if (!propStream.read(id)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_TICKS) { + return nullptr; + } + + uint32_t ticks; + if (!propStream.read(ticks)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_SUBID) { + return nullptr; + } + + uint32_t subId; + if (!propStream.read(subId)) { + return nullptr; + } + + return createCondition(static_cast(id), static_cast(type), ticks, 0, subId); +} + +bool Condition::startCondition(Creature*) +{ + if (ticks > 0) { + endTime = ticks + OTSYS_TIME(); + } + return true; +} + +bool Condition::isPersistent() const +{ + if (ticks == -1) { + return false; + } + + if (!(id == CONDITIONID_DEFAULT || id == CONDITIONID_COMBAT || conditionType == CONDITION_MUTED)) { + return false; + } + + return true; +} + +uint32_t Condition::getIcons() const +{ + return 0; +} + +bool Condition::updateCondition(const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return false; + } + + if (ticks == -1 && addCondition->getTicks() > 0) { + return false; + } + + if (addCondition->getTicks() >= 0 && getEndTime() > (OTSYS_TIME() + addCondition->getTicks())) { + return false; + } + + return true; +} + +bool ConditionGeneric::startCondition(Creature* creature) +{ + return Condition::startCondition(creature); +} + +bool ConditionGeneric::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionGeneric::endCondition(Creature*) +{ + // +} + +void ConditionGeneric::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + } +} + +uint32_t ConditionGeneric::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + + switch (conditionType) { + case CONDITION_MANASHIELD: + icons |= ICON_MANASHIELD; + break; + + case CONDITION_INFIGHT: + icons |= ICON_SWORDS; + break; + + case CONDITION_DRUNK: + icons |= ICON_DRUNK; + break; + + default: + break; + } + + return icons; +} + +void ConditionAttributes::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionAttributes& conditionAttrs = static_cast(*addCondition); + //Remove the old condition + endCondition(creature); + + //Apply the new one + memcpy(skills, conditionAttrs.skills, sizeof(skills)); + memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); + memcpy(stats, conditionAttrs.stats, sizeof(stats)); + memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + } +} + +bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SKILLS) { + return propStream.read(skills[currentSkill++]); + } else if (attr == CONDITIONATTR_STATS) { + return propStream.read(stats[currentStat++]); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionAttributes::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + propWriteStream.write(CONDITIONATTR_SKILLS); + propWriteStream.write(skills[i]); + } + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + propWriteStream.write(CONDITIONATTR_STATS); + propWriteStream.write(stats[i]); + } +} + +bool ConditionAttributes::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + + return true; +} + +void ConditionAttributes::updatePercentStats(Player* player) +{ + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (statsPercent[i] == 0) { + continue; + } + + switch (i) { + case STAT_MAXHITPOINTS: + stats[i] = static_cast(player->getMaxHealth() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAXMANAPOINTS: + stats[i] = static_cast(player->getMaxMana() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAGICPOINTS: + stats[i] = static_cast(player->getMagicLevel() * ((statsPercent[i] - 100) / 100.f)); + break; + } + } +} + +void ConditionAttributes::updateStats(Player* player) +{ + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats(static_cast(i), stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } +} + +void ConditionAttributes::updatePercentSkills(Player* player) +{ + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skillsPercent[i] == 0) { + continue; + } + + int32_t unmodifiedSkill = player->getBaseSkill(i); + skills[i] = static_cast(unmodifiedSkill * ((skillsPercent[i] - 100) / 100.f)); + } +} + +void ConditionAttributes::updateSkills(Player* player) +{ + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } +} + +bool ConditionAttributes::executeCondition(Creature* creature, int32_t interval) +{ + return ConditionGeneric::executeCondition(creature, interval); +} + +void ConditionAttributes::endCondition(Creature* creature) +{ + Player* player = creature->getPlayer(); + if (player) { + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i] || skillsPercent[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), -skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats(static_cast(i), -stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + } +} + +bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_SKILL_MELEE: { + skills[SKILL_CLUB] = value; + skills[SKILL_AXE] = value; + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_MELEEPERCENT: { + skillsPercent[SKILL_CLUB] = value; + skillsPercent[SKILL_AXE] = value; + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FIST: { + skills[SKILL_FIST] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISTPERCENT: { + skillsPercent[SKILL_FIST] = value; + return true; + } + + case CONDITION_PARAM_SKILL_CLUB: { + skills[SKILL_CLUB] = value; + return true; + } + + case CONDITION_PARAM_SKILL_CLUBPERCENT: { + skillsPercent[SKILL_CLUB] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SWORD: { + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SWORDPERCENT: { + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_AXE: { + skills[SKILL_AXE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_AXEPERCENT: { + skillsPercent[SKILL_AXE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_DISTANCE: { + skills[SKILL_DISTANCE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_DISTANCEPERCENT: { + skillsPercent[SKILL_DISTANCE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SHIELD: { + skills[SKILL_SHIELD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SHIELDPERCENT: { + skillsPercent[SKILL_SHIELD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISHING: { + skills[SKILL_FISHING] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISHINGPERCENT: { + skillsPercent[SKILL_FISHING] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXHITPOINTS: { + stats[STAT_MAXHITPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXMANAPOINTS: { + stats[STAT_MAXMANAPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAGICPOINTS: { + stats[STAT_MAGICPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT: { + statsPercent[STAT_MAXHITPOINTS] = std::max(0, value); + return true; + } + + case CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT: { + statsPercent[STAT_MAXMANAPOINTS] = std::max(0, value); + return true; + } + + case CONDITION_PARAM_STAT_MAGICPOINTSPERCENT: { + statsPercent[STAT_MAGICPOINTS] = std::max(0, value); + return true; + } + + default: + return ret; + } +} + +void ConditionRegeneration::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionRegeneration& conditionRegen = static_cast(*addCondition); + + healthTicks = conditionRegen.healthTicks; + manaTicks = conditionRegen.manaTicks; + + healthGain = conditionRegen.healthGain; + manaGain = conditionRegen.manaGain; + } +} + +bool ConditionRegeneration::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_HEALTHTICKS) { + return propStream.read(healthTicks); + } else if (attr == CONDITIONATTR_HEALTHGAIN) { + return propStream.read(healthGain); + } else if (attr == CONDITIONATTR_MANATICKS) { + return propStream.read(manaTicks); + } else if (attr == CONDITIONATTR_MANAGAIN) { + return propStream.read(manaGain); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionRegeneration::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_HEALTHTICKS); + propWriteStream.write(healthTicks); + + propWriteStream.write(CONDITIONATTR_HEALTHGAIN); + propWriteStream.write(healthGain); + + propWriteStream.write(CONDITIONATTR_MANATICKS); + propWriteStream.write(manaTicks); + + propWriteStream.write(CONDITIONATTR_MANAGAIN); + propWriteStream.write(manaGain); +} + +bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interval) +{ + internalHealthTicks += interval; + internalManaTicks += interval; + + if (creature->getZone() != ZONE_PROTECTION) { + if (internalHealthTicks >= healthTicks) { + internalHealthTicks = 0; + + creature->changeHealth(healthGain); + } + + if (internalManaTicks >= manaTicks) { + internalManaTicks = 0; + if (Player* player = creature->getPlayer()) { + player->changeMana(manaGain); + } + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_HEALTHGAIN: + healthGain = value; + return true; + + case CONDITION_PARAM_HEALTHTICKS: + healthTicks = value; + return true; + + case CONDITION_PARAM_MANAGAIN: + manaGain = value; + return true; + + case CONDITION_PARAM_MANATICKS: + manaTicks = value; + return true; + + default: + return ret; + } +} + +void ConditionSoul::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionSoul& conditionSoul = static_cast(*addCondition); + + soulTicks = conditionSoul.soulTicks; + soulGain = conditionSoul.soulGain; + } +} + +bool ConditionSoul::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SOULGAIN) { + return propStream.read(soulGain); + } else if (attr == CONDITIONATTR_SOULTICKS) { + return propStream.read(soulTicks); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionSoul::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_SOULGAIN); + propWriteStream.write(soulGain); + + propWriteStream.write(CONDITIONATTR_SOULTICKS); + propWriteStream.write(soulTicks); +} + +bool ConditionSoul::executeCondition(Creature* creature, int32_t interval) +{ + internalSoulTicks += interval; + + if (Player* player = creature->getPlayer()) { + if (player->getZone() != ZONE_PROTECTION) { + if (internalSoulTicks >= soulTicks) { + internalSoulTicks = 0; + player->changeSoul(soulGain); + } + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + switch (param) { + case CONDITION_PARAM_SOULGAIN: + soulGain = value; + return true; + + case CONDITION_PARAM_SOULTICKS: + soulTicks = value; + return true; + + default: + return ret; + } +} + +bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) +{ + switch (param) { + case CONDITION_PARAM_OWNER: + owner = value; + return true; + + case CONDITION_PARAM_CYCLE: + cycle = value; + return true; + + case CONDITION_PARAM_COUNT: + count = value; + return true; + + case CONDITION_PARAM_MAX_COUNT: + max_count = value; + return true; + + case CONDITION_PARAM_HIT_DAMAGE: + hit_damage = value; + return true; + + default: + return false; + } +} + +bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_OWNER) { + return propStream.skip(4); + } else if (attr == CONDITIONATTR_CYCLE) { + if (!propStream.read(cycle)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_COUNT) { + if (!propStream.read(count)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_MAX_COUNT) { + if (!propStream.read(max_count)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_FACTOR_PERCENT) { + if (!propStream.read(factor_percent)) { + return false; + } + + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +void ConditionDamage::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_CYCLE); + propWriteStream.write(cycle); + + propWriteStream.write(CONDITIONATTR_COUNT); + propWriteStream.write(count); + + propWriteStream.write(CONDITIONATTR_MAX_COUNT); + propWriteStream.write(max_count); + + propWriteStream.write(CONDITIONATTR_FACTOR_PERCENT); + propWriteStream.write(factor_percent); +} + +bool ConditionDamage::updateCondition(const Condition* addCondition) +{ + const ConditionDamage& conditionDamage = static_cast(*addCondition); + return conditionDamage.getTotalDamage() >= getTotalDamage(); +} + +bool ConditionDamage::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + creature->onAttacked(); + + setParam(CONDITION_PARAM_TICKINTERVAL, 1000); + + if (factor_percent == -1) { + factor_percent = 50; + } + + if (factor_percent <= 9) { + factor_percent = 10; + } + + if (factor_percent >= 1001) { + factor_percent = 1000; + } + + if (hit_damage) { + doDamage(creature, -hit_damage); + } + + return true; +} + +bool ConditionDamage::executeCondition(Creature* creature, int32_t) +{ + if (conditionType == CONDITION_FIRE) { + if (creature->isImmune(CONDITION_FIRE)) { + return false; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -10); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_POISON) { + if (creature->isImmune(CONDITION_POISON)) { + return false; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + int32_t f = factor_percent * r_cycle / 1000; + if (!f) { + f = 2 * (r_cycle > 0) - 1; + } + + cycle = r_cycle - f; + doDamage(creature, -f); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_ENERGY) { + if (creature->isImmune(CONDITION_ENERGY)) { + return false; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -25); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_DROWN) { + if (isFirstCycle && count - max_count == -2) { + doDamage(creature, -20); + isFirstCycle = false; + count = max_count; + return true; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -20); + } + else { + --count; + } + } + else { + return false; + } + } + + return true; +} + +bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) +{ + if (creature->isSuppress(getType())) { + return true; + } + + CombatDamage damage; + damage.origin = ORIGIN_CONDITION; + damage.value = healthChange; + damage.type = Combat::ConditionToDamageType(conditionType); + + Creature* attacker = g_game.getCreatureByID(owner); + + if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature) != RETURNVALUE_NOERROR) { + if (!creature->isInGhostMode()) { + g_game.addMagicEffect(creature->getPosition(), CONST_ME_POFF); + } + return false; + } + + if (g_game.combatBlockHit(damage, attacker, creature, false, false, true)) { + return false; + } + return g_game.combatChangeHealth(attacker, creature, damage); +} + +void ConditionDamage::endCondition(Creature*) +{ + // +} + +void ConditionDamage::addCondition(Creature* creature, const Condition* addCondition) +{ + if (addCondition->getType() != conditionType) { + return; + } + + const ConditionDamage& conditionDamage = static_cast(*addCondition); + + if (hit_damage) { + doDamage(creature, -conditionDamage.hit_damage); + } + + if (!updateCondition(addCondition)) { + return; + } + + owner = conditionDamage.owner; + cycle = conditionDamage.cycle; + count = conditionDamage.count; + max_count = conditionDamage.max_count; +} + +int32_t ConditionDamage::getTotalDamage() const +{ + return cycle; +} + +uint32_t ConditionDamage::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + switch (conditionType) { + case CONDITION_FIRE: + icons |= ICON_BURN; + break; + + case CONDITION_ENERGY: + icons |= ICON_ENERGY; + break; + + case CONDITION_POISON: + icons |= ICON_POISON; + break; + + case CONDITION_DROWN: + icons |= ICON_DROWNING; + break; + + default: + break; + } + return icons; +} + +bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SPEEDDELTA) { + return propStream.read(speedDelta); + } else if (attr == CONDITIONATTR_APPLIEDSPEEDDELTA) { + return propStream.read(appliedSpeedDelta); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionSpeed::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_SPEEDDELTA); + propWriteStream.write(speedDelta); + + propWriteStream.write(CONDITIONATTR_APPLIEDSPEEDDELTA); + propWriteStream.write(appliedSpeedDelta); +} + +bool ConditionSpeed::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (appliedSpeedDelta == 0) { + speedDelta = normal_random(-variation, variation) + speedDelta; + + if (speedDelta >= -100) { + speedDelta = static_cast(creature->getBaseSpeed()) * speedDelta / 100; + } else { + speedDelta = -20 - creature->getBaseSpeed(); + } + + appliedSpeedDelta = speedDelta; + } else { + speedDelta = appliedSpeedDelta; + } + + g_game.changeSpeed(creature, speedDelta); + return true; +} + +bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionSpeed::endCondition(Creature* creature) +{ + g_game.changeSpeed(creature, -appliedSpeedDelta); +} + +void ConditionSpeed::addCondition(Creature* creature, const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return; + } + + if (ticks == -1 && addCondition->getTicks() > 0) { + return; + } + + const ConditionSpeed& conditionSpeed = static_cast(*addCondition); + + int32_t newVariation = conditionSpeed.variation; + int32_t newSpeedDelta = conditionSpeed.speedDelta; + + newSpeedDelta = normal_random(-newVariation, newVariation) + newSpeedDelta; + + // update ticks + setTicks(addCondition->getTicks()); + + if (newSpeedDelta >= -100) { + newSpeedDelta = static_cast(creature->getBaseSpeed()) * newSpeedDelta / 100; + } else { + newSpeedDelta = -20 - creature->getBaseSpeed(); + } + + creature->setSpeed(-appliedSpeedDelta); + + appliedSpeedDelta = newSpeedDelta; + speedDelta = newSpeedDelta; + + g_game.changeSpeed(creature, newSpeedDelta); +} + +uint32_t ConditionSpeed::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + switch (conditionType) { + case CONDITION_HASTE: + icons |= ICON_HASTE; + break; + + case CONDITION_PARALYZE: + icons |= ICON_PARALYZE; + break; + + default: + break; + } + return icons; +} + +bool ConditionInvisible::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + g_game.internalCreatureChangeVisible(creature, false); + return true; +} + +void ConditionInvisible::endCondition(Creature* creature) +{ + if (!creature->isInvisible()) { + g_game.internalCreatureChangeVisible(creature, true); + } +} + +void ConditionOutfit::setOutfit(const Outfit_t& outfit) +{ + this->outfit = outfit; +} + +bool ConditionOutfit::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_OUTFIT) { + return propStream.read(outfit); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionOutfit::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_OUTFIT); + propWriteStream.write(outfit); +} + +bool ConditionOutfit::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + g_game.internalCreatureChangeOutfit(creature, outfit); + return true; +} + +bool ConditionOutfit::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionOutfit::endCondition(Creature* creature) +{ + g_game.internalCreatureChangeOutfit(creature, creature->getDefaultOutfit()); +} + +void ConditionOutfit::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionOutfit& conditionOutfit = static_cast(*addCondition); + outfit = conditionOutfit.outfit; + + g_game.internalCreatureChangeOutfit(creature, outfit); + } +} + +bool ConditionLight::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + internalLightTicks = 0; + lightChangeInterval = ticks / lightInfo.level; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + return true; +} + +bool ConditionLight::executeCondition(Creature* creature, int32_t interval) +{ + internalLightTicks += interval; + + if (internalLightTicks >= lightChangeInterval) { + internalLightTicks = 0; + LightInfo lightInfo = creature->getCreatureLight(); + + if (lightInfo.level > 0) { + --lightInfo.level; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + } + } + + return Condition::executeCondition(creature, interval); +} + +void ConditionLight::endCondition(Creature* creature) +{ + creature->setNormalCreatureLight(); + g_game.changeLight(creature); +} + +void ConditionLight::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionLight& conditionLight = static_cast(*addCondition); + lightInfo.level = conditionLight.lightInfo.level; + lightInfo.color = conditionLight.lightInfo.color; + lightChangeInterval = ticks / lightInfo.level; + internalLightTicks = 0; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + } +} + +bool ConditionLight::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + if (ret) { + return false; + } + + switch (param) { + case CONDITION_PARAM_LIGHT_LEVEL: + lightInfo.level = value; + return true; + + case CONDITION_PARAM_LIGHT_COLOR: + lightInfo.color = value; + return true; + + default: + return false; + } +} + +bool ConditionLight::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_LIGHTCOLOR) { + uint32_t value; + if (!propStream.read(value)) { + return false; + } + + lightInfo.color = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTLEVEL) { + uint32_t value; + if (!propStream.read(value)) { + return false; + } + + lightInfo.level = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTTICKS) { + return propStream.read(internalLightTicks); + } else if (attr == CONDITIONATTR_LIGHTINTERVAL) { + return propStream.read(lightChangeInterval); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionLight::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + // TODO: color and level could be serialized as 8-bit if we can retain backwards + // compatibility, but perhaps we should keep it like this in case they increase + // in the future... + propWriteStream.write(CONDITIONATTR_LIGHTCOLOR); + propWriteStream.write(lightInfo.color); + + propWriteStream.write(CONDITIONATTR_LIGHTLEVEL); + propWriteStream.write(lightInfo.level); + + propWriteStream.write(CONDITIONATTR_LIGHTTICKS); + propWriteStream.write(internalLightTicks); + + propWriteStream.write(CONDITIONATTR_LIGHTINTERVAL); + propWriteStream.write(lightChangeInterval); +} diff --git a/app/SabrehavenServer/src/condition.h b/app/SabrehavenServer/src/condition.h new file mode 100644 index 0000000..ed33877 --- /dev/null +++ b/app/SabrehavenServer/src/condition.h @@ -0,0 +1,379 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 +#define FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 + +#include "fileloader.h" +#include "enums.h" + +class Creature; +class Player; +class PropStream; + +enum ConditionAttr_t { + CONDITIONATTR_TYPE = 1, + CONDITIONATTR_ID, + CONDITIONATTR_TICKS, + CONDITIONATTR_HEALTHTICKS, + CONDITIONATTR_HEALTHGAIN, + CONDITIONATTR_MANATICKS, + CONDITIONATTR_MANAGAIN, + CONDITIONATTR_OWNER, + CONDITIONATTR_CYCLE, + CONDITIONATTR_COUNT, + CONDITIONATTR_MAX_COUNT, + CONDITIONATTR_FACTOR_PERCENT, + CONDITIONATTR_SPEEDDELTA, + CONDITIONATTR_APPLIEDSPEEDDELTA, + CONDITIONATTR_FORMULA_MINA, + CONDITIONATTR_FORMULA_MINB, + CONDITIONATTR_FORMULA_MAXA, + CONDITIONATTR_FORMULA_MAXB, + CONDITIONATTR_LIGHTCOLOR, + CONDITIONATTR_LIGHTLEVEL, + CONDITIONATTR_LIGHTTICKS, + CONDITIONATTR_LIGHTINTERVAL, + CONDITIONATTR_SOULTICKS, + CONDITIONATTR_SOULGAIN, + CONDITIONATTR_SKILLS, + CONDITIONATTR_STATS, + CONDITIONATTR_OUTFIT, + CONDITIONATTR_SUBID, + + //reserved for serialization + CONDITIONATTR_END = 254, +}; + +struct IntervalInfo { + int32_t timeLeft; + int32_t value; + int32_t interval; +}; + +class Condition +{ + public: + Condition() = default; + Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + endTime(ticks == -1 ? std::numeric_limits::max() : 0), + subId(subId), ticks(ticks), conditionType(type), id(id) {} + virtual ~Condition() = default; + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature) = 0; + virtual void addCondition(Creature* creature, const Condition* condition) = 0; + virtual uint32_t getIcons() const; + ConditionId_t getId() const { + return id; + } + uint32_t getSubId() const { + return subId; + } + + virtual Condition* clone() const = 0; + + ConditionType_t getType() const { + return conditionType; + } + int64_t getEndTime() const { + return endTime; + } + int32_t getTicks() const { + return ticks; + } + void setTicks(int32_t newTicks); + + static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, uint32_t subId = 0); + static Condition* createCondition(PropStream& propStream); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + //serialization + bool unserialize(PropStream& propStream); + virtual void serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + bool isPersistent() const; + + protected: + int64_t endTime; + uint32_t subId; + int32_t ticks; + ConditionType_t conditionType; + ConditionId_t id; + + virtual bool updateCondition(const Condition* addCondition); +}; + +class ConditionGeneric : public Condition +{ + public: + ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0): + Condition(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + uint32_t getIcons() const override; + + ConditionGeneric* clone() const override { + return new ConditionGeneric(*this); + } +}; + +class ConditionAttributes final : public ConditionGeneric +{ + public: + ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionAttributes* clone() const final { + return new ConditionAttributes(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + int32_t skills[SKILL_LAST + 1] = {}; + int32_t skillsPercent[SKILL_LAST + 1] = {}; + int32_t stats[STAT_LAST + 1] = {}; + int32_t statsPercent[STAT_LAST + 1] = {}; + int32_t currentSkill = 0; + int32_t currentStat = 0; + + void updatePercentStats(Player* player); + void updateStats(Player* player); + void updatePercentSkills(Player* player); + void updateSkills(Player* player); +}; + +class ConditionRegeneration final : public ConditionGeneric +{ + public: + ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0): + ConditionGeneric(id, type, ticks, subId) {} + + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionRegeneration* clone() const final { + return new ConditionRegeneration(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + uint32_t internalHealthTicks = 0; + uint32_t internalManaTicks = 0; + + uint32_t healthTicks = 1000; + uint32_t manaTicks = 1000; + uint32_t healthGain = 0; + uint32_t manaGain = 0; +}; + +class ConditionSoul final : public ConditionGeneric +{ + public: + ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionSoul* clone() const final { + return new ConditionSoul(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + uint32_t internalSoulTicks = 0; + uint32_t soulTicks = 0; + uint32_t soulGain = 0; +}; + +class ConditionInvisible final : public ConditionGeneric +{ + public: + ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + void endCondition(Creature* creature) final; + + ConditionInvisible* clone() const final { + return new ConditionInvisible(*this); + } +}; + +class ConditionDamage final : public Condition +{ + public: + ConditionDamage() = default; + ConditionDamage(ConditionId_t id, ConditionType_t type, uint32_t subId = 0) : + Condition(id, type, 0, subId) { + if (type == CONDITION_POISON) { + count = max_count = 3; + } else if (type == CONDITION_FIRE) { + count = max_count = 8; + } else if (type == CONDITION_ENERGY) { + count = max_count = 10; + } else if (type == CONDITION_DROWN) { + count = max_count = 3; + } + } + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + uint32_t getIcons() const final; + + ConditionDamage* clone() const final { + return new ConditionDamage(*this); + } + + bool setParam(ConditionParam_t param, int32_t value) final; + + int32_t getTotalDamage() const; + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + protected: + int32_t cycle = 0; + int32_t count = 0; + int32_t max_count = 0; + int32_t factor_percent = -1; + int32_t hit_damage = 0; + bool isFirstCycle = true; + uint32_t owner = 0; + + bool doDamage(Creature* creature, int32_t healthChange); + + bool updateCondition(const Condition* addCondition) final; +}; + +class ConditionSpeed final : public Condition +{ + public: + ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, int32_t changeSpeed) : + Condition(id, type, ticks, subId), speedDelta(changeSpeed) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + uint32_t getIcons() const final; + + ConditionSpeed* clone() const final { + return new ConditionSpeed(*this); + } + + void setVariation(int32_t newVariation) { + variation = newVariation; + } + void setSpeedDelta(int32_t newSpeedDelta) { + speedDelta = newSpeedDelta; + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + int32_t appliedSpeedDelta = 0; + int32_t speedDelta = 0; + int32_t variation = 0; +}; + +class ConditionOutfit final : public Condition +{ + public: + ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + Condition(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + + ConditionOutfit* clone() const final { + return new ConditionOutfit(*this); + } + + void setOutfit(const Outfit_t& outfit); + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + Outfit_t outfit; +}; + +class ConditionLight final : public Condition +{ + public: + ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor) : + Condition(id, type, ticks, subId), lightInfo(lightlevel, lightcolor) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* addCondition) final; + + ConditionLight* clone() const final { + return new ConditionLight(*this); + } + + bool setParam(ConditionParam_t param, int32_t value) final; + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + LightInfo lightInfo; + uint32_t internalLightTicks = 0; + uint32_t lightChangeInterval = 0; +}; + +#endif diff --git a/app/SabrehavenServer/src/configmanager.cpp b/app/SabrehavenServer/src/configmanager.cpp new file mode 100644 index 0000000..224185c --- /dev/null +++ b/app/SabrehavenServer/src/configmanager.cpp @@ -0,0 +1,218 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "game.h" + +#if LUA_VERSION_NUM >= 502 +#undef lua_strlen +#define lua_strlen lua_rawlen +#endif + +extern Game g_game; + +bool ConfigManager::load() +{ + lua_State* L = luaL_newstate(); + if (!L) { + throw std::runtime_error("Failed to allocate memory"); + } + + luaL_openlibs(L); + + if (luaL_dofile(L, "config.lua")) { + std::cout << "[Error - ConfigManager::load] " << lua_tostring(L, -1) << std::endl; + lua_close(L); + return false; + } + + //parse config + if (!loaded) { //info that must be loaded one time (unless we reset the modules involved) + boolean[BIND_ONLY_GLOBAL_ADDRESS] = getGlobalBoolean(L, "bindOnlyGlobalAddress", false); + boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true); + + string[IP] = getGlobalString(L, "ip", "127.0.0.1"); + string[MAP_NAME] = getGlobalString(L, "mapName", "forgotten"); + string[MAP_AUTHOR] = getGlobalString(L, "mapAuthor", "Unknown"); + string[HOUSE_RENT_PERIOD] = getGlobalString(L, "houseRentPeriod", "never"); + string[MYSQL_HOST] = getGlobalString(L, "mysqlHost", "127.0.0.1"); + string[MYSQL_USER] = getGlobalString(L, "mysqlUser", "forgottenserver"); + string[MYSQL_PASS] = getGlobalString(L, "mysqlPass", ""); + string[MYSQL_DB] = getGlobalString(L, "mysqlDatabase", "forgottenserver"); + string[MYSQL_SOCK] = getGlobalString(L, "mysqlSock", ""); + + integer[SQL_PORT] = getGlobalNumber(L, "mysqlPort", 3306); + integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172); + integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171); + integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171); + } + + boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true); + boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true); + boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", true); + boolean[AIMBOT_HOTKEY_ENABLED] = getGlobalBoolean(L, "hotkeyAimbotEnabled", true); + boolean[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true); + boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false); + boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false); + boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true); + boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false); + boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true); + boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true); + boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true); + boolean[TELEPORT_NEWBIES] = getGlobalBoolean(L, "teleportNewbies", true); + boolean[STACK_CUMULATIVES] = getGlobalBoolean(L, "autoStackCumulatives", false); + boolean[BLOCK_HEIGHT] = getGlobalBoolean(L, "blockHeight", false); + boolean[UH_TRAP] = getGlobalBoolean(L, "uhTrap", false); + boolean[ROPE_SPOT_BLOCK] = getGlobalBoolean(L, "ropeSpotBlock", false); + boolean[DROP_ITEMS] = getGlobalBoolean(L, "dropItems", false); + boolean[DISTANCE_WEAPONS_DROP_ON_GROUND] = getGlobalBoolean(L, "distanceWeaponsDropOnGround", true); + + string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); + string[SERVER_NAME] = getGlobalString(L, "serverName", ""); + string[OWNER_NAME] = getGlobalString(L, "ownerName", ""); + string[OWNER_EMAIL] = getGlobalString(L, "ownerEmail", ""); + string[URL] = getGlobalString(L, "url", ""); + string[LOCATION] = getGlobalString(L, "location", ""); + string[MOTD] = getGlobalString(L, "motd", ""); + string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp"); + + integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers"); + integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000); + integer[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2); + integer[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50); + integer[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExp", 5); + integer[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 3); + integer[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 2); + integer[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 3); + integer[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1); + integer[MIN_RATE_SPAWN] = getGlobalNumber(L, "minRateSpawn", 100); + integer[MAX_RATE_SPAWN] = getGlobalNumber(L, "maxRateSpawn", 200); + integer[BAN_LENGTH] = getGlobalNumber(L, "banLength", 30 * 24 * 60 * 60); + integer[ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenActions", 200); + integer[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenExActions", 1000); + integer[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4); + integer[KICK_AFTER_MINUTES] = getGlobalNumber(L, "kickIdlePlayerAfterMinutes", 15); + integer[PROTECTION_LEVEL] = getGlobalNumber(L, "protectionLevel", 1); + integer[DEATH_LOSE_PERCENT] = getGlobalNumber(L, "deathLosePercent", -1); + integer[KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT] = getGlobalNumber(L, "knightCloseAttackDamageIncreasePercent", -1); + integer[PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT] = getGlobalNumber(L, "paladinRangeAttackDamageIncreasePercent", -1); + integer[CORPSE_OWNER_ENABLED] = getGlobalBoolean(L, "corpseOwnerEnabled", true); + integer[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 5000); + integer[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60); + integer[RED_SKULL_TIME] = getGlobalNumber(L, "redSkullTime", 30 * 24 * 60 * 60); + integer[KILLS_DAY_RED_SKULL] = getGlobalNumber(L, "killsDayRedSkull", 3); + integer[KILLS_WEEK_RED_SKULL] = getGlobalNumber(L, "killsWeekRedSkull", 5); + integer[KILLS_MONTH_RED_SKULL] = getGlobalNumber(L, "killsMonthRedSkull", 10); + integer[KILLS_DAY_BANISHMENT] = getGlobalNumber(L, "killsDayBanishment", 5); + integer[KILLS_WEEK_BANISHMENT] = getGlobalNumber(L, "killsWeekBanishment", 8); + integer[KILLS_MONTH_BANISHMENT] = getGlobalNumber(L, "killsMonthBanishment", 10); + integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000); + integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75); + integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); + integer[NEWBIE_TOWN] = getGlobalNumber(L, "newbieTownId", 1); + integer[NEWBIE_LEVEL_THRESHOLD] = getGlobalNumber(L, "newbieLevelThreshold", 5); + integer[MONEY_RATE] = getGlobalNumber(L, "moneyRate", 1); + integer[CLIENT_VERSION] = getGlobalNumber(L, "clientVersion"); + + loaded = true; + lua_close(L); + return true; +} + +bool ConfigManager::reload() +{ + bool result = load(); + if (transformToSHA1(getString(ConfigManager::MOTD)) != g_game.getMotdHash()) { + g_game.incrementMotdNum(); + } + return result; +} + +const std::string& ConfigManager::getString(string_config_t what) const +{ + if (what >= LAST_STRING_CONFIG) { + std::cout << "[Warning - ConfigManager::getString] Accessing invalid index: " << what << std::endl; + return string[DUMMY_STR]; + } + return string[what]; +} + +int32_t ConfigManager::getNumber(integer_config_t what) const +{ + if (what >= LAST_INTEGER_CONFIG) { + std::cout << "[Warning - ConfigManager::getNumber] Accessing invalid index: " << what << std::endl; + return 0; + } + return integer[what]; +} + +bool ConfigManager::getBoolean(boolean_config_t what) const +{ + if (what >= LAST_BOOLEAN_CONFIG) { + std::cout << "[Warning - ConfigManager::getBoolean] Accessing invalid index: " << what << std::endl; + return false; + } + return boolean[what]; +} + +std::string ConfigManager::getGlobalString(lua_State* L, const char* identifier, const char* defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isstring(L, -1)) { + return defaultValue; + } + + size_t len = lua_strlen(L, -1); + std::string ret(lua_tostring(L, -1), len); + lua_pop(L, 1); + return ret; +} + +int32_t ConfigManager::getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isnumber(L, -1)) { + return defaultValue; + } + + int32_t val = lua_tonumber(L, -1); + lua_pop(L, 1); + return val; +} + +bool ConfigManager::getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isboolean(L, -1)) { + if (!lua_isstring(L, -1)) { + return defaultValue; + } + + size_t len = lua_strlen(L, -1); + std::string ret(lua_tostring(L, -1), len); + lua_pop(L, 1); + return booleanString(ret); + } + + int val = lua_toboolean(L, -1); + lua_pop(L, 1); + return val != 0; +} diff --git a/app/SabrehavenServer/src/configmanager.h b/app/SabrehavenServer/src/configmanager.h new file mode 100644 index 0000000..beb3442 --- /dev/null +++ b/app/SabrehavenServer/src/configmanager.h @@ -0,0 +1,145 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 +#define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 + +#if __has_include("luajit/lua.hpp") +#include +#else +#include +#endif + +class ConfigManager +{ + public: + enum boolean_config_t { + SHOW_MONSTER_LOOT, + ALLOW_CHANGEOUTFIT, + ONE_PLAYER_ON_ACCOUNT, + AIMBOT_HOTKEY_ENABLED, + REMOVE_RUNE_CHARGES, + EXPERIENCE_FROM_PLAYERS, + FREE_PREMIUM, + REPLACE_KICK_ON_LOGIN, + ALLOW_CLONES, + BIND_ONLY_GLOBAL_ADDRESS, + OPTIMIZE_DATABASE, + STAMINA_SYSTEM, + WARN_UNSAFE_SCRIPTS, + CONVERT_UNSAFE_SCRIPTS, + TELEPORT_NEWBIES, + STACK_CUMULATIVES, + BLOCK_HEIGHT, + UH_TRAP, + DROP_ITEMS, + DISTANCE_WEAPONS_DROP_ON_GROUND, + CORPSE_OWNER_ENABLED, + ROPE_SPOT_BLOCK, + LAST_BOOLEAN_CONFIG /* this must be the last one */ + }; + + enum string_config_t { + DUMMY_STR, + MAP_NAME, + HOUSE_RENT_PERIOD, + SERVER_NAME, + OWNER_NAME, + OWNER_EMAIL, + URL, + LOCATION, + IP, + MOTD, + WORLD_TYPE, + MYSQL_HOST, + MYSQL_USER, + MYSQL_PASS, + MYSQL_DB, + MYSQL_SOCK, + DEFAULT_PRIORITY, + MAP_AUTHOR, + + LAST_STRING_CONFIG /* this must be the last one */ + }; + + enum integer_config_t { + SQL_PORT, + MAX_PLAYERS, + PZ_LOCKED, + DEFAULT_DESPAWNRANGE, + DEFAULT_DESPAWNRADIUS, + RATE_EXPERIENCE, + RATE_SKILL, + RATE_LOOT, + RATE_MAGIC, + RATE_SPAWN, + MIN_RATE_SPAWN, + MAX_RATE_SPAWN, + BAN_LENGTH, + MAX_MESSAGEBUFFER, + ACTIONS_DELAY_INTERVAL, + EX_ACTIONS_DELAY_INTERVAL, + KICK_AFTER_MINUTES, + PROTECTION_LEVEL, + DEATH_LOSE_PERCENT, + KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT, + PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT, + STATUSQUERY_TIMEOUT, + WHITE_SKULL_TIME, + RED_SKULL_TIME, + KILLS_DAY_RED_SKULL, + KILLS_WEEK_RED_SKULL, + KILLS_MONTH_RED_SKULL, + KILLS_DAY_BANISHMENT, + KILLS_WEEK_BANISHMENT, + KILLS_MONTH_BANISHMENT, + GAME_PORT, + LOGIN_PORT, + STATUS_PORT, + STAIRHOP_DELAY, + EXP_FROM_PLAYERS_LEVEL_RANGE, + MAX_PACKETS_PER_SECOND, + NEWBIE_TOWN, + NEWBIE_LEVEL_THRESHOLD, + MONEY_RATE, + CLIENT_VERSION, + + LAST_INTEGER_CONFIG /* this must be the last one */ + }; + + bool load(); + bool reload(); + + const std::string& getString(string_config_t what) const; + int32_t getNumber(integer_config_t what) const; + bool getBoolean(boolean_config_t what) const; + + private: + static std::string getGlobalString(lua_State* L, const char* identifier, const char* defaultValue); + static int32_t getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue = 0); + static bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue); + + std::string string[LAST_STRING_CONFIG] = {}; + int32_t integer[LAST_INTEGER_CONFIG] = {}; + bool boolean[LAST_BOOLEAN_CONFIG] = {}; + + bool loaded = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/connection.cpp b/app/SabrehavenServer/src/connection.cpp new file mode 100644 index 0000000..dce33eb --- /dev/null +++ b/app/SabrehavenServer/src/connection.cpp @@ -0,0 +1,304 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "connection.h" +#include "outputmessage.h" +#include "protocol.h" +#include "scheduler.h" +#include "server.h" + +extern ConfigManager g_config; + +Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort) +{ + std::lock_guard lockClass(connectionManagerLock); + + auto connection = std::make_shared(io_service, servicePort); + connections.insert(connection); + return connection; +} + +void ConnectionManager::releaseConnection(const Connection_ptr& connection) +{ + std::lock_guard lockClass(connectionManagerLock); + + connections.erase(connection); +} + +void ConnectionManager::closeAll() +{ + std::lock_guard lockClass(connectionManagerLock); + + for (const auto& connection : connections) { + try { + boost::system::error_code error; + connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + connection->socket.close(error); + } catch (boost::system::system_error&) { + } + } + connections.clear(); +} + +// Connection + +void Connection::close(bool force) +{ + //any thread + ConnectionManager::getInstance().releaseConnection(shared_from_this()); + + std::lock_guard lockClass(connectionLock); + if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + connectionState = CONNECTION_STATE_CLOSED; + + if (protocol) { + g_dispatcher.addTask( + createTask(std::bind(&Protocol::release, protocol))); + } + + if (messageQueue.empty() || force) { + closeSocket(); + } else { + //will be closed by the destructor or onWriteOperation + } +} + +void Connection::closeSocket() +{ + if (socket.is_open()) { + try { + readTimer.cancel(); + writeTimer.cancel(); + boost::system::error_code error; + socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + socket.close(error); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl; + } + } +} + +Connection::~Connection() +{ + closeSocket(); +} + +void Connection::accept(Protocol_ptr protocol) +{ + this->protocol = protocol; + g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol))); + + accept(); +} + +void Connection::accept() +{ + std::lock_guard lockClass(connectionLock); + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + + // Read size of the first packet + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::accept] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +void Connection::parseHeader(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + readTimer.cancel(); + + if (error) { + close(FORCE_CLOSE); + return; + } else if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); + if ((++packetsSent / timePassed) > static_cast(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) { + std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl; + close(); + return; + } + + if (timePassed > 2) { + timeConnected = time(nullptr); + packetsSent = 0; + } + + uint16_t size = msg.getLengthHeader(); + if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) { + close(FORCE_CLOSE); + return; + } + + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + // Read packet content + msg.setLength(size + NetworkMessage::HEADER_LENGTH); + boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size), + std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +void Connection::parsePacket(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + readTimer.cancel(); + + if (error) { + close(FORCE_CLOSE); + return; + } + else if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + if (!receivedFirst) { + // First message received + receivedFirst = true; + + if (!protocol) { + // Game protocol has already been created at this point + protocol = service_port->make_protocol(msg, shared_from_this()); + if (!protocol) { + close(FORCE_CLOSE); + return; + } + } + else { + msg.skipBytes(1); // Skip protocol ID + } + + protocol->onRecvFirstMessage(msg); + } + else { + protocol->onRecvMessage(msg); // Send the packet to the current protocol + } + + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + // Wait to the next packet + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } + catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + + + +void Connection::send(const OutputMessage_ptr& msg) +{ + std::lock_guard lockClass(connectionLock); + if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + bool noPendingWrite = messageQueue.empty(); + messageQueue.emplace_back(msg); + if (noPendingWrite) { + internalSend(msg); + } +} + +void Connection::internalSend(const OutputMessage_ptr& msg) +{ + protocol->onSendMessage(msg); + try { + writeTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_WRITE_TIMEOUT)); + writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + boost::asio::async_write(socket, + boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), + std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +uint32_t Connection::getIP() +{ + std::lock_guard lockClass(connectionLock); + + // IP-address is expressed in network byte order + boost::system::error_code error; + const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); + if (error) { + return 0; + } + + return htonl(endpoint.address().to_v4().to_ulong()); +} + +void Connection::onWriteOperation(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + writeTimer.cancel(); + messageQueue.pop_front(); + + if (error) { + messageQueue.clear(); + close(FORCE_CLOSE); + return; + } + + if (!messageQueue.empty()) { + internalSend(messageQueue.front()); + } else if (connectionState == CONNECTION_STATE_CLOSED) { + closeSocket(); + } +} + +void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error) +{ + if (error == boost::asio::error::operation_aborted) { + //The timer has been manually cancelled + return; + } + + if (auto connection = connectionWeak.lock()) { + connection->close(FORCE_CLOSE); + } +} diff --git a/app/SabrehavenServer/src/connection.h b/app/SabrehavenServer/src/connection.h new file mode 100644 index 0000000..b885749 --- /dev/null +++ b/app/SabrehavenServer/src/connection.h @@ -0,0 +1,137 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 +#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 + +#include + +#include "networkmessage.h" + +static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; +static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; + +class Protocol; +typedef std::shared_ptr Protocol_ptr; +class OutputMessage; +typedef std::shared_ptr OutputMessage_ptr; +class Connection; +typedef std::shared_ptr Connection_ptr; +typedef std::weak_ptr ConnectionWeak_ptr; +class ServiceBase; +typedef std::shared_ptr Service_ptr; +class ServicePort; +typedef std::shared_ptr ServicePort_ptr; +typedef std::shared_ptr ConstServicePort_ptr; + +class ConnectionManager +{ + public: + static ConnectionManager& getInstance() { + static ConnectionManager instance; + return instance; + } + + Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort); + void releaseConnection(const Connection_ptr& connection); + void closeAll(); + + protected: + ConnectionManager() = default; + + std::unordered_set connections; + std::mutex connectionManagerLock; +}; + +class Connection : public std::enable_shared_from_this +{ + public: + // non-copyable + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; + + enum ConnectionState_t { + CONNECTION_STATE_OPEN, + CONNECTION_STATE_CLOSED, + }; + + enum { FORCE_CLOSE = true }; + + Connection(boost::asio::io_service& io_service, + ConstServicePort_ptr service_port) : + readTimer(io_service), + writeTimer(io_service), + service_port(std::move(service_port)), + socket(io_service) { + connectionState = CONNECTION_STATE_OPEN; + receivedFirst = false; + packetsSent = 0; + timeConnected = time(nullptr); + } + ~Connection(); + + friend class ConnectionManager; + + void close(bool force = false); + // Used by protocols that require server to send first + void accept(Protocol_ptr protocol); + void accept(); + + void send(const OutputMessage_ptr& msg); + + uint32_t getIP(); + + private: + void parseHeader(const boost::system::error_code& error); + void parsePacket(const boost::system::error_code& error); + + void onWriteOperation(const boost::system::error_code& error); + + static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error); + + void closeSocket(); + void internalSend(const OutputMessage_ptr& msg); + + boost::asio::ip::tcp::socket& getSocket() { + return socket; + } + friend class ServicePort; + + NetworkMessage msg; + + boost::asio::deadline_timer readTimer; + boost::asio::deadline_timer writeTimer; + + std::recursive_mutex connectionLock; + + std::list messageQueue; + + ConstServicePort_ptr service_port; + Protocol_ptr protocol; + + boost::asio::ip::tcp::socket socket; + + time_t timeConnected; + uint32_t packetsSent; + + bool connectionState; + bool receivedFirst; +}; + +#endif diff --git a/app/SabrehavenServer/src/const.h b/app/SabrehavenServer/src/const.h new file mode 100644 index 0000000..58aa0de --- /dev/null +++ b/app/SabrehavenServer/src/const.h @@ -0,0 +1,390 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B +#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B + +static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590; + +enum MagicEffectClasses : uint8_t { + CONST_ME_NONE, + + CONST_ME_DRAWBLOOD = 1, + CONST_ME_LOSEENERGY = 2, + CONST_ME_POFF = 3, + CONST_ME_BLOCKHIT = 4, + CONST_ME_EXPLOSIONAREA = 5, + CONST_ME_EXPLOSIONHIT = 6, + CONST_ME_FIREAREA = 7, + CONST_ME_YELLOW_RINGS = 8, + CONST_ME_GREEN_RINGS = 9, + CONST_ME_HITAREA = 10, + CONST_ME_TELEPORT = 11, + CONST_ME_ENERGYHIT = 12, + CONST_ME_MAGIC_BLUE = 13, + CONST_ME_MAGIC_RED = 14, + CONST_ME_MAGIC_GREEN = 15, + CONST_ME_HITBYFIRE = 16, + CONST_ME_HITBYPOISON = 17, + CONST_ME_MORTAREA = 18, + CONST_ME_SOUND_GREEN = 19, + CONST_ME_SOUND_RED = 20, + CONST_ME_POISONAREA = 21, + CONST_ME_SOUND_YELLOW = 22, + CONST_ME_SOUND_PURPLE = 23, + CONST_ME_SOUND_BLUE = 24, + CONST_ME_SOUND_WHITE = 25, + CONST_ME_BUBBLES = 26, + CONST_ME_CRAPS = 27, + CONST_ME_GIFT_WRAPS = 28, + CONST_ME_FIREWORK_YELLOW = 29, + CONST_ME_FIREWORK_RED = 30, + CONST_ME_FIREWORK_BLUE = 31, +}; + +enum ShootType_t : uint8_t { + CONST_ANI_NONE, + + CONST_ANI_SPEAR = 1, + CONST_ANI_BOLT = 2, + CONST_ANI_ARROW = 3, + CONST_ANI_FIRE = 4, + CONST_ANI_ENERGY = 5, + CONST_ANI_POISONARROW = 6, + CONST_ANI_BURSTARROW = 7, + CONST_ANI_THROWINGSTAR = 8, + CONST_ANI_THROWINGKNIFE = 9, + CONST_ANI_SMALLSTONE = 10, + CONST_ANI_DEATH = 11, + CONST_ANI_LARGEROCK = 12, + CONST_ANI_SNOWBALL = 13, + CONST_ANI_POWERBOLT = 14, + CONST_ANI_POISON = 15, + CONST_ANI_INFERNALBOLT = 16, +}; + +enum SpeakClasses : uint8_t { + TALKTYPE_SAY = 1, + TALKTYPE_WHISPER = 2, + TALKTYPE_YELL = 3, + TALKTYPE_PRIVATE = 4, + TALKTYPE_CHANNEL_Y = 5, // Yellow + TALKTYPE_RVR_CHANNEL = 6, + TALKTYPE_RVR_ANSWER = 7, + TALKTYPE_RVR_CONTINUE = 8, + TALKTYPE_BROADCAST = 9, + TALKTYPE_CHANNEL_R1 = 10, // Red - #c text + TALKTYPE_PRIVATE_RED = 11, // @name@text + TALKTYPE_CHANNEL_O = 12, // orange + TALKTYPE_CHANNEL_R2 = 13, // red anonymous - #d text + TALKTYPE_MONSTER_YELL = 0x10, + TALKTYPE_MONSTER_SAY = 0x11, +}; + +enum MessageClasses : uint8_t { + MESSAGE_STATUS_CONSOLE_YELLOW = 0x01, //Yellow message in the console + MESSAGE_STATUS_CONSOLE_LBLUE = 0x04, //Light blue message in the console + MESSAGE_STATUS_CONSOLE_ORANGE = 0x11, //Orange message in the console + MESSAGE_STATUS_WARNING = 0x12, //Red message in game window and in the console + MESSAGE_EVENT_ADVANCE = 0x13, //White message in game window and in the console + MESSAGE_EVENT_DEFAULT = 0x14, //White message at the bottom of the game window and in the console + MESSAGE_STATUS_DEFAULT = 0x15, //White message at the bottom of the game window and in the console + MESSAGE_INFO_DESCR = 0x16, //Green message in game window and in the console + MESSAGE_STATUS_SMALL = 0x17, //White message at the bottom of the game window" + MESSAGE_STATUS_CONSOLE_BLUE = 0x18, //Blue message in the console + MESSAGE_STATUS_CONSOLE_RED = 0x19, //Red message in the console + + MESSAGE_CLASS_FIRST = MESSAGE_STATUS_CONSOLE_YELLOW, + MESSAGE_CLASS_LAST = MESSAGE_STATUS_CONSOLE_RED, +}; + +enum FluidTypes_t : uint8_t +{ + FLUID_NONE = 0, + FLUID_WATER, + FLUID_WINE, + FLUID_BEER, + FLUID_MUD, + FLUID_BLOOD, + FLUID_SLIME, + FLUID_OIL, + FLUID_URINE, + FLUID_MILK, + FLUID_MANAFLUID, + FLUID_LIFEFLUID, + FLUID_LEMONADE, + FLUID_RUM, + FLUID_COCONUTMILK, + FLUID_FRUITJUICE, +}; + +const uint8_t reverseFluidMap[] = { + FLUID_NONE, + FLUID_WATER, + FLUID_MANAFLUID, + FLUID_BEER, + FLUID_NONE, + FLUID_LIFEFLUID, + FLUID_SLIME, + FLUID_NONE, + FLUID_LEMONADE, + FLUID_MILK, +}; + +enum FluidColor_t : uint8_t +{ + FLUID_COLOR_NONE = 0, + FLUID_COLOR_BLUE = 1, + FLUID_COLOR_PURPLE = 2, + FLUID_COLOR_BROWN = 3, + FLUID_COLOR_BROWN1 = 4, + FLUID_COLOR_RED = 5, + FLUID_COLOR_GREEN = 6, + FLUID_COLOR_BROWN2 = 7, + FLUID_COLOR_YELLOW = 8, + FLUID_COLOR_WHITE = 9, +}; + +enum SquareColor_t : uint8_t { + SQ_COLOR_BLACK = 0, +}; + +enum TextColor_t : uint8_t { + TEXTCOLOR_BLUE = 5, + TEXTCOLOR_LIGHTGREEN = 30, + TEXTCOLOR_LIGHTBLUE = 35, + TEXTCOLOR_MAYABLUE = 95, + TEXTCOLOR_DARKRED = 108, + TEXTCOLOR_LIGHTGREY = 129, + TEXTCOLOR_SKYBLUE = 143, + TEXTCOLOR_PURPLE = 155, + TEXTCOLOR_RED = 180, + TEXTCOLOR_ORANGE = 198, + TEXTCOLOR_YELLOW = 210, + TEXTCOLOR_WHITE_EXP = 215, + TEXTCOLOR_NONE = 255, +}; + +enum Icons_t { + ICON_POISON = 1 << 0, + ICON_BURN = 1 << 1, + ICON_ENERGY = 1 << 2, + ICON_DRUNK = 1 << 3, + ICON_MANASHIELD = 1 << 4, + ICON_PARALYZE = 1 << 5, + ICON_HASTE = 1 << 6, + ICON_SWORDS = 1 << 7, + ICON_DROWNING = 1 << 8, +}; + +enum WeaponType_t : uint8_t { + WEAPON_NONE, + WEAPON_SWORD, + WEAPON_CLUB, + WEAPON_AXE, + WEAPON_SHIELD, + WEAPON_DISTANCE, + WEAPON_WAND, + WEAPON_AMMO, +}; + +enum Ammo_t : uint8_t { + AMMO_NONE, + AMMO_BOLT, + AMMO_ARROW, + AMMO_SPEAR, + AMMO_THROWINGSTAR, + AMMO_THROWINGKNIFE, + AMMO_STONE, + AMMO_SNOWBALL, +}; + +enum WeaponAction_t : uint8_t { + WEAPONACTION_NONE, + WEAPONACTION_REMOVECOUNT, + WEAPONACTION_REMOVECHARGE, + WEAPONACTION_MOVE, +}; + +enum WieldInfo_t { + WIELDINFO_LEVEL = 1 << 0, + WIELDINFO_MAGLV = 1 << 1, + WIELDINFO_VOCREQ = 1 << 2, + WIELDINFO_PREMIUM = 1 << 3, +}; + +enum Skulls_t : uint8_t { + SKULL_NONE = 0, + SKULL_YELLOW = 1, + SKULL_GREEN = 2, + SKULL_WHITE = 3, + SKULL_RED = 4, +}; + +enum PartyShields_t : uint8_t { + SHIELD_NONE = 0, + SHIELD_WHITEYELLOW = 1, + SHIELD_WHITEBLUE = 2, + SHIELD_BLUE = 3, + SHIELD_YELLOW = 4, + SHIELD_BLUE_SHAREDEXP = 5, + SHIELD_YELLOW_SHAREDEXP = 6, + SHIELD_BLUE_NOSHAREDEXP_BLINK = 7, + SHIELD_YELLOW_NOSHAREDEXP_BLINK = 8, + SHIELD_BLUE_NOSHAREDEXP = 9, + SHIELD_YELLOW_NOSHAREDEXP = 10, + SHIELD_GRAY = 11, +}; + +enum item_t : uint16_t { + ITEM_FIREFIELD_PVP_FULL = 2118, + ITEM_FIREFIELD_PVP_MEDIUM = 2119, + ITEM_FIREFIELD_PVP_SMALL = 2120, + ITEM_FIREFIELD_PERSISTENT_FULL = 2123, + ITEM_FIREFIELD_PERSISTENT_MEDIUM = 2124, + ITEM_FIREFIELD_PERSISTENT_SMALL = 2125, + ITEM_FIREFIELD_NOPVP = 2131, + + ITEM_POISONFIELD_PVP = 2121, + ITEM_POISONFIELD_PERSISTENT = 2127, + ITEM_POISONFIELD_NOPVP = 2134, + + ITEM_ENERGYFIELD_PVP = 2122, + ITEM_ENERGYFIELD_PERSISTENT = 2126, + ITEM_ENERGYFIELD_NOPVP = 2135, + + ITEM_MAGICWALL = 2128, + ITEM_MAGICWALL_PERSISTENT = 2128, + + ITEM_WILDGROWTH = 2130, + ITEM_WILDGROWTH_PERSISTENT = 2130, + + ITEM_GOLD_COIN = 3031, + ITEM_PLATINUM_COIN = 3035, + ITEM_CRYSTAL_COIN = 3043, + + ITEM_DEPOT = 3502, + ITEM_LOCKER1 = 3497, + + ITEM_MALE_CORPSE = 4240, + ITEM_FEMALE_CORPSE = 4247, + + ITEM_FULLSPLASH = 2886, + ITEM_SMALLSPLASH = 2889, + + ITEM_BAG = 2853, + + ITEM_PARCEL = 3503, + ITEM_PARCEL_STAMPED = 3504, + ITEM_LETTER = 3505, + ITEM_LETTER_STAMPED = 3506, + ITEM_LABEL = 3507, + + ITEM_AMULETOFLOSS = 3057, + + ITEM_DOCUMENT_RO = 2819, //read-only +}; + +enum PlayerFlags : uint64_t { + PlayerFlag_CannotUseCombat = 1 << 0, + PlayerFlag_CannotAttackPlayer = 1 << 1, + PlayerFlag_CannotAttackMonster = 1 << 2, + PlayerFlag_CannotBeAttacked = 1 << 3, + PlayerFlag_CanConvinceAll = 1 << 4, + PlayerFlag_CanSummonAll = 1 << 5, + PlayerFlag_CanIllusionAll = 1 << 6, + PlayerFlag_CanSenseInvisibility = 1 << 7, + PlayerFlag_IgnoredByMonsters = 1 << 8, + PlayerFlag_NotGainInFight = 1 << 9, + PlayerFlag_HasInfiniteMana = 1 << 10, + PlayerFlag_HasInfiniteSoul = 1 << 11, + PlayerFlag_HasNoExhaustion = 1 << 12, + PlayerFlag_CannotUseSpells = 1 << 13, + PlayerFlag_CannotPickupItem = 1 << 14, + PlayerFlag_CanAlwaysLogin = 1 << 15, + PlayerFlag_CanBroadcast = 1 << 16, + PlayerFlag_CanEditHouses = 1 << 17, + PlayerFlag_CannotBeBanned = 1 << 18, + PlayerFlag_CannotBePushed = 1 << 19, + PlayerFlag_HasInfiniteCapacity = 1 << 20, + PlayerFlag_CanPushAllCreatures = 1 << 21, + PlayerFlag_CanTalkRedPrivate = 1 << 22, + PlayerFlag_CanTalkRedChannel = 1 << 23, + PlayerFlag_TalkOrangeHelpChannel = 1 << 24, + PlayerFlag_NotGainExperience = 1 << 25, + PlayerFlag_NotGainMana = 1 << 26, + PlayerFlag_NotGainHealth = 1 << 27, + PlayerFlag_NotGainSkill = 1 << 28, + PlayerFlag_SetMaxSpeed = 1 << 29, + PlayerFlag_SpecialVIP = 1 << 30, + PlayerFlag_NotGenerateLoot = static_cast(1) << 31, + PlayerFlag_CanTalkRedChannelAnonymous = static_cast(1) << 32, + PlayerFlag_IgnoreProtectionZone = static_cast(1) << 33, + PlayerFlag_IgnoreSpellCheck = static_cast(1) << 34, + PlayerFlag_IgnoreWeaponCheck = static_cast(1) << 35, + PlayerFlag_CannotBeMuted = static_cast(1) << 36, + PlayerFlag_IsAlwaysPremium = static_cast(1) << 37, + PlayerFlag_SpecialMoveUse = static_cast(1) << 38, +}; + +enum ReloadTypes_t : uint8_t { + RELOAD_TYPE_ALL, + RELOAD_TYPE_ACTIONS, + RELOAD_TYPE_CHAT, + RELOAD_TYPE_COMMANDS, + RELOAD_TYPE_CONFIG, + RELOAD_TYPE_CREATURESCRIPTS, + RELOAD_TYPE_EVENTS, + RELOAD_TYPE_GLOBAL, + RELOAD_TYPE_GLOBALEVENTS, + RELOAD_TYPE_ITEMS, + RELOAD_TYPE_MONSTERS, + RELOAD_TYPE_MOUNTS, + RELOAD_TYPE_MOVEMENTS, + RELOAD_TYPE_NPCS, + RELOAD_TYPE_QUESTS, + RELOAD_TYPE_RAIDS, + RELOAD_TYPE_SPELLS, + RELOAD_TYPE_TALKACTIONS, + RELOAD_TYPE_WEAPONS, +}; + +enum ClientVersion_t : uint16_t { + CLIENT_VERSION_780 = 780, + CLIENT_VERSION_781 = 781, + CLIENT_VERSION_790 = 790, + CLIENT_VERSION_792 = 792, +}; + +static constexpr int32_t CHANNEL_GUILD = 0x00; +static constexpr int32_t CHANNEL_PARTY = 0x01; +static constexpr int32_t CHANNEL_RULE_REP = 0x02; +static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; + +//Reserved player storage key ranges; +//[10000000 - 20000000]; +static constexpr int32_t PSTRG_RESERVED_RANGE_START = 10000000; +static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000; +//[1000 - 1500]; +static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000); +static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500; + +#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) + +#endif diff --git a/app/SabrehavenServer/src/container.cpp b/app/SabrehavenServer/src/container.cpp new file mode 100644 index 0000000..f949f81 --- /dev/null +++ b/app/SabrehavenServer/src/container.cpp @@ -0,0 +1,692 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "container.h" +#include "iomap.h" +#include "game.h" + +extern Game g_game; + +Container::Container(uint16_t type) : + Container(type, items[type].maxItems) {} + +Container::Container(uint16_t type, uint16_t size) : + Item(type), + maxSize(size) +{} + +Container::~Container() +{ + for (Item* item : itemlist) { + item->setParent(nullptr); + item->decrementReferenceCounter(); + } +} + +Item* Container::clone() const +{ + Container* clone = static_cast(Item::clone()); + for (Item* item : itemlist) { + clone->addItem(item->clone()); + } + clone->totalWeight = totalWeight; + return clone; +} + +Container* Container::getParentContainer() +{ + Thing* thing = getParent(); + if (!thing) { + return nullptr; + } + return thing->getContainer(); +} + +bool Container::hasParent() const +{ + return dynamic_cast(getParent()) != nullptr; +} + +void Container::addItem(Item* item) +{ + itemlist.push_back(item); + item->setParent(this); +} + +Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_CONTAINER_ITEMS) { + if (!propStream.read(serializationCount)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_END; + } + return Item::readAttr(attr, propStream); +} + +bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) +{ + bool ret = Item::unserializeItemNode(f, node, propStream); + if (!ret) { + return false; + } + + uint32_t type; + NODE nodeItem = f.getChildNode(node, type); + while (nodeItem) { + //load container items + if (type != OTBM_ITEM) { + // unknown type + return false; + } + + PropStream itemPropStream; + if (!f.getProps(nodeItem, itemPropStream)) { + return false; + } + + Item* item = Item::CreateItem(itemPropStream); + if (!item) { + return false; + } + + if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) { + return false; + } + + addItem(item); + updateItemWeight(item->getWeight()); + + nodeItem = f.getNextNode(nodeItem, type); + } + return true; +} + +void Container::updateItemWeight(int32_t diff) +{ + totalWeight += diff; + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(diff); + } +} + +uint32_t Container::getWeight() const +{ + return Item::getWeight() + totalWeight; +} + +std::string Container::getContentDescription() const +{ + std::ostringstream os; + return getContentDescription(os).str(); +} + +std::ostringstream& Container::getContentDescription(std::ostringstream& os) const +{ + bool firstitem = true; + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + Item* item = *it; + + Container* container = item->getContainer(); + if (container && !container->empty()) { + continue; + } + + if (firstitem) { + firstitem = false; + } else { + os << ", "; + } + + os << item->getNameDescription(); + } + + if (firstitem) { + os << "nothing"; + } + return os; +} + +Item* Container::getItemByIndex(size_t index) const +{ + if (index >= size()) { + return nullptr; + } + return itemlist[index]; +} + +uint32_t Container::getItemHoldingCount() const +{ + uint32_t counter = 0; + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + ++counter; + } + return counter; +} + +bool Container::isHoldingItem(const Item* item) const +{ + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + if (*it == item) { + return true; + } + } + return false; +} + +void Container::onAddContainerItem(Item* item) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendAddContainerItem(this, item); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onAddContainerItem(item); + } +} + +void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem); + } +} + +void Container::onRemoveContainerItem(uint32_t index, Item* item) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send change to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendRemoveContainerItem(this, index); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onRemoveContainerItem(this, item); + } +} + +ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor/* = nullptr*/) const +{ + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + if (childIsOwner) { + //a child container is querying, since we are the top container (not carried by a player) + //just return with no error. + return RETURNVALUE_NOERROR; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isPickupable()) { + return RETURNVALUE_CANNOTPICKUP; + } + + if (item == this) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + const Cylinder* cylinder = getParent(); + if (!hasBitSet(FLAG_NOLIMIT, flags)) { + while (cylinder) { + if (cylinder == &thing) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + cylinder = cylinder->getParent(); + } + + if (index == INDEX_WHEREEVER && size() >= capacity()) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } else { + while (cylinder) { + if (cylinder == &thing) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + cylinder = cylinder->getParent(); + } + } + + const Cylinder* topParent = getTopParent(); + if (topParent != this) { + return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor); + } else { + return RETURNVALUE_NOERROR; + } +} + +ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + maxQueryCount = 0; + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; + } + + int32_t freeSlots = std::max(capacity() - size(), 0); + + if (item->isStackable()) { + uint32_t n = 0; + + if (index == INDEX_WHEREEVER) { + //Iterate through every item and check how much free stackable slots there is. + uint32_t slotIndex = 0; + for (Item* containerItem : itemlist) { + if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) { + uint32_t remainder = (100 - containerItem->getItemCount()); + if (queryAdd(slotIndex++, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n += remainder; + } + } + } + } else { + const Item* destItem = getItemByIndex(index); + if (item->equals(destItem) && destItem->getItemCount() < 100) { + uint32_t remainder = 100 - destItem->getItemCount(); + if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n = remainder; + } + } + } + + maxQueryCount = freeSlots * 100 + n; + if (maxQueryCount < count) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } else { + maxQueryCount = freeSlots; + if (maxQueryCount == 0) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + return RETURNVALUE_NOERROR; +} + +Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem, + uint32_t& flags) +{ + if (index == 254 /*move up*/) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + + Container* parentContainer = dynamic_cast(getParent()); + if (parentContainer) { + return parentContainer; + } + return this; + } + + if (index == 255 /*add wherever*/) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + } + else if (index >= static_cast(capacity())) { + /* + if you have a container, maximize it to show all 20 slots + then you open a bag that is inside the container you will have a bag with 8 slots + and a "grey" area where the other 12 slots where from the container + if you drop the item on that grey area + the client calculates the slot position as if the bag has 20 slots + */ + index = INDEX_WHEREEVER; + *destItem = nullptr; + } + + const Item* item = thing.getItem(); + if (!item) { + return this; + } + + if (index != INDEX_WHEREEVER) { + Item* itemFromIndex = getItemByIndex(index); + if (itemFromIndex) { + *destItem = itemFromIndex; + } + + Cylinder* subCylinder = dynamic_cast(*destItem); + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + return subCylinder; + } + } + + if (g_config.getBoolean(ConfigManager::STACK_CUMULATIVES)) { + bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags); + if (autoStack && item->isStackable() && item->getParent() != this) { + //try find a suitable item to stack with + uint32_t n = 0; + for (Item* listItem : itemlist) { + if (listItem != item && listItem->equals(item) && listItem->getItemCount() < 100) { + *destItem = listItem; + index = n; + return this; + } + ++n; + } + } + } + + return this; +} + +void Container::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Container::addThing(int32_t index, Thing* thing) +{ + if (index >= static_cast(capacity())) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + itemlist.push_front(item); + updateItemWeight(item->getWeight()); + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::addItemBack(Item* item) +{ + addItem(item); + updateItemWeight(item->getWeight()); + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + const int32_t oldWeight = item->getWeight(); + item->setID(itemId); + item->setSubType(count); + updateItemWeight(-oldWeight + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, item, item); + } +} + +void Container::replaceThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* replacedItem = getItemByIndex(index); + if (!replacedItem) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + itemlist[index] = item; + item->setParent(this); + updateItemWeight(-static_cast(replacedItem->getWeight()) + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, replacedItem, item); + } + + replacedItem->setParent(nullptr); +} + +void Container::removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (item->isStackable() && count != item->getItemCount()) { + uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); + const int32_t oldWeight = item->getWeight(); + item->setItemCount(newCount); + updateItemWeight(-oldWeight + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, item, item); + } + } else { + updateItemWeight(-static_cast(item->getWeight())); + + //send change to client + if (getParent()) { + onRemoveContainerItem(index, item); + } + + item->setParent(nullptr); + itemlist.erase(itemlist.begin() + index); + } +} + +int32_t Container::getThingIndex(const Thing* thing) const +{ + int32_t index = 0; + for (Item* item : itemlist) { + if (item == thing) { + return index; + } + ++index; + } + return -1; +} + +size_t Container::getFirstIndex() const +{ + return 0; +} + +size_t Container::getLastIndex() const +{ + return size(); +} + +uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const +{ + uint32_t count = 0; + for (Item* item : itemlist) { + if (item->getID() == itemId) { + count += countByType(item, subType); + } + } + return count; +} + +std::map& Container::getAllItemTypeCount(std::map& countMap) const +{ + for (Item* item : itemlist) { + countMap[item->getID()] += item->getItemCount(); + } + return countMap; +} + +Thing* Container::getThing(size_t index) const +{ + return getItemByIndex(index); +} + +void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + Cylinder* topParent = getTopParent(); + if (topParent->getCreature()) { + topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT); + } else if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + } else { + topParent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + Cylinder* topParent = getTopParent(); + if (topParent->getCreature()) { + topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT); + } else if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR); + } + } else { + topParent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} + +void Container::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Container::internalAddThing(uint32_t, Thing* thing) +{ + Item* item = thing->getItem(); + if (item == nullptr) { + return; + } + + item->setParent(this); + itemlist.push_front(item); + updateItemWeight(item->getWeight()); +} + +void Container::startDecaying() +{ + for (Item* item : itemlist) { + item->startDecaying(); + } +} + +ContainerIterator Container::iterator() const +{ + ContainerIterator cit; + if (!itemlist.empty()) { + cit.over.push_back(this); + cit.cur = itemlist.begin(); + } + return cit; +} + +Item* ContainerIterator::operator*() +{ + return *cur; +} + +void ContainerIterator::advance() +{ + if (Item* i = *cur) { + if (Container* c = i->getContainer()) { + if (!c->empty()) { + over.push_back(c); + } + } + } + + ++cur; + + if (cur == over.front()->itemlist.end()) { + over.pop_front(); + if (!over.empty()) { + cur = over.front()->itemlist.begin(); + } + } +} diff --git a/app/SabrehavenServer/src/container.h b/app/SabrehavenServer/src/container.h new file mode 100644 index 0000000..2330c26 --- /dev/null +++ b/app/SabrehavenServer/src/container.h @@ -0,0 +1,162 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC +#define FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC + +#include + +#include "cylinder.h" +#include "item.h" + +class Container; +class DepotLocker; + +class ContainerIterator +{ + public: + bool hasNext() const { + return !over.empty(); + } + + void advance(); + Item* operator*(); + + protected: + std::list over; + ItemDeque::const_iterator cur; + + friend class Container; +}; + +class Container : public Item, public Cylinder +{ + public: + explicit Container(uint16_t type); + Container(uint16_t type, uint16_t size); + ~Container(); + + // non-copyable + Container(const Container&) = delete; + Container& operator=(const Container&) = delete; + + Item* clone() const final; + + Container* getContainer() final { + return this; + } + const Container* getContainer() const final { + return this; + } + + virtual DepotLocker* getDepotLocker() override { + return nullptr; + } + virtual const DepotLocker* getDepotLocker() const override { + return nullptr; + } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) override; + std::string getContentDescription() const; + + size_t size() const { + return itemlist.size(); + } + bool empty() const { + return itemlist.empty(); + } + uint32_t capacity() const { + return maxSize; + } + + ContainerIterator iterator() const; + + const ItemDeque& getItemList() const { + return itemlist; + } + + ItemDeque::const_reverse_iterator getReversedItems() const { + return itemlist.rbegin(); + } + ItemDeque::const_reverse_iterator getReversedEnd() const { + return itemlist.rend(); + } + + bool hasParent() const; + void addItem(Item* item); + Item* getItemByIndex(size_t index) const; + bool isHoldingItem(const Item* item) const; + + uint32_t getItemHoldingCount() const; + uint32_t getWeight() const final; + + //cylinder implementations + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + void addItemBack(Item* item); + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + std::map& getAllItemTypeCount(std::map& countMap) const final; + Thing* getThing(size_t index) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + void startDecaying() final; + + private: + void onAddContainerItem(Item* item); + void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); + void onRemoveContainerItem(uint32_t index, Item* item); + + Container* getParentContainer(); + void updateItemWeight(int32_t diff); + + protected: + std::ostringstream& getContentDescription(std::ostringstream& os) const; + + uint32_t maxSize; + uint32_t totalWeight = 0; + ItemDeque itemlist; + uint32_t serializationCount = 0; + + friend class ContainerIterator; + friend class IOMapSerialize; +}; + +#endif diff --git a/app/SabrehavenServer/src/creature.cpp b/app/SabrehavenServer/src/creature.cpp new file mode 100644 index 0000000..437b285 --- /dev/null +++ b/app/SabrehavenServer/src/creature.cpp @@ -0,0 +1,1566 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "creature.h" +#include "game.h" +#include "monster.h" +#include "configmanager.h" +#include "scheduler.h" + +extern Game g_game; +extern ConfigManager g_config; +extern CreatureEvents* g_creatureEvents; + +Creature::Creature() +{ + onIdleStatus(); +} + +Creature::~Creature() +{ + for (Creature* summon : summons) { + summon->setAttackedCreature(nullptr); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + + for (Condition* condition : conditions) { + condition->endCondition(this); + delete condition; + } +} + +bool Creature::canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY) +{ + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (pos.z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (Position::getDistanceZ(myPos, pos) > 2) { + return false; + } + } + + const int_fast32_t offsetz = myPos.getZ() - pos.getZ(); + return (pos.getX() >= myPos.getX() - viewRangeX + offsetz) && (pos.getX() <= myPos.getX() + viewRangeX + offsetz) + && (pos.getY() >= myPos.getY() - viewRangeY + offsetz) && (pos.getY() <= myPos.getY() + viewRangeY + offsetz); +} + +bool Creature::canSee(const Position& pos) const +{ + return canSee(getPosition(), pos, Map::maxViewportX, Map::maxViewportY); +} + +bool Creature::canSeeCreature(const Creature* creature) const +{ + if (!canSeeInvisibility() && creature->isInvisible()) { + return false; + } + return true; +} + +void Creature::setSkull(Skulls_t newSkull) +{ + skull = newSkull; + g_game.updateCreatureSkull(this); +} + +int64_t Creature::getTimeSinceLastMove() const +{ + if (lastStep) { + return OTSYS_TIME() - lastStep; + } + return std::numeric_limits::max(); +} + +int32_t Creature::getWalkDelay(Direction dir) const +{ + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration(dir); + return stepDuration - (ct - lastStep); +} + +int32_t Creature::getWalkDelay() const +{ + //Used for auto-walking + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration() * lastStepCost; + return stepDuration - (ct - lastStep); +} + +void Creature::onThink(uint32_t interval) +{ + if (!isMapLoaded && useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (followCreature && master != followCreature && !canSeeCreature(followCreature)) { + onCreatureDisappear(followCreature, false); + } + + if (attackedCreature && master != attackedCreature && !canSeeCreature(attackedCreature)) { + onCreatureDisappear(attackedCreature, false); + } + + blockTicks += interval; + if (blockTicks >= 1000) { + blockCount = std::min(blockCount + 1, 2); + blockTicks = 0; + } + + if (followCreature) { + walkUpdateTicks += interval; + if (forceUpdateFollowPath || walkUpdateTicks >= 2000) { + walkUpdateTicks = 0; + forceUpdateFollowPath = false; + isUpdatingPath = true; + } + } + + if (isUpdatingPath) { + isUpdatingPath = false; + goToFollowCreature(); + } + + //scripting event - onThink + const CreatureEventList& thinkEvents = getCreatureEvents(CREATURE_EVENT_THINK); + for (CreatureEvent* thinkEvent : thinkEvents) { + thinkEvent->executeOnThink(this, interval); + } +} + +void Creature::onAttacking(uint32_t interval) +{ + if (!attackedCreature) { + return; + } + + onAttacked(); + attackedCreature->onAttacked(); + + if (g_game.isSightClear(getPosition(), attackedCreature->getPosition(), true)) { + doAttacking(interval); + } +} + +void Creature::onIdleStatus() +{ + if (getHealth() > 0) { + damageMap.clear(); + lastHitCreatureId = 0; + } +} + +void Creature::onWalk() +{ + if (getWalkDelay() <= 0) { + Direction dir; + uint32_t flags = FLAG_IGNOREFIELDDAMAGE; + if (getNextStep(dir, flags)) { + ReturnValue ret = g_game.internalMoveCreature(this, dir, flags); + if (ret != RETURNVALUE_NOERROR) { + if (Player* player = getPlayer()) { + player->sendCancelMessage(ret); + player->sendCancelWalk(); + } + + forceUpdateFollowPath = true; + } + } else { + if (listWalkDir.empty()) { + onWalkComplete(); + } + + stopEventWalk(); + } + } + + if (cancelNextWalk) { + listWalkDir.clear(); + onWalkAborted(); + cancelNextWalk = false; + } + + if (eventWalk != 0) { + eventWalk = 0; + addEventWalk(); + } +} + +void Creature::onWalk(Direction& dir) +{ + if (hasCondition(CONDITION_DRUNK)) { + uint32_t r = uniform_random(0, 20); + if (r <= DIRECTION_DIAGONAL_MASK) { + if (r < DIRECTION_DIAGONAL_MASK) { + dir = static_cast(r); + } + g_game.internalCreatureSay(this, TALKTYPE_SAY, "Hicks!", false); + } + } +} + +bool Creature::getNextStep(Direction& dir, uint32_t&) +{ + if (listWalkDir.empty()) { + return false; + } + + dir = listWalkDir.front(); + listWalkDir.pop_front(); + onWalk(dir); + return true; +} + +void Creature::startAutoWalk(const std::forward_list& listDir) +{ + listWalkDir = listDir; + + size_t size = 0; + for (auto it = listDir.begin(); it != listDir.end() && size <= 1; ++it) { + size++; + } + addEventWalk(size == 1); +} + +void Creature::addEventWalk(bool firstStep) +{ + cancelNextWalk = false; + + if (getStepSpeed() <= 0) { + return; + } + + if (eventWalk != 0) { + return; + } + + int64_t ticks = getEventStepTicks(firstStep); + if (ticks <= 0) { + return; + } + + // Take first step right away, but still queue the next + if (ticks == 1) { + g_game.checkCreatureWalk(getID()); + } + + if (const Monster* monster = getMonster()) { + Creature* creature = monster->getFollowCreature(); + if (!creature) { + ticks = ticks * 1.25; + } + } + + eventWalk = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Game::checkCreatureWalk, &g_game, getID()))); +} + +void Creature::stopEventWalk() +{ + if (eventWalk != 0) { + g_scheduler.stopEvent(eventWalk); + eventWalk = 0; + } +} + +void Creature::updateMapCache() +{ + Tile* tile; + const Position& myPos = getPosition(); + Position pos(0, 0, myPos.z); + + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + pos.x = myPos.getX() + x; + pos.y = myPos.getY() + y; + tile = g_game.map.getTile(pos); + updateTileCache(tile, pos); + } + } +} + +void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) +{ + if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { + localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR; + } +} + +void Creature::updateTileCache(const Tile* tile, const Position& pos) +{ + const Position& myPos = getPosition(); + if (pos.z == myPos.z) { + int32_t dx = Position::getOffsetX(pos, myPos); + int32_t dy = Position::getOffsetY(pos, myPos); + updateTileCache(tile, dx, dy); + } +} + +int32_t Creature::getWalkCache(const Position& pos) const +{ + if (!useCacheMap()) { + return 2; + } + + const Position& myPos = getPosition(); + if (myPos.z != pos.z) { + return 0; + } + + if (pos == myPos) { + return 1; + } + + int32_t dx = Position::getOffsetX(pos, myPos); + if (std::abs(dx) <= maxWalkCacheWidth) { + int32_t dy = Position::getOffsetY(pos, myPos); + if (std::abs(dy) <= maxWalkCacheHeight) { + if (localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx]) { + return 1; + } else { + return 0; + } + } + } + + //out of range + return 2; +} + +void Creature::onAddTileItem(const Tile* tile, const Position& pos) +{ + if (isMapLoaded && pos.z == getPosition().z) { + updateTileCache(tile, pos); + } +} + +void Creature::onUpdateTileItem(const Tile* tile, const Position& pos, const Item*, + const ItemType& oldType, const Item*, const ItemType& newType) +{ + if (!isMapLoaded) { + return; + } + + if (oldType.blockSolid || oldType.blockPathFind || newType.blockPathFind || newType.blockSolid) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item*) +{ + if (!isMapLoaded) { + return; + } + + if (iType.blockSolid || iType.blockPathFind || iType.isGroundTile()) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onCreatureAppear(Creature* creature, bool isLogin) +{ + if (creature == this) { + if (useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (isLogin) { + setLastPosition(getPosition()); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onRemoveCreature(Creature* creature, bool) +{ + onCreatureDisappear(creature, true); + if (creature == this) { + if (master && !master->isRemoved()) { + master->removeSummon(this); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onCreatureDisappear(const Creature* creature, bool isLogout) +{ + if (attackedCreature == creature) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(isLogout); + } + + if (followCreature == creature) { + setFollowCreature(nullptr); + onFollowCreatureDisappear(isLogout); + } +} + +void Creature::onChangeZone(ZoneType_t zone) +{ + if (attackedCreature && zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + if (creature == this) { + lastStep = OTSYS_TIME(); + lastStepCost = 1; + + if (!teleport) { + if (oldPos.z != newPos.z) { + //floor change extra cost + lastStepCost = 2; + } else if (Position::getDistanceX(newPos, oldPos) >= 1 && Position::getDistanceY(newPos, oldPos) >= 1) { + //diagonal extra cost + lastStepCost = 3; + } + } else { + stopEventWalk(); + } + + if (!summons.empty()) { + //check if any of our summons is out of range (+/- 2 floors or 30 tiles away) + std::forward_list despawnList; + for (Creature* summon : summons) { + const Position& pos = summon->getPosition(); + if (Position::getDistanceZ(newPos, pos) > 2 || (std::max(Position::getDistanceX(newPos, pos), Position::getDistanceY(newPos, pos)) > 30)) { + despawnList.push_front(summon); + } + } + + for (Creature* despawnCreature : despawnList) { + g_game.removeCreature(despawnCreature, true); + } + } + + if (newTile->getZone() != oldTile->getZone()) { + onChangeZone(getZone()); + } + + //update map cache + if (isMapLoaded) { + if (teleport || oldPos.z != newPos.z) { + updateMapCache(); + } else { + const Position& myPos = getPosition(); + + if (oldPos.y > newPos.y) { //north + //shift y south + for (int32_t y = mapWalkHeight - 1; --y >= 0;) { + memcpy(localMapCache[y + 1], localMapCache[y], sizeof(localMapCache[y])); + } + + //update 0 + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() - maxWalkCacheHeight, myPos.z); + updateTileCache(cacheTile, x, -maxWalkCacheHeight); + } + } else if (oldPos.y < newPos.y) { // south + //shift y north + for (int32_t y = 0; y <= mapWalkHeight - 2; ++y) { + memcpy(localMapCache[y], localMapCache[y + 1], sizeof(localMapCache[y])); + } + + //update mapWalkHeight - 1 + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() + maxWalkCacheHeight, myPos.z); + updateTileCache(cacheTile, x, maxWalkCacheHeight); + } + } + + if (oldPos.x < newPos.x) { // east + //shift y west + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = Position::getDistanceY(oldPos, newPos); + + if (dy < 0) { + endy += dy; + } else if (dy > 0) { + starty = dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = 0; x <= mapWalkWidth - 2; ++x) { + localMapCache[y][x] = localMapCache[y][x + 1]; + } + } + + //update mapWalkWidth - 1 + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + Tile* cacheTile = g_game.map.getTile(myPos.x + maxWalkCacheWidth, myPos.y + y, myPos.z); + updateTileCache(cacheTile, maxWalkCacheWidth, y); + } + } else if (oldPos.x > newPos.x) { // west + //shift y east + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = Position::getDistanceY(oldPos, newPos); + + if (dy < 0) { + endy += dy; + } else if (dy > 0) { + starty = dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = mapWalkWidth - 1; --x >= 0;) { + localMapCache[y][x + 1] = localMapCache[y][x]; + } + } + + //update 0 + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + Tile* cacheTile = g_game.map.getTile(myPos.x - maxWalkCacheWidth, myPos.y + y, myPos.z); + updateTileCache(cacheTile, -maxWalkCacheWidth, y); + } + } + + updateTileCache(oldTile, oldPos); + } + } + } else { + if (isMapLoaded) { + const Position& myPos = getPosition(); + + if (newPos.z == myPos.z) { + updateTileCache(newTile, newPos); + } + + if (oldPos.z == myPos.z) { + updateTileCache(oldTile, oldPos); + } + } + } + + if (creature == followCreature || (creature == this && followCreature)) { + if (hasFollowPath) { + isUpdatingPath = true; + // this updates following walking + if (lastWalkUpdate == 0 || OTSYS_TIME() - lastWalkUpdate >= 250) { + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + lastWalkUpdate = OTSYS_TIME(); + } + } + + if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { + onCreatureDisappear(followCreature, false); + } + } + + if (creature == attackedCreature || (creature == this && attackedCreature)) { + if (newPos.z != oldPos.z || !canSee(attackedCreature->getPosition())) { + onCreatureDisappear(attackedCreature, false); + } else { + if (hasExtraSwing()) { + //our target is moving lets see if we can get in hit + if (getMonster()) { + g_dispatcher.addTask(createTask(std::bind(&Game::checkMonsterExtraAttack, &g_game, getID()))); + } + else { + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + } + + if (newTile->getZone() != oldTile->getZone()) { + onAttackedCreatureChangeZone(attackedCreature->getZone()); + } + } + } +} + +void Creature::onDeath() +{ + bool lastHitUnjustified = false; + bool mostDamageUnjustified = false; + Creature* lastHitCreature = g_game.getCreatureByID(lastHitCreatureId); + Creature* lastHitCreatureMaster; + if (lastHitCreature) { + lastHitUnjustified = lastHitCreature->onKilledCreature(this); + lastHitCreatureMaster = lastHitCreature->getMaster(); + } + else { + lastHitCreatureMaster = nullptr; + } + + Creature* mostDamageCreature = nullptr; + + const int64_t timeNow = OTSYS_TIME(); + const uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); + int32_t mostDamage = 0; + std::map experienceMap; + for (const auto& it : damageMap) { + if (Creature* attacker = g_game.getCreatureByID(it.first)) { + CountBlock_t cb = it.second; + if ((cb.total > mostDamage && (timeNow - cb.ticks <= inFightTicks))) { + mostDamage = cb.total; + mostDamageCreature = attacker; + } + + if (attacker != this) { + uint64_t gainExp = getGainedExperience(attacker); + if (Player* attackerPlayer = attacker->getPlayer()) { + attackerPlayer->removeAttacked(getPlayer()); + Party* party = attackerPlayer->getParty(); + if (party && party->getLeader() && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + attacker = party->getLeader(); + } + } + + auto tmpIt = experienceMap.find(attacker); + if (tmpIt == experienceMap.end()) { + experienceMap[attacker] = gainExp; + } + else { + tmpIt->second += gainExp; + } + } + } + } + + for (const auto& it : experienceMap) { + it.first->onGainExperience(it.second, this); + } + + if (mostDamageCreature) { + if (mostDamageCreature != lastHitCreature && mostDamageCreature != lastHitCreatureMaster) { + Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); + if (lastHitCreature != mostDamageCreatureMaster && (lastHitCreatureMaster == nullptr || mostDamageCreatureMaster != lastHitCreatureMaster)) { + mostDamageUnjustified = mostDamageCreature->onKilledCreature(this, false); + } + } + } + + bool droppedCorpse = dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + death(lastHitCreature); + + if (master) { + master->removeSummon(this); + } + + if (droppedCorpse) { + g_game.removeCreature(this, false); + } +} + +bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + Item* splash; + switch (getRace()) { + case RACE_VENOM: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); + break; + + case RACE_BLOOD: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); + break; + + default: + splash = nullptr; + break; + } + + Tile* tile = getTile(); + + if (splash) { + g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(splash); + } + + Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(corpse); + } + + //scripting event - onDeath + for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { + deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + if (corpse) { + dropLoot(corpse->getContainer(), lastHitCreature); + } + + return true; +} + +bool Creature::hasBeenAttacked(uint32_t attackerId) +{ + auto it = damageMap.find(attackerId); + if (it == damageMap.end()) { + return false; + } + return (OTSYS_TIME() - it->second.ticks) <= g_config.getNumber(ConfigManager::PZ_LOCKED); +} + +Item* Creature::getCorpse(Creature*, Creature*) +{ + return Item::CreateItem(getLookCorpse()); +} + +void Creature::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + int32_t oldHealth = health; + + if (healthChange > 0) { + health += std::min(healthChange, getMaxHealth() - health); + } else { + health = std::max(0, health + healthChange); + } + + if (sendHealthChange && oldHealth != health) { + g_game.addCreatureHealth(this); + } +} + +void Creature::gainHealth(Creature* healer, int32_t healthGain) +{ + changeHealth(healthGain); + if (healer) { + healer->onTargetCreatureGainHealth(this, healthGain); + } +} + +void Creature::drainHealth(Creature* attacker, int32_t damage) +{ + changeHealth(-damage, false); + + if (attacker) { + attacker->onAttackedCreatureDrainHealth(this, damage); + } +} + +BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false */, bool checkArmor /* = false */, bool /* field = false */) +{ + BlockType_t blockType = BLOCK_NONE; + + if (isImmune(combatType)) { + damage = 0; + blockType = BLOCK_IMMUNITY; + } else if (checkDefense || checkArmor) { + if (checkDefense && OTSYS_TIME() >= earliestDefendTime) { + damage -= getDefense(); + + earliestDefendTime = lastDefendTime + 2000; + lastDefendTime = OTSYS_TIME(); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + } + } + + if (checkArmor) { + if (damage > 0 && combatType == COMBAT_PHYSICALDAMAGE) { + damage -= getArmor(); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + } + + bool hasDefense = false; + + if (blockCount > 0) { + --blockCount; + hasDefense = true; + } + + if (hasDefense && blockType != BLOCK_NONE) { + onBlockHit(); + } + } + + if (attacker) { + attacker->onAttackedCreature(this); + attacker->onAttackedCreatureBlockHit(blockType); + } + + onAttacked(); + return blockType; +} + +bool Creature::setAttackedCreature(Creature* creature) +{ + if (creature) { + const Position& creaturePos = creature->getPosition(); + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + attackedCreature = nullptr; + return false; + } + + if (isSummon() && master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1) { + if (!monster->hasFollowPath && !monster->followCreature) { + return false; + } + } + } + } + + attackedCreature = creature; + onAttackedCreature(attackedCreature); + attackedCreature->onAttacked(); + } else { + attackedCreature = nullptr; + } + + for (Creature* summon : summons) { + summon->setAttackedCreature(creature); + } + return true; +} + +void Creature::getPathSearchParams(const Creature*, FindPathParams& fpp) const +{ + fpp.fullPathSearch = !hasFollowPath; + fpp.clearSight = true; + fpp.maxSearchDist = 12; + fpp.minTargetDist = 1; + fpp.maxTargetDist = 1; +} + +void Creature::goToFollowCreature() +{ + if (followCreature) { + FindPathParams fpp; + getPathSearchParams(followCreature, fpp); + + Monster* monster = getMonster(); + if (monster && !monster->getMaster() && (monster->isFleeing() || fpp.maxTargetDist > 1)) { + Direction dir = DIRECTION_NONE; + + if (monster->isFleeing()) { + monster->getDistanceStep(followCreature->getPosition(), dir, true); + } else { //maxTargetDist > 1 + if (!monster->getDistanceStep(followCreature->getPosition(), dir)) { + // if we can't get anything then let the A* calculate + listWalkDir.clear(); + if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + return; + } + } + + if (dir != DIRECTION_NONE) { + listWalkDir.clear(); + listWalkDir.push_front(dir); + + hasFollowPath = true; + startAutoWalk(listWalkDir); + } + } else { + listWalkDir.clear(); + if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + } + } + + onFollowCreatureComplete(followCreature); +} + +bool Creature::setFollowCreature(Creature* creature) +{ + if (creature) { + if (followCreature == creature) { + return true; + } + + const Position& creaturePos = creature->getPosition(); + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + followCreature = nullptr; + return false; + } + + if (!listWalkDir.empty()) { + listWalkDir.clear(); + onWalkAborted(); + } + + hasFollowPath = false; + forceUpdateFollowPath = false; + followCreature = creature; + isUpdatingPath = true; + } else { + isUpdatingPath = false; + followCreature = nullptr; + } + + onFollowCreature(creature); + return true; +} + +double Creature::getDamageRatio(Creature* attacker) const +{ + uint32_t totalDamage = 0; + uint32_t attackerDamage = 0; + + for (const auto& it : damageMap) { + const CountBlock_t& cb = it.second; + totalDamage += cb.total; + if (it.first == attacker->getID()) { + attackerDamage += cb.total; + } + } + + if (totalDamage == 0) { + return 0; + } + + return (static_cast(attackerDamage) / totalDamage); +} + +uint64_t Creature::getGainedExperience(Creature* attacker) const +{ + return std::floor(getDamageRatio(attacker) * getLostExperience()); +} + +void Creature::addDamagePoints(Creature* attacker, int32_t damagePoints) +{ + if (damagePoints <= 0) { + return; + } + + uint32_t attackerId = attacker->id; + + auto it = damageMap.find(attackerId); + if (it == damageMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.total = damagePoints; + damageMap[attackerId] = cb; + } else { + it->second.total += damagePoints; + it->second.ticks = OTSYS_TIME(); + } + + lastHitCreatureId = attackerId; +} + +void Creature::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_PARALYZE && hasCondition(CONDITION_HASTE)) { + removeCondition(CONDITION_HASTE); + } else if (type == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + removeCondition(CONDITION_PARALYZE); + } +} + +void Creature::onAddCombatCondition(ConditionType_t) +{ + // +} + +void Creature::onEndCondition(ConditionType_t) +{ + // +} + +void Creature::onTickCondition(ConditionType_t type, bool& bRemove) +{ + const MagicField* field = getTile()->getFieldItem(); + if (!field) { + return; + } + + switch (type) { + case CONDITION_FIRE: + bRemove = (field->getCombatType() != COMBAT_FIREDAMAGE); + break; + case CONDITION_ENERGY: + bRemove = (field->getCombatType() != COMBAT_ENERGYDAMAGE); + break; + case CONDITION_POISON: + bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE); + break; + case CONDITION_DROWN: + bRemove = (field->getCombatType() != COMBAT_DROWNDAMAGE); + break; + default: + break; + } +} + +void Creature::onCombatRemoveCondition(Condition* condition) +{ + removeCondition(condition); +} + +void Creature::onAttacked() +{ + // +} + +void Creature::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + target->addDamagePoints(this, points); +} + +bool Creature::onKilledCreature(Creature* target, bool) +{ + if (latestKillEvent == target->getID()) { + return false; + } + + latestKillEvent = target->getID(); + + if (master) { + master->onKilledCreature(target); + return false; + } + + //scripting event - onKill + const CreatureEventList& killEvents = getCreatureEvents(CREATURE_EVENT_KILL); + for (CreatureEvent* killEvent : killEvents) { + killEvent->executeOnKill(this, target); + } + return false; +} + +void Creature::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (gainExp == 0 || !master) { + return; + } + + gainExp /= 2; + master->onGainExperience(gainExp, target); + + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(gainExp)); +} + +void Creature::addSummon(Creature* creature) +{ + creature->setDropLoot(false); + creature->setSkillLoss(false); + creature->setMaster(this); + creature->incrementReferenceCounter(); + summons.push_back(creature); +} + +void Creature::removeSummon(Creature* creature) +{ + auto cit = std::find(summons.begin(), summons.end(), creature); + if (cit != summons.end()) { + creature->setDropLoot(true); + creature->setSkillLoss(true); + creature->setMaster(nullptr); + creature->decrementReferenceCounter(); + summons.erase(cit); + } +} + +bool Creature::addCondition(Condition* condition, bool force/* = false*/) +{ + if (condition == nullptr) { + return false; + } + + if (!force && condition->getType() == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceAddCondition, &g_game, getID(), condition))); + return false; + } + } + + Condition* prevCond = getCondition(condition->getType(), condition->getId(), condition->getSubId()); + if (prevCond) { + prevCond->addCondition(this, condition); + delete condition; + return true; + } + + if (condition->startCondition(this)) { + conditions.push_back(condition); + onAddCondition(condition->getType()); + return true; + } + + delete condition; + return false; +} + +bool Creature::addCombatCondition(Condition* condition) +{ + //Caution: condition variable could be deleted after the call to addCondition + ConditionType_t type = condition->getType(); + + if (!addCondition(condition)) { + return false; + } + + onAddCombatCondition(type); + return true; +} + +void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->getType() != type) { + ++it; + continue; + } + + if (!force && type == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force/* = false*/) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->getType() != type || condition->getId() != conditionId) { + ++it; + continue; + } + + if (!force && type == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCombatCondition(ConditionType_t type) +{ + std::vector removeConditions; + for (Condition* condition : conditions) { + if (condition->getType() == type) { + removeConditions.push_back(condition); + } + } + + for (Condition* condition : removeConditions) { + onCombatRemoveCondition(condition); + } +} + +void Creature::removeCondition(Condition* condition, bool force/* = false*/) +{ + auto it = std::find(conditions.begin(), conditions.end(), condition); + if (it == conditions.end()) { + return; + } + + if (!force && condition->getType() == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), condition->getType()))); + return; + } + } + + conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; +} + +Condition* Creature::getCondition(ConditionType_t type) const +{ + for (Condition* condition : conditions) { + if (condition->getType() == type) { + return condition; + } + } + return nullptr; +} + +Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId/* = 0*/) const +{ + for (Condition* condition : conditions) { + if (condition->getType() == type && condition->getId() == conditionId && condition->getSubId() == subId) { + return condition; + } + } + return nullptr; +} + +void Creature::executeConditions(uint32_t interval) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (!condition->executeCondition(this, interval)) { + ConditionType_t type = condition->getType(); + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } else { + ++it; + } + } +} + +bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const +{ + if (isSuppress(type)) { + return false; + } + + int64_t timeNow = OTSYS_TIME(); + for (Condition* condition : conditions) { + if (condition->getType() != type || condition->getSubId() != subId) { + continue; + } + + if (condition->getEndTime() >= timeNow) { + return true; + } + } + return false; +} + +bool Creature::isImmune(CombatType_t type) const +{ + return hasBitSet(static_cast(type), getDamageImmunities()); +} + +bool Creature::isImmune(ConditionType_t type) const +{ + return hasBitSet(static_cast(type), getConditionImmunities()); +} + +bool Creature::isSuppress(ConditionType_t type) const +{ + return hasBitSet(static_cast(type), getConditionSuppressions()); +} + +int64_t Creature::getStepDuration(Direction dir) const +{ + int64_t stepDuration = getStepDuration(); + if ((dir & DIRECTION_DIAGONAL_MASK) != 0) { + stepDuration *= 3; + } + return stepDuration; +} + +int64_t Creature::getStepDuration() const +{ + if (isRemoved()) { + return 0; + } + + uint32_t groundSpeed; + int32_t stepSpeed = getStepSpeed(); + + Item* ground = tile->getGround(); + if (ground) { + groundSpeed = Item::items[ground->getID()].speed; + if (groundSpeed == 0) { + groundSpeed = 150; + } + } else { + groundSpeed = 150; + } + + double duration = std::floor(1000 * groundSpeed) / stepSpeed; + int64_t stepDuration = std::ceil(duration / 50) * 50; + + const Monster* monster = getMonster(); + if (monster && monster->isTargetNearby() && !monster->isFleeing() && !monster->getMaster()) { + stepDuration *= 3; + } + + return stepDuration; +} + +int64_t Creature::getEventStepTicks(bool onlyDelay) const +{ + int64_t ret = getWalkDelay(); + if (ret <= 0) { + int64_t stepDuration = getStepDuration(); + if (onlyDelay && stepDuration > 0) { + ret = 1; + } else { + ret = stepDuration * lastStepCost; + } + } + return ret; +} + +LightInfo Creature::getCreatureLight() const +{ + return internalLight; +} + +void Creature::setNormalCreatureLight() +{ + internalLight.level = 0; + internalLight.color = 0; +} + +bool Creature::registerCreatureEvent(const std::string& name) +{ + CreatureEvent* event = g_creatureEvents->getEventByName(name); + if (!event) { + return false; + } + + CreatureEventType_t type = event->getEventType(); + if (hasEventRegistered(type)) { + for (CreatureEvent* creatureEvent : eventsList) { + if (creatureEvent == event) { + return false; + } + } + } else { + scriptEventsBitField |= static_cast(1) << type; + } + + eventsList.push_back(event); + return true; +} + +bool Creature::unregisterCreatureEvent(const std::string& name) +{ + CreatureEvent* event = g_creatureEvents->getEventByName(name); + if (!event) { + return false; + } + + CreatureEventType_t type = event->getEventType(); + if (!hasEventRegistered(type)) { + return false; + } + + bool resetTypeBit = true; + + auto it = eventsList.begin(), end = eventsList.end(); + while (it != end) { + CreatureEvent* curEvent = *it; + if (curEvent == event) { + it = eventsList.erase(it); + continue; + } + + if (curEvent->getEventType() == type) { + resetTypeBit = false; + } + ++it; + } + + if (resetTypeBit) { + scriptEventsBitField &= ~(static_cast(1) << type); + } + return true; +} + +CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) +{ + CreatureEventList tmpEventList; + + if (!hasEventRegistered(type)) { + return tmpEventList; + } + + for (CreatureEvent* creatureEvent : eventsList) { + if (creatureEvent->getEventType() == type) { + tmpEventList.push_back(creatureEvent); + } + } + + return tmpEventList; +} + +bool FrozenPathingConditionCall::isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const +{ + if (fpp.fullPathSearch) { + if (testPos.x > targetPos.x + fpp.maxTargetDist) { + return false; + } + + if (testPos.x < targetPos.x - fpp.maxTargetDist) { + return false; + } + + if (testPos.y > targetPos.y + fpp.maxTargetDist) { + return false; + } + + if (testPos.y < targetPos.y - fpp.maxTargetDist) { + return false; + } + } else { + int_fast32_t dx = Position::getOffsetX(startPos, targetPos); + + int32_t dxMax = (dx >= 0 ? fpp.maxTargetDist : 0); + if (testPos.x > targetPos.x + dxMax) { + return false; + } + + int32_t dxMin = (dx <= 0 ? fpp.maxTargetDist : 0); + if (testPos.x < targetPos.x - dxMin) { + return false; + } + + int_fast32_t dy = Position::getOffsetY(startPos, targetPos); + + int32_t dyMax = (dy >= 0 ? fpp.maxTargetDist : 0); + if (testPos.y > targetPos.y + dyMax) { + return false; + } + + int32_t dyMin = (dy <= 0 ? fpp.maxTargetDist : 0); + if (testPos.y < targetPos.y - dyMin) { + return false; + } + } + return true; +} + +bool FrozenPathingConditionCall::operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const +{ + if (!isInRange(startPos, testPos, fpp)) { + return false; + } + + if (fpp.clearSight && !g_game.isSightClear(testPos, targetPos, true)) { + return false; + } + + int32_t testDist = std::max(Position::getDistanceX(targetPos, testPos), Position::getDistanceY(targetPos, testPos)); + if (fpp.maxTargetDist == 1) { + if (testDist < fpp.minTargetDist || testDist > fpp.maxTargetDist) { + return false; + } + + return true; + } else if (testDist <= fpp.maxTargetDist) { + if (testDist < fpp.minTargetDist) { + return false; + } + + if (testDist == fpp.maxTargetDist) { + bestMatchDist = 0; + return true; + } else if (testDist > bestMatchDist) { + //not quite what we want, but the best so far + bestMatchDist = testDist; + return true; + } + } + return false; +} + +bool Creature::isInvisible() const +{ + return std::find_if(conditions.begin(), conditions.end(), [] (const Condition* condition) { + return condition->getType() == CONDITION_INVISIBLE; + }) != conditions.end(); +} + +bool Creature::getPathTo(const Position& targetPos, std::forward_list& dirList, const FindPathParams& fpp) const +{ + return g_game.map.getPathMatching(*this, dirList, FrozenPathingConditionCall(targetPos), fpp); +} + +bool Creature::getPathTo(const Position& targetPos, std::forward_list& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch /*= true*/, bool clearSight /*= true*/, int32_t maxSearchDist /*= 0*/) const +{ + FindPathParams fpp; + fpp.fullPathSearch = fullPathSearch; + fpp.maxSearchDist = maxSearchDist; + fpp.clearSight = clearSight; + fpp.minTargetDist = minTargetDist; + fpp.maxTargetDist = maxTargetDist; + return getPathTo(targetPos, dirList, fpp); +} diff --git a/app/SabrehavenServer/src/creature.h b/app/SabrehavenServer/src/creature.h new file mode 100644 index 0000000..f673066 --- /dev/null +++ b/app/SabrehavenServer/src/creature.h @@ -0,0 +1,556 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CREATURE_H_5363C04015254E298F84E6D59A139508 +#define FS_CREATURE_H_5363C04015254E298F84E6D59A139508 + +#include "map.h" +#include "position.h" +#include "condition.h" +#include "const.h" +#include "tile.h" +#include "enums.h" +#include "creatureevent.h" + +typedef std::list ConditionList; +typedef std::list CreatureEventList; + +enum slots_t : uint8_t { + CONST_SLOT_WHEREEVER = 0, + CONST_SLOT_HEAD = 1, + CONST_SLOT_NECKLACE = 2, + CONST_SLOT_BACKPACK = 3, + CONST_SLOT_ARMOR = 4, + CONST_SLOT_RIGHT = 5, + CONST_SLOT_LEFT = 6, + CONST_SLOT_LEGS = 7, + CONST_SLOT_FEET = 8, + CONST_SLOT_RING = 9, + CONST_SLOT_AMMO = 10, + + CONST_SLOT_FIRST = CONST_SLOT_HEAD, + CONST_SLOT_LAST = CONST_SLOT_AMMO, +}; + +struct FindPathParams { + bool fullPathSearch = true; + bool clearSight = true; + bool allowDiagonal = true; + bool keepDistance = false; + int32_t maxSearchDist = 0; + int32_t minTargetDist = -1; + int32_t maxTargetDist = -1; +}; + +class Map; +class Thing; +class Container; +class Player; +class Monster; +class Npc; +class Item; +class Tile; +class Combat; + +static constexpr int32_t EVENT_CREATURECOUNT = 10; +static constexpr int32_t EVENT_CREATURE_THINK_INTERVAL = 1000; +static constexpr int32_t EVENT_CHECK_CREATURE_INTERVAL = (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT); + +class FrozenPathingConditionCall +{ + public: + explicit FrozenPathingConditionCall(Position targetPos) : targetPos(std::move(targetPos)) {} + + bool operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const; + + bool isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const; + + protected: + Position targetPos; +}; + +////////////////////////////////////////////////////////////////////// +// Defines the Base class for all creatures and base functions which +// every creature has + +class Creature : virtual public Thing +{ + protected: + Creature(); + + public: + virtual ~Creature(); + + // non-copyable + Creature(const Creature&) = delete; + Creature& operator=(const Creature&) = delete; + + Creature* getCreature() final { + return this; + } + const Creature* getCreature() const final { + return this; + } + virtual Player* getPlayer() { + return nullptr; + } + virtual const Player* getPlayer() const { + return nullptr; + } + virtual Npc* getNpc() { + return nullptr; + } + virtual const Npc* getNpc() const { + return nullptr; + } + virtual Monster* getMonster() { + return nullptr; + } + virtual const Monster* getMonster() const { + return nullptr; + } + + virtual const std::string& getName() const = 0; + virtual const std::string& getNameDescription() const = 0; + + virtual void setID() = 0; + void setRemoved() { + isInternalRemoved = true; + } + + uint32_t getID() const { + return id; + } + virtual void removeList() = 0; + virtual void addList() = 0; + + virtual bool canSee(const Position& pos) const; + virtual bool canSeeCreature(const Creature* creature) const; + + virtual RaceType_t getRace() const { + return RACE_NONE; + } + virtual Skulls_t getSkull() const { + return skull; + } + virtual Skulls_t getSkullClient(const Creature* creature) const { + return creature->getSkull(); + } + void setSkull(Skulls_t newSkull); + Direction getDirection() const { + return direction; + } + void setDirection(Direction dir) { + direction = dir; + } + + bool isHealthHidden() const { + return hiddenHealth; + } + void setHiddenHealth(bool b) { + hiddenHealth = b; + } + + int32_t getThrowRange() const final { + return 1; + } + bool isPushable() const override { + return getWalkDelay() <= 0; + } + bool isRemoved() const final { + return isInternalRemoved; + } + virtual bool canSeeInvisibility() const { + return false; + } + virtual bool isInGhostMode() const { + return false; + } + + int32_t getWalkDelay(Direction dir) const; + int32_t getWalkDelay() const; + int64_t getTimeSinceLastMove() const; + + int64_t getEventStepTicks(bool onlyDelay = false) const; + int64_t getStepDuration(Direction dir) const; + int64_t getStepDuration() const; + virtual int32_t getStepSpeed() const { + return getSpeed(); + } + int32_t getSpeed() const { + if (baseSpeed == 0) { + return 0; + } + + return (2 * (varSpeed + baseSpeed)) + 80; + } + void setSpeed(int32_t varSpeedDelta) { + int32_t oldSpeed = getSpeed(); + varSpeed += varSpeedDelta; + + if (getSpeed() <= 0) { + stopEventWalk(); + cancelNextWalk = true; + } else if (oldSpeed <= 0 && !listWalkDir.empty()) { + addEventWalk(); + } + } + + void setBaseSpeed(uint32_t newBaseSpeed) { + baseSpeed = newBaseSpeed; + } + uint32_t getBaseSpeed() const { + return baseSpeed; + } + + int32_t getHealth() const { + return health; + } + virtual int32_t getMaxHealth() const { + return healthMax; + } + + const Outfit_t getCurrentOutfit() const { + return currentOutfit; + } + void setCurrentOutfit(Outfit_t outfit) { + currentOutfit = outfit; + } + const Outfit_t getDefaultOutfit() const { + return defaultOutfit; + } + bool isInvisible() const; + ZoneType_t getZone() const { + return getTile()->getZone(); + } + + //walk functions + void startAutoWalk(const std::forward_list& listDir); + void addEventWalk(bool firstStep = false); + void stopEventWalk(); + virtual void goToFollowCreature(); + + //walk events + virtual void onWalk(Direction& dir); + virtual void onWalkAborted() {} + virtual void onWalkComplete() {} + + //follow functions + Creature* getFollowCreature() const { + return followCreature; + } + virtual bool setFollowCreature(Creature* creature); + + //follow events + virtual void onFollowCreature(const Creature*) {} + virtual void onFollowCreatureComplete(const Creature*) {} + + //combat functions + Creature* getAttackedCreature() { + return attackedCreature; + } + virtual bool setAttackedCreature(Creature* creature); + virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false); + + void setMaster(Creature* creature) { + master = creature; + } + bool isSummon() const { + return master != nullptr; + } + Creature* getMaster() const { + return master; + } + + void addSummon(Creature* creature); + void removeSummon(Creature* creature); + const std::list& getSummons() const { + return summons; + } + + virtual int32_t getArmor() const { + return 0; + } + virtual int32_t getDefense() { + return 0; + } + + bool addCondition(Condition* condition, bool force = false); + bool addCombatCondition(Condition* condition); + void removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force = false); + void removeCondition(ConditionType_t type, bool force = false); + void removeCondition(Condition* condition, bool force = false); + void removeCombatCondition(ConditionType_t type); + Condition* getCondition(ConditionType_t type) const; + Condition* getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId = 0) const; + void executeConditions(uint32_t interval); + bool hasCondition(ConditionType_t type, uint32_t subId = 0) const; + virtual bool isImmune(ConditionType_t type) const; + virtual bool isImmune(CombatType_t type) const; + virtual bool isSuppress(ConditionType_t type) const; + virtual uint32_t getDamageImmunities() const { + return 0; + } + virtual uint32_t getConditionImmunities() const { + return 0; + } + virtual uint32_t getConditionSuppressions() const { + return 0; + } + virtual bool isAttackable() const { + return true; + } + + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + + void gainHealth(Creature* attacker, int32_t healthGain); + virtual void drainHealth(Creature* attacker, int32_t damage); + + virtual bool challengeCreature(Creature*) { + return false; + } + virtual bool convinceCreature(Creature*) { + return false; + } + + void onDeath(); + virtual uint64_t getGainedExperience(Creature* attacker) const; + void addDamagePoints(Creature* attacker, int32_t damagePoints); + bool hasBeenAttacked(uint32_t attackerId); + + //combat event functions + virtual void onAddCondition(ConditionType_t type); + virtual void onAddCombatCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + void onTickCondition(ConditionType_t type, bool& bRemove); + virtual void onCombatRemoveCondition(Condition* condition); + virtual void onAttackedCreature(Creature*) {} + virtual void onAttacked(); + virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); + virtual void onTargetCreatureGainHealth(Creature*, int32_t) {} + virtual bool onKilledCreature(Creature* target, bool lastHit = true); + virtual void onGainExperience(uint64_t gainExp, Creature* target); + virtual void onAttackedCreatureBlockHit(BlockType_t) {} + virtual void onBlockHit() {} + virtual void onChangeZone(ZoneType_t zone); + virtual void onAttackedCreatureChangeZone(ZoneType_t zone); + virtual void onIdleStatus(); + + virtual LightInfo getCreatureLight() const; + virtual void setNormalCreatureLight(); + void setCreatureLight(LightInfo light) { + internalLight = light; + } + + virtual void onThink(uint32_t interval); + void onAttacking(uint32_t interval); + virtual void onWalk(); + virtual bool getNextStep(Direction& dir, uint32_t& flags); + + void onAddTileItem(const Tile* tile, const Position& pos); + virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType); + virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item); + + virtual void onCreatureAppear(Creature* creature, bool isLogin); + virtual void onRemoveCreature(Creature* creature, bool isLogout); + virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void onAttackedCreatureDisappear(bool) {} + virtual void onFollowCreatureDisappear(bool) {} + + virtual void onCreatureSay(Creature*, SpeakClasses, const std::string&) {} + + virtual void onCreatureConvinced(const Creature*, const Creature*) {} + virtual void onPlacedCreature() {} + + virtual bool getCombatValues(int32_t&, int32_t&) { + return false; + } + + size_t getSummonCount() const { + return summons.size(); + } + void setDropLoot(bool lootDrop) { + this->lootDrop = lootDrop; + } + void setSkillLoss(bool skillLoss) { + this->skillLoss = skillLoss; + } + + //creature script events + bool registerCreatureEvent(const std::string& name); + bool unregisterCreatureEvent(const std::string& name); + + Cylinder* getParent() const final { + return tile; + } + void setParent(Cylinder* cylinder) final { + tile = static_cast(cylinder); + position = tile->getPosition(); + } + + inline const Position& getPosition() const final { + return position; + } + + Tile* getTile() final { + return tile; + } + const Tile* getTile() const final { + return tile; + } + + int32_t getWalkCache(const Position& pos) const; + + const Position& getLastPosition() const { + return lastPosition; + } + void setLastPosition(Position newLastPos) { + lastPosition = newLastPos; + } + + static bool canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY); + + double getDamageRatio(Creature* attacker) const; + + bool getPathTo(const Position& targetPos, std::forward_list& dirList, const FindPathParams& fpp) const; + bool getPathTo(const Position& targetPos, std::forward_list& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, int32_t maxSearchDist = 0) const; + + void incrementReferenceCounter() { + ++referenceCounter; + } + void decrementReferenceCounter() { + if (--referenceCounter == 0) { + delete this; + } + } + + protected: + virtual bool useCacheMap() const { + return false; + } + + struct CountBlock_t { + int32_t total; + int64_t ticks; + }; + + static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; + static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; + static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; + static constexpr int32_t maxWalkCacheHeight = (mapWalkHeight - 1) / 2; + + Position position; + + typedef std::map CountMap; + CountMap damageMap; + + std::list summons; + CreatureEventList eventsList; + ConditionList conditions; + + std::forward_list listWalkDir; + + Tile* tile = nullptr; + Creature* attackedCreature = nullptr; + Creature* master = nullptr; + Creature* followCreature = nullptr; + + int64_t earliestDefendTime = 0; + int64_t lastDefendTime = 0; + + uint64_t lastWalkUpdate = 0; + uint64_t lastStep = 0; + uint32_t referenceCounter = 0; + uint32_t id = 0; + uint32_t scriptEventsBitField = 0; + uint32_t eventWalk = 0; + uint32_t walkUpdateTicks = 0; + uint32_t lastHitCreatureId = 0; + uint32_t blockCount = 0; + uint32_t blockTicks = 0; + uint32_t lastStepCost = 1; + uint32_t baseSpeed = 70; + uint32_t latestKillEvent = 0; + int32_t varSpeed = 0; + int32_t health = 1000; + int32_t healthMax = 1000; + + Outfit_t currentOutfit; + Outfit_t defaultOutfit; + + Position lastPosition; + LightInfo internalLight; + + Direction direction = DIRECTION_SOUTH; + Skulls_t skull = SKULL_NONE; + + bool localMapCache[mapWalkHeight][mapWalkWidth] = {{ false }}; + bool isInternalRemoved = false; + bool isMapLoaded = false; + bool isUpdatingPath = false; + bool creatureCheck = false; + bool inCheckCreaturesVector = false; + bool skillLoss = true; + bool lootDrop = true; + bool cancelNextWalk = false; + bool hasFollowPath = false; + bool forceUpdateFollowPath = false; + bool hiddenHealth = false; + + //creature script events + bool hasEventRegistered(CreatureEventType_t event) const { + return (0 != (scriptEventsBitField & (static_cast(1) << event))); + } + CreatureEventList getCreatureEvents(CreatureEventType_t type); + + void updateMapCache(); + void updateTileCache(const Tile* tile, int32_t dx, int32_t dy); + void updateTileCache(const Tile* tile, const Position& pos); + void onCreatureDisappear(const Creature* creature, bool isLogout); + virtual void doAttacking(uint32_t) {} + virtual bool hasExtraSwing() { + return false; + } + + virtual uint64_t getLostExperience() const { + return 0; + } + virtual void dropLoot(Container*, Creature*) {} + virtual uint16_t getLookCorpse() const { + return 0; + } + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + virtual void death(Creature*) {} + virtual bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified); + virtual Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature); + + friend class Game; + friend class Map; + friend class LuaScriptInterface; + friend class Combat; +}; + +#endif diff --git a/app/SabrehavenServer/src/creatureevent.cpp b/app/SabrehavenServer/src/creatureevent.cpp new file mode 100644 index 0000000..eb72b28 --- /dev/null +++ b/app/SabrehavenServer/src/creatureevent.cpp @@ -0,0 +1,523 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "creatureevent.h" +#include "tools.h" +#include "player.h" + +CreatureEvents::CreatureEvents() : + scriptInterface("CreatureScript Interface") +{ + scriptInterface.initState(); +} + +CreatureEvents::~CreatureEvents() +{ + for (const auto& it : creatureEvents) { + delete it.second; + } +} + +void CreatureEvents::clear() +{ + //clear creature events + for (const auto& it : creatureEvents) { + it.second->clearEvent(); + } + + //clear lua state + scriptInterface.reInitState(); +} + +LuaScriptInterface& CreatureEvents::getScriptInterface() +{ + return scriptInterface; +} + +std::string CreatureEvents::getScriptBaseName() const +{ + return "creaturescripts"; +} + +Event* CreatureEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "event") != 0) { + return nullptr; + } + return new CreatureEvent(&scriptInterface); +} + +bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) +{ + CreatureEvent* creatureEvent = static_cast(event); //event is guaranteed to be a CreatureEvent + if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { + std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl; + return false; + } + + CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); + if (oldEvent) { + //if there was an event with the same that is not loaded + //(happens when realoading), it is reused + if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { + oldEvent->copyEvent(creatureEvent); + } + + return false; + } else { + //if not, register it normally + creatureEvents[creatureEvent->getName()] = creatureEvent; + return true; + } +} + +CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/) +{ + auto it = creatureEvents.find(name); + if (it != creatureEvents.end()) { + if (!forceLoaded || it->second->isLoaded()) { + return it->second; + } + } + return nullptr; +} + +bool CreatureEvents::playerLogin(Player* player) const +{ + //fire global event if is registered + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_LOGIN) { + if (!it.second->executeOnLogin(player)) { + return false; + } + } + } + return true; +} + +bool CreatureEvents::playerLogout(Player* player) const +{ + //fire global event if is registered + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_LOGOUT) { + if (!it.second->executeOnLogout(player)) { + return false; + } + } + } + return true; +} + +bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_ADVANCE) { + if (!it.second->executeAdvance(player, skill, oldLevel, newLevel)) { + return false; + } + } + } + return true; +} + +///////////////////////////////////// + +CreatureEvent::CreatureEvent(LuaScriptInterface* interface) : + Event(interface), type(CREATURE_EVENT_NONE), loaded(false) {} + +bool CreatureEvent::configureEvent(const pugi::xml_node& node) +{ + // Name that will be used in monster xml files and + // lua function to register events to reference this event + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - CreatureEvent::configureEvent] Missing name for creature event" << std::endl; + return false; + } + + eventName = nameAttribute.as_string(); + + pugi::xml_attribute typeAttribute = node.attribute("type"); + if (!typeAttribute) { + std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName << std::endl; + return false; + } + + std::string tmpStr = asLowerCaseString(typeAttribute.as_string()); + if (tmpStr == "login") { + type = CREATURE_EVENT_LOGIN; + } else if (tmpStr == "logout") { + type = CREATURE_EVENT_LOGOUT; + } else if (tmpStr == "think") { + type = CREATURE_EVENT_THINK; + } else if (tmpStr == "preparedeath") { + type = CREATURE_EVENT_PREPAREDEATH; + } else if (tmpStr == "death") { + type = CREATURE_EVENT_DEATH; + } else if (tmpStr == "kill") { + type = CREATURE_EVENT_KILL; + } else if (tmpStr == "advance") { + type = CREATURE_EVENT_ADVANCE; + } else if (tmpStr == "healthchange") { + type = CREATURE_EVENT_HEALTHCHANGE; + } else if (tmpStr == "manachange") { + type = CREATURE_EVENT_MANACHANGE; + } else if (tmpStr == "extendedopcode") { + type = CREATURE_EVENT_EXTENDED_OPCODE; + } else { + std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName << std::endl; + return false; + } + + loaded = true; + return true; +} + +std::string CreatureEvent::getScriptEventName() const +{ + //Depending on the type script event name is different + switch (type) { + case CREATURE_EVENT_LOGIN: + return "onLogin"; + + case CREATURE_EVENT_LOGOUT: + return "onLogout"; + + case CREATURE_EVENT_THINK: + return "onThink"; + + case CREATURE_EVENT_PREPAREDEATH: + return "onPrepareDeath"; + + case CREATURE_EVENT_DEATH: + return "onDeath"; + + case CREATURE_EVENT_KILL: + return "onKill"; + + case CREATURE_EVENT_ADVANCE: + return "onAdvance"; + + case CREATURE_EVENT_HEALTHCHANGE: + return "onHealthChange"; + + case CREATURE_EVENT_MANACHANGE: + return "onManaChange"; + + case CREATURE_EVENT_EXTENDED_OPCODE: + return "onExtendedOpcode"; + + case CREATURE_EVENT_NONE: + default: + return std::string(); + } +} + +void CreatureEvent::copyEvent(CreatureEvent* creatureEvent) +{ + scriptId = creatureEvent->scriptId; + scriptInterface = creatureEvent->scriptInterface; + scripted = creatureEvent->scripted; + loaded = creatureEvent->loaded; +} + +void CreatureEvent::clearEvent() +{ + scriptId = 0; + scriptInterface = nullptr; + scripted = false; + loaded = false; +} + +bool CreatureEvent::executeOnLogin(Player* player) +{ + //onLogin(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnLogin] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + return scriptInterface->callFunction(1); +} + +bool CreatureEvent::executeOnLogout(Player* player) +{ + //onLogout(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnLogout] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + return scriptInterface->callFunction(1); +} + +bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) +{ + //onThink(creature, interval) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnThink] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + lua_pushnumber(L, interval); + + return scriptInterface->callFunction(2); +} + +bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer) +{ + //onPrepareDeath(creature, killer) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (killer) { + LuaScriptInterface::pushUserdata(L, killer); + LuaScriptInterface::setCreatureMetatable(L, -1, killer); + } else { + lua_pushnil(L); + } + + return scriptInterface->callFunction(2); +} + +bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + //onDeath(creature, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnDeath] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushThing(L, corpse); + + if (killer) { + LuaScriptInterface::pushUserdata(L, killer); + LuaScriptInterface::setCreatureMetatable(L, -1, killer); + } else { + lua_pushnil(L); + } + + if (mostDamageKiller) { + LuaScriptInterface::pushUserdata(L, mostDamageKiller); + LuaScriptInterface::setCreatureMetatable(L, -1, mostDamageKiller); + } else { + lua_pushnil(L); + } + + LuaScriptInterface::pushBoolean(L, lastHitUnjustified); + LuaScriptInterface::pushBoolean(L, mostDamageUnjustified); + + return scriptInterface->callFunction(6); +} + +bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + //onAdvance(player, skill, oldLevel, newLevel) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeAdvance] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + lua_pushnumber(L, static_cast(skill)); + lua_pushnumber(L, oldLevel); + lua_pushnumber(L, newLevel); + + return scriptInterface->callFunction(4); +} + +void CreatureEvent::executeOnKill(Creature* creature, Creature* target) +{ + //onKill(creature, target) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + scriptInterface->callVoidFunction(2); +} + +void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage) +{ + //onHealthChange(creature, attacker, value, type, min, max, origin) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeHealthChange] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + if (attacker) { + LuaScriptInterface::pushUserdata(L, attacker); + LuaScriptInterface::setCreatureMetatable(L, -1, attacker); + } + else { + lua_pushnil(L); + } + + LuaScriptInterface::pushCombatDamage(L, damage); + + if (scriptInterface->protectedCall(L, 7, 4) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + damage.value = std::abs(LuaScriptInterface::getNumber(L, -4)); + damage.type = LuaScriptInterface::getNumber(L, -3); + damage.min = std::abs(LuaScriptInterface::getNumber(L, -2)); + damage.max = LuaScriptInterface::getNumber(L, -1); + + lua_pop(L, 4); + if (damage.type != COMBAT_HEALING) { + damage.value = -damage.value; + } + } + + scriptInterface->resetScriptEnv(); +} + +void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) { + //onManaChange(creature, attacker, value, type, min, max, origin) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeManaChange] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + if (attacker) { + LuaScriptInterface::pushUserdata(L, attacker); + LuaScriptInterface::setCreatureMetatable(L, -1, attacker); + } + else { + lua_pushnil(L); + } + + LuaScriptInterface::pushCombatDamage(L, damage); + + if (scriptInterface->protectedCall(L, 7, 4) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + damage = LuaScriptInterface::getCombatDamage(L); + } + + scriptInterface->resetScriptEnv(); +} + +void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer) +{ + //onExtendedOpcode(player, opcode, buffer) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeExtendedOpcode] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, opcode); + LuaScriptInterface::pushString(L, buffer); + + scriptInterface->callVoidFunction(3); +} diff --git a/app/SabrehavenServer/src/creatureevent.h b/app/SabrehavenServer/src/creatureevent.h new file mode 100644 index 0000000..6dbdeac --- /dev/null +++ b/app/SabrehavenServer/src/creatureevent.h @@ -0,0 +1,115 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 +#define FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 + +#include "luascript.h" +#include "baseevents.h" +#include "enums.h" + +enum CreatureEventType_t { + CREATURE_EVENT_NONE, + CREATURE_EVENT_LOGIN, + CREATURE_EVENT_LOGOUT, + CREATURE_EVENT_THINK, + CREATURE_EVENT_PREPAREDEATH, + CREATURE_EVENT_DEATH, + CREATURE_EVENT_KILL, + CREATURE_EVENT_ADVANCE, + CREATURE_EVENT_HEALTHCHANGE, + CREATURE_EVENT_MANACHANGE, + CREATURE_EVENT_EXTENDED_OPCODE, // otclient additional network opcodes +}; + +class CreatureEvent; + +class CreatureEvents final : public BaseEvents +{ + public: + CreatureEvents(); + ~CreatureEvents(); + + // non-copyable + CreatureEvents(const CreatureEvents&) = delete; + CreatureEvents& operator=(const CreatureEvents&) = delete; + + // global events + bool playerLogin(Player* player) const; + bool playerLogout(Player* player) const; + bool playerAdvance(Player* player, skills_t, uint32_t, uint32_t); + + CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); + + protected: + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + void clear() final; + + //creature events + typedef std::map CreatureEventList; + CreatureEventList creatureEvents; + + LuaScriptInterface scriptInterface; +}; + +class CreatureEvent final : public Event +{ + public: + explicit CreatureEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) final; + + CreatureEventType_t getEventType() const { + return type; + } + const std::string& getName() const { + return eventName; + } + bool isLoaded() const { + return loaded; + } + + void clearEvent(); + void copyEvent(CreatureEvent* creatureEvent); + + //scripting + bool executeOnLogin(Player* player); + bool executeOnLogout(Player* player); + bool executeOnThink(Creature* creature, uint32_t interval); + bool executeOnPrepareDeath(Creature* creature, Creature* killer); + bool executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified); + void executeOnKill(Creature* creature, Creature* target); + bool executeAdvance(Player* player, skills_t, uint32_t, uint32_t); + void executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); + // + + protected: + std::string getScriptEventName() const final; + + std::string eventName; + CreatureEventType_t type; + bool loaded; +}; + +#endif diff --git a/app/SabrehavenServer/src/cylinder.cpp b/app/SabrehavenServer/src/cylinder.cpp new file mode 100644 index 0000000..406ca9a --- /dev/null +++ b/app/SabrehavenServer/src/cylinder.cpp @@ -0,0 +1,69 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "cylinder.h" + +VirtualCylinder* VirtualCylinder::virtualCylinder = new VirtualCylinder; + +int32_t Cylinder::getThingIndex(const Thing*) const +{ + return -1; +} + +size_t Cylinder::getFirstIndex() const +{ + return 0; +} + +size_t Cylinder::getLastIndex() const +{ + return 0; +} + +uint32_t Cylinder::getItemTypeCount(uint16_t, int32_t) const +{ + return 0; +} + +std::map& Cylinder::getAllItemTypeCount(std::map& countMap) const +{ + return countMap; +} + +Thing* Cylinder::getThing(size_t) const +{ + return nullptr; +} + +void Cylinder::internalAddThing(Thing*) +{ + // +} + +void Cylinder::internalAddThing(uint32_t, Thing*) +{ + // +} + +void Cylinder::startDecaying() +{ + // +} diff --git a/app/SabrehavenServer/src/cylinder.h b/app/SabrehavenServer/src/cylinder.h new file mode 100644 index 0000000..872609c --- /dev/null +++ b/app/SabrehavenServer/src/cylinder.h @@ -0,0 +1,250 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 +#define FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 + +#include "enums.h" +#include "thing.h" + +class Item; +class Creature; + +static constexpr int32_t INDEX_WHEREEVER = -1; + +enum cylinderflags_t { + FLAG_NOLIMIT = 1 << 0, //Bypass limits like capacity/container limits, blocking items/creatures etc. + FLAG_IGNOREBLOCKITEM = 1 << 1, //Bypass movable blocking item checks + FLAG_IGNOREBLOCKCREATURE = 1 << 2, //Bypass creature checks + FLAG_CHILDISOWNER = 1 << 3, //Used by containers to query capacity of the carrier (player) + FLAG_PATHFINDING = 1 << 4, //An additional check is done for floor changing/teleport items + FLAG_IGNOREFIELDDAMAGE = 1 << 5, //Bypass field damage checks + FLAG_IGNORENOTMOVEABLE = 1 << 6, //Bypass check for mobility + FLAG_IGNOREAUTOSTACK = 1 << 7, //queryDestination will not try to stack items together + FLAG_PLACECHECK = 1 << 8, //Special check for placing the monster +}; + +enum cylinderlink_t { + LINK_OWNER, + LINK_PARENT, + LINK_TOPPARENT, + LINK_NEAR, +}; + +class Cylinder : virtual public Thing +{ + public: + /** + * Query if the cylinder can add an object + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param thing the object to move/add + * \param count is the amount that we want to move/add + * \param flags if FLAG_CHILDISOWNER if set the query is from a child-cylinder (check cap etc.) + * if FLAG_NOLIMIT is set blocking items/container limits is ignored + * \param actor the creature trying to add the thing + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const = 0; + + /** + * Query the cylinder how much it can accept + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param thing the object to move/add + * \param count is the amount that we want to move/add + * \param maxQueryCount is the max amount that the cylinder can accept + * \param flags optional flags to modify the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const = 0; + + /** + * Query if the cylinder can remove an object + * \param thing the object to move/remove + * \param count is the amount that we want to remove + * \param flags optional flags to modify the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const = 0; + + /** + * Query the destination cylinder + * \param index points to the destination index (inventory slot/container position), + * -1 is a internal value and means add to a empty position, with no destItem + * this method can change the index to point to the new cylinder index + * \param destItem is the destination object + * \param flags optional flags to modify the default behaviour + * this method can modify the flags + * \returns Cylinder returns the destination cylinder + */ + virtual Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) = 0; + + /** + * Add the object to the cylinder + * \param thing is the object to add + */ + virtual void addThing(Thing* thing) = 0; + + /** + * Add the object to the cylinder + * \param index points to the destination index (inventory slot/container position) + * \param thing is the object to add + */ + virtual void addThing(int32_t index, Thing* thing) = 0; + + /** + * Update the item count or type for an object + * \param thing is the object to update + * \param itemId is the new item id + * \param count is the new count value + */ + virtual void updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0; + + /** + * Replace an object with a new + * \param index is the position to change (inventory slot/container position) + * \param thing is the object to update + */ + virtual void replaceThing(uint32_t index, Thing* thing) = 0; + + /** + * Remove an object + * \param thing is the object to delete + * \param count is the new count value + */ + virtual void removeThing(Thing* thing, uint32_t count) = 0; + + /** + * Is sent after an operation (move/add) to update internal values + * \param thing is the object that has been added + * \param index is the objects new index value + * \param link holds the relation the object has to the cylinder + */ + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Is sent after an operation (move/remove) to update internal values + * \param thing is the object that has been removed + * \param index is the previous index of the removed object + * \param link holds the relation the object has to the cylinder + */ + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Gets the index of an object + * \param thing the object to get the index value from + * \returns the index of the object, returns -1 if not found + */ + virtual int32_t getThingIndex(const Thing* thing) const; + + /** + * Returns the first index + * \returns the first index, if not implemented 0 is returned + */ + virtual size_t getFirstIndex() const; + + /** + * Returns the last index + * \returns the last index, if not implemented 0 is returned + */ + virtual size_t getLastIndex() const; + + /** + * Gets the object based on index + * \returns the object, returns nullptr if not found + */ + virtual Thing* getThing(size_t index) const; + + /** + * Get the amount of items of a certain type + * \param itemId is the item type to the get the count of + * \param subType is the extra type an item can have such as charges/fluidtype, -1 means not used + * \returns the amount of items of the asked item type + */ + virtual uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + + /** + * Get the amount of items of a all types + * \param countMap a map to put the itemID:count mapping in + * \returns a map mapping item id to count (same as first argument) + */ + virtual std::map& getAllItemTypeCount(std::map& countMap) const; + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + */ + virtual void internalAddThing(Thing* thing); + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + * \param index points to the destination index (inventory slot/container position) + */ + virtual void internalAddThing(uint32_t index, Thing* thing); + + virtual void startDecaying(); +}; + +class VirtualCylinder final : public Cylinder +{ + public: + static VirtualCylinder* virtualCylinder; + + virtual ReturnValue queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryRemove(const Thing&, uint32_t, uint32_t) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual Cylinder* queryDestination(int32_t&, const Thing&, Item**, uint32_t&) override { + return nullptr; + } + + virtual void addThing(Thing*) override {} + virtual void addThing(int32_t, Thing*) override {} + virtual void updateThing(Thing*, uint16_t, uint32_t) override {} + virtual void replaceThing(uint32_t, Thing*) override {} + virtual void removeThing(Thing*, uint32_t) override {} + + virtual void postAddNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + virtual void postRemoveNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + + bool isPushable() const override { + return false; + } + int32_t getThrowRange() const override { + return 1; + } + std::string getDescription(int32_t) const override { + return {}; + } + bool isRemoved() const override { + return false; + } +}; + +#endif diff --git a/app/SabrehavenServer/src/database.cpp b/app/SabrehavenServer/src/database.cpp new file mode 100644 index 0000000..0dfc590 --- /dev/null +++ b/app/SabrehavenServer/src/database.cpp @@ -0,0 +1,295 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "database.h" + +#include + +extern ConfigManager g_config; + +Database::~Database() +{ + if (handle != nullptr) { + mysql_close(handle); + } +} + +bool Database::connect() +{ + // connection handle initialization + handle = mysql_init(nullptr); + if (!handle) { + std::cout << std::endl << "Failed to initialize MySQL connection handle." << std::endl; + return false; + } + + // automatic reconnect + bool reconnect = true; + mysql_options(handle, MYSQL_OPT_RECONNECT, &reconnect); + + // connects to database + if (!mysql_real_connect(handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), g_config.getString(ConfigManager::MYSQL_USER).c_str(), g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), g_config.getNumber(ConfigManager::SQL_PORT), g_config.getString(ConfigManager::MYSQL_SOCK).c_str(), 0)) { + std::cout << std::endl << "MySQL Error Message: " << mysql_error(handle) << std::endl; + return false; + } + + DBResult_ptr result = storeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + if (result) { + maxPacketSize = result->getNumber("Value"); + } + return true; +} + +bool Database::beginTransaction() +{ + if (!executeQuery("BEGIN")) { + return false; + } + + databaseLock.lock(); + return true; +} + +bool Database::rollback() +{ + if (mysql_rollback(handle) != 0) { + std::cout << "[Error - mysql_rollback] Message: " << mysql_error(handle) << std::endl; + databaseLock.unlock(); + return false; + } + + databaseLock.unlock(); + return true; +} + +bool Database::commit() +{ + if (mysql_commit(handle) != 0) { + std::cout << "[Error - mysql_commit] Message: " << mysql_error(handle) << std::endl; + databaseLock.unlock(); + return false; + } + + databaseLock.unlock(); + return true; +} + +bool Database::executeQuery(const std::string& query) +{ + bool success = true; + + // executes the query + databaseLock.lock(); + + while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { + std::cout << "[Error - mysql_real_query] Query: " << query.substr(0, 256) << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + success = false; + break; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + MYSQL_RES* m_res = mysql_store_result(handle); + databaseLock.unlock(); + + if (m_res) { + mysql_free_result(m_res); + } + + return success; +} + +DBResult_ptr Database::storeQuery(const std::string& query) +{ + databaseLock.lock(); + + retry: + while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { + std::cout << "[Error - mysql_real_query] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + break; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // we should call that every time as someone would call executeQuery('SELECT...') + // as it is described in MySQL manual: "it doesn't hurt" :P + MYSQL_RES* res = mysql_store_result(handle); + if (res == nullptr) { + std::cout << "[Error - mysql_store_result] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + databaseLock.unlock(); + return nullptr; + } + goto retry; + } + databaseLock.unlock(); + + // retrieving results of query + DBResult_ptr result = std::make_shared(res); + if (!result->hasNext()) { + return nullptr; + } + return result; +} + +std::string Database::escapeString(const std::string& s) const +{ + return escapeBlob(s.c_str(), s.length()); +} + +std::string Database::escapeBlob(const char* s, uint32_t length) const +{ + // the worst case is 2n + 1 + size_t maxLength = (length * 2) + 1; + + std::string escaped; + escaped.reserve(maxLength + 2); + escaped.push_back('\''); + + if (length != 0) { + char* output = new char[maxLength]; + mysql_real_escape_string(handle, output, s, length); + escaped.append(output); + delete[] output; + } + + escaped.push_back('\''); + return escaped; +} + +DBResult::DBResult(MYSQL_RES* res) +{ + handle = res; + + size_t i = 0; + + MYSQL_FIELD* field = mysql_fetch_field(handle); + while (field) { + listNames[field->name] = i++; + field = mysql_fetch_field(handle); + } + + row = mysql_fetch_row(handle); +} + +DBResult::~DBResult() +{ + mysql_free_result(handle); +} + +std::string DBResult::getString(const std::string& s) const +{ + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getString] Column '" << s << "' does not exist in result set." << std::endl; + return std::string(); + } + + if (row[it->second] == nullptr) { + return std::string(); + } + + return std::string(row[it->second]); +} + +const char* DBResult::getStream(const std::string& s, unsigned long& size) const +{ + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getStream] Column '" << s << "' doesn't exist in the result set" << std::endl; + size = 0; + return nullptr; + } + + if (row[it->second] == nullptr) { + size = 0; + return nullptr; + } + + size = mysql_fetch_lengths(handle)[it->second]; + return row[it->second]; +} + +bool DBResult::hasNext() const +{ + return row != nullptr; +} + +bool DBResult::next() +{ + row = mysql_fetch_row(handle); + return row != nullptr; +} + +DBInsert::DBInsert(std::string query) : query(std::move(query)) +{ + this->length = this->query.length(); +} + +bool DBInsert::addRow(const std::string& row) +{ + // adds new row to buffer + const size_t rowLength = row.length(); + length += rowLength; + if (length > Database::getInstance()->getMaxPacketSize() && !execute()) { + return false; + } + + if (values.empty()) { + values.reserve(rowLength + 2); + values.push_back('('); + values.append(row); + values.push_back(')'); + } else { + values.reserve(values.length() + rowLength + 3); + values.push_back(','); + values.push_back('('); + values.append(row); + values.push_back(')'); + } + return true; +} + +bool DBInsert::addRow(std::ostringstream& row) +{ + bool ret = addRow(row.str()); + row.str(std::string()); + return ret; +} + +bool DBInsert::execute() +{ + if (values.empty()) { + return true; + } + + // executes buffer + bool res = Database::getInstance()->executeQuery(query + values); + values.clear(); + length = query.length(); + return res; +} diff --git a/app/SabrehavenServer/src/database.h b/app/SabrehavenServer/src/database.h new file mode 100644 index 0000000..e0168ba --- /dev/null +++ b/app/SabrehavenServer/src/database.h @@ -0,0 +1,243 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 +#define FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 + +#include + +#include + +class DBResult; +typedef std::shared_ptr DBResult_ptr; + +class Database +{ + public: + Database() = default; + ~Database(); + + // non-copyable + Database(const Database&) = delete; + Database& operator=(const Database&) = delete; + + /** + * Singleton implementation. + * + * @return database connection handler singleton + */ + static Database* getInstance() + { + static Database instance; + return &instance; + } + + /** + * Connects to the database + * + * @return true on successful connection, false on error + */ + bool connect(); + + /** + * Executes command. + * + * Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...). + * + * @param query command + * @return true on success, false on error + */ + bool executeQuery(const std::string& query); + + /** + * Queries database. + * + * Executes query which generates results (mostly SELECT). + * + * @return results object (nullptr on error) + */ + DBResult_ptr storeQuery(const std::string& query); + + /** + * Escapes string for query. + * + * Prepares string to fit SQL queries including quoting it. + * + * @param s string to be escaped + * @return quoted string + */ + std::string escapeString(const std::string& s) const; + + /** + * Escapes binary stream for query. + * + * Prepares binary stream to fit SQL queries. + * + * @param s binary stream + * @param length stream length + * @return quoted string + */ + std::string escapeBlob(const char* s, uint32_t length) const; + + /** + * Retrieve id of last inserted row + * + * @return id on success, 0 if last query did not result on any rows with auto_increment keys + */ + uint64_t getLastInsertId() const { + return static_cast(mysql_insert_id(handle)); + } + + /** + * Get database engine version + * + * @return the database engine version + */ + static const char* getClientVersion() { + return mysql_get_client_info(); + } + + uint64_t getMaxPacketSize() const { + return maxPacketSize; + } + + protected: + /** + * Transaction related methods. + * + * Methods for starting, commiting and rolling back transaction. Each of the returns boolean value. + * + * @return true on success, false on error + */ + bool beginTransaction(); + bool rollback(); + bool commit(); + + private: + MYSQL* handle = nullptr; + std::recursive_mutex databaseLock; + uint64_t maxPacketSize = 1048576; + + friend class DBTransaction; +}; + +class DBResult +{ + public: + explicit DBResult(MYSQL_RES* res); + ~DBResult(); + + // non-copyable + DBResult(const DBResult&) = delete; + DBResult& operator=(const DBResult&) = delete; + + template + T getNumber(const std::string& s) const + { + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" << std::endl; + return static_cast(0); + } + + if (row[it->second] == nullptr) { + return static_cast(0); + } + + T data; + try { + data = boost::lexical_cast(row[it->second]); + } catch (boost::bad_lexical_cast&) { + data = 0; + } + return data; + } + + std::string getString(const std::string& s) const; + const char* getStream(const std::string& s, unsigned long& size) const; + + bool hasNext() const; + bool next(); + + private: + MYSQL_RES* handle; + MYSQL_ROW row; + + std::map listNames; + + friend class Database; +}; + +/** + * INSERT statement. + */ +class DBInsert +{ + public: + explicit DBInsert(std::string query); + bool addRow(const std::string& row); + bool addRow(std::ostringstream& row); + bool execute(); + + protected: + std::string query; + std::string values; + size_t length; +}; + +class DBTransaction +{ + public: + constexpr DBTransaction() = default; + + ~DBTransaction() { + if (state == STATE_START) { + Database::getInstance()->rollback(); + } + } + + // non-copyable + DBTransaction(const DBTransaction&) = delete; + DBTransaction& operator=(const DBTransaction&) = delete; + + bool begin() { + state = STATE_START; + return Database::getInstance()->beginTransaction(); + } + + bool commit() { + if (state != STATE_START) { + return false; + } + + state = STEATE_COMMIT; + return Database::getInstance()->commit(); + } + + private: + enum TransactionStates_t { + STATE_NO_START, + STATE_START, + STEATE_COMMIT, + }; + + TransactionStates_t state = STATE_NO_START; +}; + +#endif diff --git a/app/SabrehavenServer/src/databasemanager.cpp b/app/SabrehavenServer/src/databasemanager.cpp new file mode 100644 index 0000000..dc38b37 --- /dev/null +++ b/app/SabrehavenServer/src/databasemanager.cpp @@ -0,0 +1,117 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "databasemanager.h" +#include "luascript.h" + +extern ConfigManager g_config; + +bool DatabaseManager::optimizeTables() +{ + Database* db = Database::getInstance(); + std::ostringstream query; + + query << "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `DATA_FREE` > 0"; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + do { + std::string tableName = result->getString("TABLE_NAME"); + std::cout << "> Optimizing table " << tableName << "..." << std::flush; + + query.str(std::string()); + query << "OPTIMIZE TABLE `" << tableName << '`'; + + if (db->executeQuery(query.str())) { + std::cout << " [success]" << std::endl; + } else { + std::cout << " [failed]" << std::endl; + } + } while (result->next()); + return true; +} + +bool DatabaseManager::tableExists(const std::string& tableName) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TABLE_NAME` = " << db->escapeString(tableName) << " LIMIT 1"; + return db->storeQuery(query.str()).get() != nullptr; +} + +bool DatabaseManager::isDatabaseSetup() +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)); + return db->storeQuery(query.str()).get() != nullptr; +} + +int32_t DatabaseManager::getDatabaseVersion() +{ + if (!tableExists("server_config")) { + Database* db = Database::getInstance(); + db->executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); + db->executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)"); + return 0; + } + + int32_t version = 0; + if (getDatabaseConfig("db_version", version)) { + return version; + } + return -1; +} + +bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + value = result->getNumber("value"); + return true; +} + +void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + + int32_t tmp; + + if (!getDatabaseConfig(config, tmp)) { + query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')"; + } else { + query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config); + } + + db->executeQuery(query.str()); +} diff --git a/app/SabrehavenServer/src/databasemanager.h b/app/SabrehavenServer/src/databasemanager.h new file mode 100644 index 0000000..d15f1ec --- /dev/null +++ b/app/SabrehavenServer/src/databasemanager.h @@ -0,0 +1,37 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C +#define FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C +#include "database.h" + +class DatabaseManager +{ + public: + static bool tableExists(const std::string& table); + + static int32_t getDatabaseVersion(); + static bool isDatabaseSetup(); + + static bool optimizeTables(); + + static bool getDatabaseConfig(const std::string& config, int32_t& value); + static void registerDatabaseConfig(const std::string& config, int32_t value); +}; +#endif diff --git a/app/SabrehavenServer/src/databasetasks.cpp b/app/SabrehavenServer/src/databasetasks.cpp new file mode 100644 index 0000000..37f1176 --- /dev/null +++ b/app/SabrehavenServer/src/databasetasks.cpp @@ -0,0 +1,101 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "databasetasks.h" +#include "tasks.h" + +extern Dispatcher g_dispatcher; + + +void DatabaseTasks::start() +{ + db.connect(); + ThreadHolder::start(); +} + +void DatabaseTasks::threadMain() +{ + std::unique_lock taskLockUnique(taskLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { + taskLockUnique.lock(); + if (tasks.empty()) { + taskSignal.wait(taskLockUnique); + } + + if (!tasks.empty()) { + DatabaseTask task = std::move(tasks.front()); + tasks.pop_front(); + taskLockUnique.unlock(); + runTask(task); + } else { + taskLockUnique.unlock(); + } + } +} + +void DatabaseTasks::addTask(const std::string& query, const std::function& callback/* = nullptr*/, bool store/* = false*/) +{ + bool signal = false; + taskLock.lock(); + if (getState() == THREAD_STATE_RUNNING) { + signal = tasks.empty(); + tasks.emplace_back(query, callback, store); + } + taskLock.unlock(); + + if (signal) { + taskSignal.notify_one(); + } +} + +void DatabaseTasks::runTask(const DatabaseTask& task) +{ + bool success; + DBResult_ptr result; + if (task.store) { + result = db.storeQuery(task.query); + success = true; + } else { + result = nullptr; + success = db.executeQuery(task.query); + } + + if (task.callback) { + g_dispatcher.addTask(createTask(std::bind(task.callback, result, success))); + } +} + +void DatabaseTasks::flush() +{ + while (!tasks.empty()) { + runTask(tasks.front()); + tasks.pop_front(); + } +} + +void DatabaseTasks::shutdown() +{ + taskLock.lock(); + setState(THREAD_STATE_TERMINATED); + flush(); + taskLock.unlock(); + taskSignal.notify_one(); +} diff --git a/app/SabrehavenServer/src/databasetasks.h b/app/SabrehavenServer/src/databasetasks.h new file mode 100644 index 0000000..42b36e2 --- /dev/null +++ b/app/SabrehavenServer/src/databasetasks.h @@ -0,0 +1,60 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 +#define FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 + +#include +#include "thread_holder_base.h" +#include "database.h" +#include "enums.h" + +struct DatabaseTask { + DatabaseTask(std::string query, std::function callback, bool store) : + query(std::move(query)), callback(std::move(callback)), store(store) {} + + std::string query; + std::function callback; + bool store; +}; + +class DatabaseTasks : public ThreadHolder +{ + public: + DatabaseTasks() = default; + void start(); + void flush(); + void shutdown(); + + void addTask(const std::string& query, const std::function& callback = nullptr, bool store = false); + + void threadMain(); + private: + void runTask(const DatabaseTask& task); + + Database db; + std::thread thread; + std::list tasks; + std::mutex taskLock; + std::condition_variable taskSignal; +}; + +extern DatabaseTasks g_databaseTasks; + +#endif diff --git a/app/SabrehavenServer/src/definitions.h b/app/SabrehavenServer/src/definitions.h new file mode 100644 index 0000000..bab0b0e --- /dev/null +++ b/app/SabrehavenServer/src/definitions.h @@ -0,0 +1,75 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 +#define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 + +static constexpr auto STATUS_SERVER_NAME = "Sabrehaven"; +static constexpr auto STATUS_SERVER_VERSION = "1.0"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "OTLand community & Sabrehaven Developers Team"; + +static constexpr auto AUTHENTICATOR_DIGITS = 6U; +static constexpr auto AUTHENTICATOR_PERIOD = 30U; + +#ifndef __FUNCTION__ +#define __FUNCTION__ __func__ +#endif + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +#include + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#define WIN32_LEAN_AND_MEAN + +#ifdef _MSC_VER +#ifdef NDEBUG +#define _SECURE_SCL 0 +#define HAS_ITERATOR_DEBUGGING 0 +#endif + +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:4244) // 'argument' : conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4250) // 'class1' : inherits 'class2::member' via dominance +#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data +#pragma warning(disable:4351) // new behavior: elements of array will be default initialized +#pragma warning(disable:4458) // declaration hides class member +#pragma warning(disable:4996) // inetpton warning +#endif + +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +#ifndef _WIN32_WINNT +// 0x0602: Windows 7 +#define _WIN32_WINNT 0x0602 +#endif +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#endif diff --git a/app/SabrehavenServer/src/depotlocker.cpp b/app/SabrehavenServer/src/depotlocker.cpp new file mode 100644 index 0000000..09189a1 --- /dev/null +++ b/app/SabrehavenServer/src/depotlocker.cpp @@ -0,0 +1,89 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "depotlocker.h" +#include "creature.h" +#include "player.h" +#include "tools.h" + +DepotLocker::DepotLocker(uint16_t type) : + Container(type, 30), depotId(0) {} + +Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_DEPOT_ID) { + if (!propStream.read(depotId)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +ReturnValue DepotLocker::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + if (!skipLimit) { + int32_t addCount = 0; + + if ((item->isStackable() && item->getItemCount() != count)) { + addCount = 1; + } + + if (item->getTopParent() != this) { + if (const Container* container = item->getContainer()) { + addCount = container->getItemHoldingCount() + 1; + } else { + addCount = 1; + } + } + + if (actor) { + Player* player = actor->getPlayer(); + if (player) { + if (getItemHoldingCount() + addCount > player->getMaxDepotItems()) { + return RETURNVALUE_DEPOTISFULL; + } + } + } + } + + return Container::queryAdd(index, thing, count, flags, actor); +} + +void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + if (parent != nullptr) { + parent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + if (parent != nullptr) { + parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} diff --git a/app/SabrehavenServer/src/depotlocker.h b/app/SabrehavenServer/src/depotlocker.h new file mode 100644 index 0000000..094defc --- /dev/null +++ b/app/SabrehavenServer/src/depotlocker.h @@ -0,0 +1,63 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 +#define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 + +#include "container.h" + +class DepotLocker final : public Container +{ + public: + explicit DepotLocker(uint16_t type); + + DepotLocker* getDepotLocker() final { + return this; + } + const DepotLocker* getDepotLocker() const final { + return this; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + + uint16_t getDepotId() const { + return depotId; + } + void setDepotId(uint16_t depotId) { + this->depotId = depotId; + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + bool canRemove() const final { + return false; + } + + private: + uint16_t depotId; +}; + +#endif + diff --git a/app/SabrehavenServer/src/enums.h b/app/SabrehavenServer/src/enums.h new file mode 100644 index 0000000..d6bf53a --- /dev/null +++ b/app/SabrehavenServer/src/enums.h @@ -0,0 +1,401 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE +#define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE + +enum ThreadState { + THREAD_STATE_RUNNING, + THREAD_STATE_CLOSING, + THREAD_STATE_TERMINATED, +}; + +enum itemAttrTypes : uint32_t { + ITEM_ATTRIBUTE_NONE, + + ITEM_ATTRIBUTE_ACTIONID = 1 << 0, + ITEM_ATTRIBUTE_MOVEMENTID = 1 << 1, + ITEM_ATTRIBUTE_DESCRIPTION = 1 << 2, + ITEM_ATTRIBUTE_TEXT = 1 << 3, + ITEM_ATTRIBUTE_DATE = 1 << 4, + ITEM_ATTRIBUTE_WRITER = 1 << 5, + ITEM_ATTRIBUTE_NAME = 1 << 6, + ITEM_ATTRIBUTE_ARTICLE = 1 << 7, + ITEM_ATTRIBUTE_PLURALNAME = 1 << 8, + ITEM_ATTRIBUTE_WEIGHT = 1 << 9, + ITEM_ATTRIBUTE_ATTACK = 1 << 10, + ITEM_ATTRIBUTE_DEFENSE = 1 << 11, + ITEM_ATTRIBUTE_ARMOR = 1 << 12, + ITEM_ATTRIBUTE_SHOOTRANGE = 1 << 13, + ITEM_ATTRIBUTE_OWNER = 1 << 14, + ITEM_ATTRIBUTE_DURATION = 1 << 15, + ITEM_ATTRIBUTE_DECAYSTATE = 1 << 16, + ITEM_ATTRIBUTE_CORPSEOWNER = 1 << 17, + ITEM_ATTRIBUTE_CHARGES = 1 << 18, + ITEM_ATTRIBUTE_FLUIDTYPE = 1 << 19, + ITEM_ATTRIBUTE_DOORID = 1 << 20, + ITEM_ATTRIBUTE_KEYNUMBER = 1 << 21, + ITEM_ATTRIBUTE_KEYHOLENUMBER = 1 << 22, + ITEM_ATTRIBUTE_DOORQUESTNUMBER = 1 << 23, + ITEM_ATTRIBUTE_DOORQUESTVALUE = 1 << 24, + ITEM_ATTRIBUTE_DOORLEVEL = 1 << 25, + ITEM_ATTRIBUTE_CHESTQUESTNUMBER = 1 << 26, +}; + +enum VipStatus_t : uint8_t { + VIPSTATUS_OFFLINE = 0, + VIPSTATUS_ONLINE = 1, +}; + +enum OperatingSystem_t : uint8_t { + CLIENTOS_NONE = 0, + + CLIENTOS_LINUX = 1, + CLIENTOS_WINDOWS = 2, + CLIENTOS_FLASH = 3, + + CLIENTOS_OTCLIENT_LINUX = 10, + CLIENTOS_OTCLIENT_WINDOWS = 11, + CLIENTOS_OTCLIENT_MAC = 12, +}; + +enum AccountType_t : uint8_t { + ACCOUNT_TYPE_NORMAL = 1, + ACCOUNT_TYPE_TUTOR = 2, + ACCOUNT_TYPE_SENIORTUTOR = 3, + ACCOUNT_TYPE_GAMEMASTER = 4, + ACCOUNT_TYPE_GOD = 5 +}; + +enum RaceType_t : uint8_t { + RACE_NONE, + RACE_VENOM, + RACE_BLOOD, + RACE_UNDEAD, + RACE_FIRE, +}; + +enum CombatType_t : uint16_t { + COMBAT_NONE = 0, + + COMBAT_PHYSICALDAMAGE = 1 << 0, + COMBAT_ENERGYDAMAGE = 1 << 1, + COMBAT_EARTHDAMAGE = 1 << 2, + COMBAT_FIREDAMAGE = 1 << 3, + COMBAT_UNDEFINEDDAMAGE = 1 << 4, + COMBAT_LIFEDRAIN = 1 << 5, + COMBAT_MANADRAIN = 1 << 6, + COMBAT_HEALING = 1 << 7, + COMBAT_DROWNDAMAGE = 1 << 8, + + COMBAT_COUNT = 10 +}; + +enum CombatParam_t { + COMBAT_PARAM_TYPE, + COMBAT_PARAM_EFFECT, + COMBAT_PARAM_DISTANCEEFFECT, + COMBAT_PARAM_BLOCKSHIELD, + COMBAT_PARAM_BLOCKARMOR, + COMBAT_PARAM_TARGETCASTERORTOPMOST, + COMBAT_PARAM_CREATEITEM, + COMBAT_PARAM_AGGRESSIVE, + COMBAT_PARAM_DISPEL, + COMBAT_PARAM_USECHARGES, + COMBAT_PARAM_DECREASEDAMAGE, + COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE, +}; + +enum fightMode_t : uint8_t { + FIGHTMODE_ATTACK = 1, + FIGHTMODE_BALANCED = 2, + FIGHTMODE_DEFENSE = 3, +}; + +enum CallBackParam_t { + CALLBACK_PARAM_LEVELMAGICVALUE, + CALLBACK_PARAM_SKILLVALUE, + CALLBACK_PARAM_TARGETTILE, + CALLBACK_PARAM_TARGETCREATURE, +}; + +enum ConditionParam_t { + CONDITION_PARAM_OWNER = 1, + CONDITION_PARAM_TICKS = 2, + //CONDITION_PARAM_OUTFIT = 3, + CONDITION_PARAM_HEALTHGAIN = 4, + CONDITION_PARAM_HEALTHTICKS = 5, + CONDITION_PARAM_MANAGAIN = 6, + CONDITION_PARAM_MANATICKS = 7, + CONDITION_PARAM_DELAYED = 8, + CONDITION_PARAM_SPEED = 9, + CONDITION_PARAM_LIGHT_LEVEL = 10, + CONDITION_PARAM_LIGHT_COLOR = 11, + CONDITION_PARAM_SOULGAIN = 12, + CONDITION_PARAM_SOULTICKS = 13, + CONDITION_PARAM_MINVALUE = 14, + CONDITION_PARAM_MAXVALUE = 15, + CONDITION_PARAM_STARTVALUE = 16, + CONDITION_PARAM_TICKINTERVAL = 17, + CONDITION_PARAM_SKILL_MELEE = 19, + CONDITION_PARAM_SKILL_FIST = 20, + CONDITION_PARAM_SKILL_CLUB = 21, + CONDITION_PARAM_SKILL_SWORD = 22, + CONDITION_PARAM_SKILL_AXE = 23, + CONDITION_PARAM_SKILL_DISTANCE = 24, + CONDITION_PARAM_SKILL_SHIELD = 25, + CONDITION_PARAM_SKILL_FISHING = 26, + CONDITION_PARAM_STAT_MAXHITPOINTS = 27, + CONDITION_PARAM_STAT_MAXMANAPOINTS = 28, + // CONDITION_PARAM_STAT_SOULPOINTS = 29, + CONDITION_PARAM_STAT_MAGICPOINTS = 30, + CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT = 31, + CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT = 32, + // CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33, + CONDITION_PARAM_STAT_MAGICPOINTSPERCENT = 34, + CONDITION_PARAM_PERIODICDAMAGE = 35, + CONDITION_PARAM_SKILL_MELEEPERCENT = 36, + CONDITION_PARAM_SKILL_FISTPERCENT = 37, + CONDITION_PARAM_SKILL_CLUBPERCENT = 38, + CONDITION_PARAM_SKILL_SWORDPERCENT = 39, + CONDITION_PARAM_SKILL_AXEPERCENT = 40, + CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41, + CONDITION_PARAM_SKILL_SHIELDPERCENT = 42, + CONDITION_PARAM_SKILL_FISHINGPERCENT = 43, + // CONDITION_PARAM_BUFF_SPELL = 44, + CONDITION_PARAM_SUBID = 45, + CONDITION_PARAM_FIELD = 46, + CONDITION_PARAM_CYCLE = 47, + CONDITION_PARAM_HIT_DAMAGE = 48, + CONDITION_PARAM_COUNT = 49, + CONDITION_PARAM_MAX_COUNT = 50, +}; + +enum BlockType_t : uint8_t { + BLOCK_NONE, + BLOCK_DEFENSE, + BLOCK_ARMOR, + BLOCK_IMMUNITY +}; + +enum skills_t : uint8_t { + SKILL_FIST = 0, + SKILL_CLUB = 1, + SKILL_SWORD = 2, + SKILL_AXE = 3, + SKILL_DISTANCE = 4, + SKILL_SHIELD = 5, + SKILL_FISHING = 6, + + SKILL_MAGLEVEL = 7, + SKILL_LEVEL = 8, + + SKILL_FIRST = SKILL_FIST, + SKILL_LAST = SKILL_FISHING +}; + +enum stats_t { + STAT_MAXHITPOINTS, + STAT_MAXMANAPOINTS, + STAT_SOULPOINTS, // unused + STAT_MAGICPOINTS, + + STAT_FIRST = STAT_MAXHITPOINTS, + STAT_LAST = STAT_MAGICPOINTS +}; + +enum formulaType_t { + COMBAT_FORMULA_UNDEFINED, + COMBAT_FORMULA_LEVELMAGIC, + COMBAT_FORMULA_SKILL, + COMBAT_FORMULA_DAMAGE, +}; + +enum ConditionType_t { + CONDITION_NONE, + + CONDITION_POISON = 1 << 0, + CONDITION_FIRE = 1 << 1, + CONDITION_ENERGY = 1 << 2, + CONDITION_HASTE = 1 << 3, + CONDITION_PARALYZE = 1 << 4, + CONDITION_OUTFIT = 1 << 5, + CONDITION_INVISIBLE = 1 << 6, + CONDITION_LIGHT = 1 << 7, + CONDITION_MANASHIELD = 1 << 8, + CONDITION_INFIGHT = 1 << 9, + CONDITION_DRUNK = 1 << 10, + CONDITION_REGENERATION = 1 << 11, + CONDITION_SOUL = 1 << 12, + CONDITION_MUTED = 1 << 13, + CONDITION_CHANNELMUTEDTICKS = 1 << 14, + CONDITION_YELLTICKS = 1 << 15, + CONDITION_ATTRIBUTES = 1 << 16, + CONDITION_EXHAUST = 1 << 17, + CONDITION_PACIFIED = 1 << 18, + CONDITION_AGGRESSIVE = 1 << 19, + CONDITION_DROWN = 1 << 20, +}; + +enum ConditionId_t : int8_t { + CONDITIONID_DEFAULT = -1, + CONDITIONID_COMBAT, + CONDITIONID_HEAD, + CONDITIONID_NECKLACE, + CONDITIONID_BACKPACK, + CONDITIONID_ARMOR, + CONDITIONID_RIGHT, + CONDITIONID_LEFT, + CONDITIONID_LEGS, + CONDITIONID_FEET, + CONDITIONID_RING, + CONDITIONID_AMMO, +}; + +enum PlayerSex_t : uint8_t { + PLAYERSEX_FEMALE = 0, + PLAYERSEX_MALE = 1, + + PLAYERSEX_LAST = PLAYERSEX_MALE +}; + +enum Vocation_t : uint16_t { + VOCATION_NONE, + VOCATION_SORCERER = 1 << 0, + VOCATION_DRUID = 1 << 1, + VOCATION_PALADIN = 1 << 2, + VOCATION_KNIGHT = 1 << 3, +}; + +enum ReturnValue { + RETURNVALUE_NOERROR, + RETURNVALUE_NOTPOSSIBLE, + RETURNVALUE_NOTENOUGHROOM, + RETURNVALUE_PLAYERISPZLOCKED, + RETURNVALUE_PLAYERISNOTINVITED, + RETURNVALUE_CANNOTTHROW, + RETURNVALUE_THEREISNOWAY, + RETURNVALUE_DESTINATIONOUTOFREACH, + RETURNVALUE_CREATUREBLOCK, + RETURNVALUE_NOTMOVEABLE, + RETURNVALUE_DROPTWOHANDEDITEM, + RETURNVALUE_BOTHHANDSNEEDTOBEFREE, + RETURNVALUE_CANONLYUSEONEWEAPON, + RETURNVALUE_NEEDEXCHANGE, + RETURNVALUE_CANNOTBEDRESSED, + RETURNVALUE_PUTTHISOBJECTINYOURHAND, + RETURNVALUE_PUTTHISOBJECTINBOTHHANDS, + RETURNVALUE_TOOFARAWAY, + RETURNVALUE_FIRSTGODOWNSTAIRS, + RETURNVALUE_FIRSTGOUPSTAIRS, + RETURNVALUE_CONTAINERNOTENOUGHROOM, + RETURNVALUE_NOTENOUGHCAPACITY, + RETURNVALUE_CANNOTPICKUP, + RETURNVALUE_THISISIMPOSSIBLE, + RETURNVALUE_DEPOTISFULL, + RETURNVALUE_CREATUREDOESNOTEXIST, + RETURNVALUE_CANNOTUSETHISOBJECT, + RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE, + RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE, + RETURNVALUE_YOUAREALREADYTRADING, + RETURNVALUE_THISPLAYERISALREADYTRADING, + RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT, + RETURNVALUE_DIRECTPLAYERSHOOT, + RETURNVALUE_NOTENOUGHLEVEL, + RETURNVALUE_NOTENOUGHMAGICLEVEL, + RETURNVALUE_NOTENOUGHMANA, + RETURNVALUE_NOTENOUGHSOUL, + RETURNVALUE_YOUAREEXHAUSTED, + RETURNVALUE_PLAYERISNOTREACHABLE, + RETURNVALUE_CANONLYUSETHISRUNEONCREATURES, + RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER, + RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE, + RETURNVALUE_YOUCANONLYUSEITONCREATURES, + RETURNVALUE_CREATUREISNOTREACHABLE, + RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS, + RETURNVALUE_YOUNEEDPREMIUMACCOUNT, + RETURNVALUE_YOUNEEDTOLEARNTHISSPELL, + RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL, + RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL, + RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE, + RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE, + RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE, + RETURNVALUE_YOUCANNOTLOGOUTHERE, + RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL, + RETURNVALUE_CANNOTCONJUREITEMHERE, + RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS, + RETURNVALUE_NAMEISTOOAMBIGIOUS, + RETURNVALUE_CANONLYUSEONESHIELD, + RETURNVALUE_NOPARTYMEMBERSINRANGE, + RETURNVALUE_YOUARENOTTHEOWNER, + RETURNVALUE_TRADEPLAYERFARAWAY, + RETURNVALUE_YOUDONTOWNTHISHOUSE, + RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE, + RETURNVALUE_TRADEPLAYERHIGHESTBIDDER, + RETURNVALUE_YOUCANNOTTRADETHISHOUSE, +}; + +struct Outfit_t { + uint16_t lookType = 0; + uint16_t lookTypeEx = 0; + uint8_t lookHead = 0; + uint8_t lookBody = 0; + uint8_t lookLegs = 0; + uint8_t lookFeet = 0; + uint8_t lookAddons = 0; +}; + +struct LightInfo { + uint8_t level = 0; + uint8_t color = 0; + constexpr LightInfo() = default; + constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {} +}; + +enum CombatOrigin +{ + ORIGIN_NONE, + ORIGIN_CONDITION, + ORIGIN_SPELL, + ORIGIN_MELEE, + ORIGIN_RANGED, +}; + +struct CombatDamage +{ + CombatType_t type; + int32_t value; + int32_t min; + int32_t max; + CombatOrigin origin; + + CombatDamage() + { + origin = ORIGIN_NONE; + type = COMBAT_NONE; + value = 0; + min = 0; + max = 0; + } +}; + +#endif diff --git a/app/SabrehavenServer/src/events.cpp b/app/SabrehavenServer/src/events.cpp new file mode 100644 index 0000000..868901e --- /dev/null +++ b/app/SabrehavenServer/src/events.cpp @@ -0,0 +1,827 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2016 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "events.h" +#include "tools.h" +#include "item.h" +#include "player.h" + +#include + +Events::Events() : + scriptInterface("Event Interface") +{ + clear(); + scriptInterface.initState(); +} + +void Events::clear() +{ + // Creature + creatureOnChangeOutfit = -1; + creatureOnAreaCombat = -1; + creatureOnTargetCombat = -1; + + // Party + partyOnJoin = -1; + partyOnLeave = -1; + partyOnDisband = -1; + + // Player + playerOnLook = -1; + playerOnLookInBattleList = -1; + playerOnLookInTrade = -1; + playerOnMoveItem = -1; + playerOnItemMoved = -1; + playerOnMoveCreature = -1; + playerOnTurn = -1; + playerOnTradeRequest = -1; + playerOnTradeAccept = -1; + playerOnGainExperience = -1; + playerOnLoseExperience = -1; + playerOnGainSkillTries = -1; + playerOnReportBug = -1; + +} + +bool Events::load() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/events/events.xml"); + if (!result) { + printXMLError("Error - Events::load", "data/events/events.xml", result); + return false; + } + + std::set classes; + for (auto eventNode : doc.child("events").children()) { + if (!eventNode.attribute("enabled").as_bool()) { + continue; + } + + const std::string& className = eventNode.attribute("class").as_string(); + auto res = classes.insert(className); + if (res.second) { + const std::string& lowercase = asLowerCaseString(className); + if (scriptInterface.loadFile("data/events/scripts/" + lowercase + ".lua") != 0) { + std::cout << "[Warning - Events::load] Can not load script: " << lowercase << ".lua" << std::endl; + std::cout << scriptInterface.getLastLuaError() << std::endl; + } + } + + const std::string& methodName = eventNode.attribute("method").as_string(); + const int32_t event = scriptInterface.getMetaEvent(className, methodName); + if (className == "Creature") { + if (methodName == "onChangeOutfit") { + creatureOnChangeOutfit = event; + } + else if (methodName == "onAreaCombat") { + creatureOnAreaCombat = event; + } + else if (methodName == "onTargetCombat") { + creatureOnTargetCombat = event; + } + else { + std::cout << "[Warning - Events::load] Unknown creature method: " << methodName << std::endl; + } + } + else if (className == "Party") { + if (methodName == "onJoin") { + partyOnJoin = event; + } + else if (methodName == "onLeave") { + partyOnLeave = event; + } + else if (methodName == "onDisband") { + partyOnDisband = event; + } + else if (methodName == "onShareExperience") { + partyOnShareExperience = event; + } + else { + std::cout << "[Warning - Events::load] Unknown party method: " << methodName << std::endl; + } + } + else if (className == "Player") { + if (methodName == "onLook") { + playerOnLook = event; + } + else if (methodName == "onLookInBattleList") { + playerOnLookInBattleList = event; + } + else if (methodName == "onLookInTrade") { + playerOnLookInTrade = event; + } + else if (methodName == "onTradeRequest") { + playerOnTradeRequest = event; + } + else if (methodName == "onTradeAccept") { + playerOnTradeAccept = event; + } + else if (methodName == "onMoveItem") { + playerOnMoveItem = event; + } + else if (methodName == "onItemMoved") { + playerOnItemMoved = event; + } + else if (methodName == "onMoveCreature") { + playerOnMoveCreature = event; + } + else if (methodName == "onReportBug") { + playerOnReportBug = event; + } + else if (methodName == "onTurn") { + playerOnTurn = event; + } + else if (methodName == "onGainExperience") { + playerOnGainExperience = event; + } + else if (methodName == "onLoseExperience") { + playerOnLoseExperience = event; + } + else if (methodName == "onGainSkillTries") { + playerOnGainSkillTries = event; + } + else { + std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl; + } + } + else { + std::cout << "[Warning - Events::load] Unknown class: " << className << std::endl; + } + } + return true; +} + +// Creature +bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit) +{ + // Creature:onChangeOutfit(outfit) or Creature.onChangeOutfit(self, outfit) + if (creatureOnChangeOutfit == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventCreatureOnChangeOutfit] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(creatureOnChangeOutfit, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(creatureOnChangeOutfit); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushOutfit(L, outfit); + + return scriptInterface.callFunction(2); +} + +ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive) +{ + // Creature:onAreaCombat(tile, aggressive) or Creature.onAreaCombat(self, tile, aggressive) + if (creatureOnAreaCombat == -1) { + return RETURNVALUE_NOERROR; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventCreatureOnAreaCombat] Call stack overflow" << std::endl; + return RETURNVALUE_NOTPOSSIBLE; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(creatureOnAreaCombat, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(creatureOnAreaCombat); + + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } + else { + lua_pushnil(L); + } + + LuaScriptInterface::pushUserdata(L, tile); + LuaScriptInterface::setMetatable(L, -1, "Tile"); + + LuaScriptInterface::pushBoolean(L, aggressive); + + ReturnValue returnValue; + if (scriptInterface.protectedCall(L, 3, 1) != 0) { + returnValue = RETURNVALUE_NOTPOSSIBLE; + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + returnValue = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); + return returnValue; +} + +ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* target) +{ + // Creature:onTargetCombat(target) or Creature.onTargetCombat(self, target) + if (creatureOnTargetCombat == -1) { + return RETURNVALUE_NOERROR; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventCreatureOnTargetCombat] Call stack overflow" << std::endl; + return RETURNVALUE_NOTPOSSIBLE; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(creatureOnTargetCombat, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(creatureOnTargetCombat); + + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } + else { + lua_pushnil(L); + } + + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + + ReturnValue returnValue; + if (scriptInterface.protectedCall(L, 2, 1) != 0) { + returnValue = RETURNVALUE_NOTPOSSIBLE; + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + returnValue = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); + return returnValue; +} + +// Party +bool Events::eventPartyOnJoin(Party* party, Player* player) +{ + // Party:onJoin(player) or Party.onJoin(self, player) + if (partyOnJoin == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPartyOnJoin] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(partyOnJoin, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(partyOnJoin); + + LuaScriptInterface::pushUserdata(L, party); + LuaScriptInterface::setMetatable(L, -1, "Party"); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface.callFunction(2); +} + +bool Events::eventPartyOnLeave(Party* party, Player* player) +{ + // Party:onLeave(player) or Party.onLeave(self, player) + if (partyOnLeave == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPartyOnLeave] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(partyOnLeave, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(partyOnLeave); + + LuaScriptInterface::pushUserdata(L, party); + LuaScriptInterface::setMetatable(L, -1, "Party"); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface.callFunction(2); +} + +bool Events::eventPartyOnDisband(Party* party) +{ + // Party:onDisband() or Party.onDisband(self) + if (partyOnDisband == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPartyOnDisband] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(partyOnDisband, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(partyOnDisband); + + LuaScriptInterface::pushUserdata(L, party); + LuaScriptInterface::setMetatable(L, -1, "Party"); + + return scriptInterface.callFunction(1); +} + +void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) +{ + // Party:onShareExperience(exp) or Party.onShareExperience(self, exp) + if (partyOnShareExperience == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPartyOnShareExperience] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(partyOnShareExperience, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(partyOnShareExperience); + + LuaScriptInterface::pushUserdata(L, party); + LuaScriptInterface::setMetatable(L, -1, "Party"); + + lua_pushnumber(L, exp); + + if (scriptInterface.protectedCall(L, 2, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + exp = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); +} + +void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance) +{ + // Player:onLook(thing, position, distance) or Player.onLook(self, thing, position, distance) + if (playerOnLook == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnLook] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnLook, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnLook); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + if (Creature* creature = thing->getCreature()) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } + else if (Item* item = thing->getItem()) { + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + } + else { + lua_pushnil(L); + } + + LuaScriptInterface::pushPosition(L, position, stackpos); + lua_pushnumber(L, lookDistance); + + scriptInterface.callVoidFunction(4); +} + +void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance) +{ + // Player:onLookInBattleList(creature, position, distance) or Player.onLookInBattleList(self, creature, position, distance) + if (playerOnLookInBattleList == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnLookInBattleList] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnLookInBattleList, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnLookInBattleList); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + lua_pushnumber(L, lookDistance); + + scriptInterface.callVoidFunction(3); +} + +void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance) +{ + // Player:onLookInTrade(partner, item, distance) or Player.onLookInTrade(self, partner, item, distance) + if (playerOnLookInTrade == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnLookInTrade] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnLookInTrade, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnLookInTrade); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, partner); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + lua_pushnumber(L, lookDistance); + + scriptInterface.callVoidFunction(4); +} + +bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) +{ + // Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if (playerOnMoveItem == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnMoveItem] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnMoveItem, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnMoveItem); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + lua_pushnumber(L, count); + LuaScriptInterface::pushPosition(L, fromPosition); + LuaScriptInterface::pushPosition(L, toPosition); + + LuaScriptInterface::pushCylinder(L, fromCylinder); + LuaScriptInterface::pushCylinder(L, toCylinder); + + return scriptInterface.callFunction(7); +} + +void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) +{ + // Player:onItemMoved(item, count, fromPosition, toPosition) or Player.onItemMoved(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if (playerOnItemMoved == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnItemMoved] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnItemMoved, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnItemMoved); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + lua_pushnumber(L, count); + LuaScriptInterface::pushPosition(L, fromPosition); + LuaScriptInterface::pushPosition(L, toPosition); + + LuaScriptInterface::pushCylinder(L, fromCylinder); + LuaScriptInterface::pushCylinder(L, toCylinder); + + scriptInterface.callVoidFunction(7); +} + +bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition) +{ + // Player:onMoveCreature(creature, fromPosition, toPosition) or Player.onMoveCreature(self, creature, fromPosition, toPosition) + if (playerOnMoveCreature == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnMoveCreature] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnMoveCreature, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnMoveCreature); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushPosition(L, fromPosition); + LuaScriptInterface::pushPosition(L, toPosition); + + return scriptInterface.callFunction(4); +} + +bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position) +{ + // Player:onReportBug(message, position, category) + if (playerOnReportBug == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnReportBug] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnReportBug, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnReportBug); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushString(L, message); + LuaScriptInterface::pushPosition(L, position); + + return scriptInterface.callFunction(3); +} + +bool Events::eventPlayerOnTurn(Player* player, Direction direction) +{ + // Player:onTurn(direction) or Player.onTurn(self, direction) + if (playerOnTurn == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnTurn] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnTurn, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnTurn); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, direction); + + return scriptInterface.callFunction(2); +} + +bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* item) +{ + // Player:onTradeRequest(target, item) + if (playerOnTradeRequest == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnTradeRequest] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnTradeRequest, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnTradeRequest); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + return scriptInterface.callFunction(3); +} + +bool Events::eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem) +{ + // Player:onTradeAccept(target, item, targetItem) + if (playerOnTradeAccept == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnTradeAccept] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnTradeAccept, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnTradeAccept); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + LuaScriptInterface::pushUserdata(L, targetItem); + LuaScriptInterface::setItemMetatable(L, -1, targetItem); + + return scriptInterface.callFunction(4); +} + +void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp) +{ + // Player:onGainExperience(source, exp, rawExp) + // rawExp gives the original exp which is not multiplied + if (playerOnGainExperience == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnGainExperience] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnGainExperience, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnGainExperience); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + if (source) { + LuaScriptInterface::pushUserdata(L, source); + LuaScriptInterface::setCreatureMetatable(L, -1, source); + } + else { + lua_pushnil(L); + } + + lua_pushnumber(L, exp); + lua_pushnumber(L, rawExp); + + if (scriptInterface.protectedCall(L, 4, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + exp = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); +} + +void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) +{ + // Player:onLoseExperience(exp) + if (playerOnLoseExperience == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnLoseExperience] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnLoseExperience, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnLoseExperience); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, exp); + + if (scriptInterface.protectedCall(L, 2, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + exp = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); +} + +void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries) +{ + // Player:onGainSkillTries(skill, tries) + if (playerOnGainSkillTries == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnGainSkillTries] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(playerOnGainSkillTries, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(playerOnGainSkillTries); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, skill); + lua_pushnumber(L, tries); + + if (scriptInterface.protectedCall(L, 3, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + else { + tries = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/events.h b/app/SabrehavenServer/src/events.h new file mode 100644 index 0000000..7582c29 --- /dev/null +++ b/app/SabrehavenServer/src/events.h @@ -0,0 +1,95 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2016 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_EVENTS_H_BD444CC0EE167E5777E4C90C766B36DC +#define FS_EVENTS_H_BD444CC0EE167E5777E4C90C766B36DC + +#include "luascript.h" + +class Party; +class ItemType; +class Tile; + +class Events +{ +public: + Events(); + + void clear(); + bool load(); + + // Creature + bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); + ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); + ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); + + // Party + bool eventPartyOnJoin(Party* party, Player* player); + bool eventPartyOnLeave(Party* party, Player* player); + bool eventPartyOnDisband(Party* party); + void eventPartyOnShareExperience(Party* party, uint64_t& exp); + + // Player + void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); + void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); + void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); + bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); + void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); + bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position); + bool eventPlayerOnTurn(Player* player, Direction direction); + bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); + bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); + void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); + void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); + void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); + +private: + LuaScriptInterface scriptInterface; + + // Creature + int32_t creatureOnChangeOutfit; + int32_t creatureOnAreaCombat; + int32_t creatureOnTargetCombat; + + // Party + int32_t partyOnJoin; + int32_t partyOnLeave; + int32_t partyOnDisband; + int32_t partyOnShareExperience; + + // Player + int32_t playerOnLook; + int32_t playerOnLookInBattleList; + int32_t playerOnLookInTrade; + int32_t playerOnMoveItem; + int32_t playerOnItemMoved; + int32_t playerOnMoveCreature; + int32_t playerOnReportRuleViolation; + int32_t playerOnReportBug; + int32_t playerOnTurn; + int32_t playerOnTradeRequest; + int32_t playerOnTradeAccept; + int32_t playerOnGainExperience; + int32_t playerOnLoseExperience; + int32_t playerOnGainSkillTries; +}; + +#endif \ No newline at end of file diff --git a/app/SabrehavenServer/src/fileloader.cpp b/app/SabrehavenServer/src/fileloader.cpp new file mode 100644 index 0000000..be25ecf --- /dev/null +++ b/app/SabrehavenServer/src/fileloader.cpp @@ -0,0 +1,405 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "fileloader.h" + +FileLoader::~FileLoader() +{ + if (file) { + fclose(file); + file = nullptr; + } + + NodeStruct::clearNet(root); + delete[] buffer; + + for (auto& i : cached_data) { + delete[] i.data; + } +} + +bool FileLoader::openFile(const char* filename, const char* accept_identifier) +{ + file = fopen(filename, "rb"); + if (!file) { + lastError = ERROR_CAN_NOT_OPEN; + return false; + } + + char identifier[4]; + if (fread(identifier, 1, 4, file) < 4) { + fclose(file); + file = nullptr; + lastError = ERROR_EOF; + return false; + } + + // The first four bytes must either match the accept identifier or be 0x00000000 (wildcard) + if (memcmp(identifier, accept_identifier, 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) { + fclose(file); + file = nullptr; + lastError = ERROR_INVALID_FILE_VERSION; + return false; + } + + fseek(file, 0, SEEK_END); + int32_t file_size = ftell(file); + cache_size = std::min(32768, std::max(file_size / 20, 8192)) & ~0x1FFF; + + if (!safeSeek(4)) { + lastError = ERROR_INVALID_FORMAT; + return false; + } + + delete root; + root = new NodeStruct(); + root->start = 4; + + int32_t byte; + if (safeSeek(4) && readByte(byte) && byte == NODE_START) { + return parseNode(root); + } + + return false; +} + +bool FileLoader::parseNode(NODE node) +{ + int32_t byte, pos; + NODE currentNode = node; + + while (readByte(byte)) { + currentNode->type = byte; + bool setPropsSize = false; + + while (true) { + if (!readByte(byte)) { + return false; + } + + bool skipNode = false; + + switch (byte) { + case NODE_START: { + //child node start + if (!safeTell(pos)) { + return false; + } + + NODE childNode = new NodeStruct(); + childNode->start = pos; + currentNode->propsSize = pos - currentNode->start - 2; + currentNode->child = childNode; + + setPropsSize = true; + + if (!parseNode(childNode)) { + return false; + } + + break; + } + + case NODE_END: { + //current node end + if (!setPropsSize) { + if (!safeTell(pos)) { + return false; + } + + currentNode->propsSize = pos - currentNode->start - 2; + } + + if (!readByte(byte)) { + return true; + } + + switch (byte) { + case NODE_START: { + //starts next node + if (!safeTell(pos)) { + return false; + } + + skipNode = true; + NODE nextNode = new NodeStruct(); + nextNode->start = pos; + currentNode->next = nextNode; + currentNode = nextNode; + break; + } + + case NODE_END: + return safeTell(pos) && safeSeek(pos); + + default: + lastError = ERROR_INVALID_FORMAT; + return false; + } + + break; + } + + case ESCAPE_CHAR: { + if (!readByte(byte)) { + return false; + } + + break; + } + + default: + break; + } + + if (skipNode) { + break; + } + } + } + return false; +} + +const uint8_t* FileLoader::getProps(const NODE node, size_t& size) +{ + if (!node) { + return nullptr; + } + + if (node->propsSize >= buffer_size) { + delete[] buffer; + + while (node->propsSize >= buffer_size) { + buffer_size *= 2; + } + + buffer = new uint8_t[buffer_size]; + } + + //get buffer + if (!readBytes(node->propsSize, node->start + 2)) { + return nullptr; + } + + //unscape buffer + size_t j = 0; + bool escaped = false; + for (uint32_t i = 0; i < node->propsSize; ++i, ++j) { + if (buffer[i] == ESCAPE_CHAR) { + //escape char found, skip it and write next + buffer[j] = buffer[++i]; + //is neede a displacement for next bytes + escaped = true; + } else if (escaped) { + //perform that displacement + buffer[j] = buffer[i]; + } + } + + size = j; + return buffer; +} + +bool FileLoader::getProps(const NODE node, PropStream& props) +{ + size_t size; + if (const uint8_t* a = getProps(node, size)) { + props.init(reinterpret_cast(a), size); // does not break strict aliasing + return true; + } + + props.init(nullptr, 0); + return false; +} + +NODE FileLoader::getChildNode(const NODE parent, uint32_t& type) +{ + if (parent) { + NODE child = parent->child; + if (child) { + type = child->type; + } + + return child; + } + + type = root->type; + return root; +} + +NODE FileLoader::getNextNode(const NODE prev, uint32_t& type) +{ + if (!prev) { + return NO_NODE; + } + + NODE next = prev->next; + if (next) { + type = next->type; + } + return next; +} + +inline bool FileLoader::readByte(int32_t& value) +{ + if (cache_index == NO_VALID_CACHE) { + lastError = ERROR_CACHE_ERROR; + return false; + } + + if (cache_offset >= cached_data[cache_index].size) { + int32_t pos = cache_offset + cached_data[cache_index].base; + int32_t tmp = getCacheBlock(pos); + if (tmp < 0) { + return false; + } + + cache_index = tmp; + cache_offset = pos - cached_data[cache_index].base; + if (cache_offset >= cached_data[cache_index].size) { + return false; + } + } + + value = cached_data[cache_index].data[cache_offset++]; + return true; +} + +inline bool FileLoader::readBytes(uint32_t size, int32_t pos) +{ + //seek at pos + uint32_t remain = size; + uint8_t* buf = this->buffer; + do { + //prepare cache + uint32_t i = getCacheBlock(pos); + if (i == NO_VALID_CACHE) { + return false; + } + + cache_index = i; + cache_offset = pos - cached_data[i].base; + + //get maximum read block size and calculate remaining bytes + uint32_t reading = std::min(remain, cached_data[i].size - cache_offset); + remain -= reading; + + //read it + memcpy(buf, cached_data[cache_index].data + cache_offset, reading); + + //update variables + cache_offset += reading; + buf += reading; + pos += reading; + } while (remain > 0); + return true; +} + +inline bool FileLoader::safeSeek(uint32_t pos) +{ + uint32_t i = getCacheBlock(pos); + if (i == NO_VALID_CACHE) { + return false; + } + + cache_index = i; + cache_offset = pos - cached_data[i].base; + return true; +} + +inline bool FileLoader::safeTell(int32_t& pos) +{ + if (cache_index == NO_VALID_CACHE) { + lastError = ERROR_CACHE_ERROR; + return false; + } + + pos = cached_data[cache_index].base + cache_offset - 1; + return true; +} + +inline uint32_t FileLoader::getCacheBlock(uint32_t pos) +{ + bool found = false; + uint32_t i, base_pos = pos & ~(cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (cached_data[i].loaded) { + if (cached_data[i].base == base_pos) { + found = true; + break; + } + } + } + + if (!found) { + i = loadCacheBlock(pos); + } + + return i; +} + +int32_t FileLoader::loadCacheBlock(uint32_t pos) +{ + int32_t i, loading_cache = -1, base_pos = pos & ~(cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (!cached_data[i].loaded) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + for (i = 0; i < CACHE_BLOCKS; i++) { + if (std::abs(static_cast(cached_data[i].base) - base_pos) > static_cast(2 * cache_size)) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + loading_cache = 0; + } + } + + if (cached_data[loading_cache].data == nullptr) { + cached_data[loading_cache].data = new uint8_t[cache_size]; + } + + cached_data[loading_cache].base = base_pos; + + if (fseek(file, cached_data[loading_cache].base, SEEK_SET) != 0) { + lastError = ERROR_SEEK_ERROR; + return -1; + } + + uint32_t size = fread(cached_data[loading_cache].data, 1, cache_size, file); + cached_data[loading_cache].size = size; + + if (size < (pos - cached_data[loading_cache].base)) { + lastError = ERROR_SEEK_ERROR; + return -1; + } + + cached_data[loading_cache].loaded = 1; + return loading_cache; +} diff --git a/app/SabrehavenServer/src/fileloader.h b/app/SabrehavenServer/src/fileloader.h new file mode 100644 index 0000000..74872b0 --- /dev/null +++ b/app/SabrehavenServer/src/fileloader.h @@ -0,0 +1,247 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E +#define FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E + +#include +#include + +struct NodeStruct; + +typedef NodeStruct* NODE; + +struct NodeStruct { + uint32_t start = 0; + uint32_t propsSize = 0; + uint32_t type = 0; + NodeStruct* next = nullptr; + NodeStruct* child = nullptr; + + static void clearNet(NodeStruct* root) { + if (root) { + clearChild(root); + } + } + + private: + static void clearNext(NodeStruct* node) { + NodeStruct* deleteNode = node; + NodeStruct* nextNode; + + while (deleteNode) { + if (deleteNode->child) { + clearChild(deleteNode->child); + } + + nextNode = deleteNode->next; + delete deleteNode; + deleteNode = nextNode; + } + } + + static void clearChild(NodeStruct* node) { + if (node->child) { + clearChild(node->child); + } + + if (node->next) { + clearNext(node->next); + } + + delete node; + } +}; + +static constexpr auto NO_NODE = nullptr; + +enum FILELOADER_ERRORS { + ERROR_NONE, + ERROR_INVALID_FILE_VERSION, + ERROR_CAN_NOT_OPEN, + ERROR_CAN_NOT_CREATE, + ERROR_EOF, + ERROR_SEEK_ERROR, + ERROR_NOT_OPEN, + ERROR_INVALID_NODE, + ERROR_INVALID_FORMAT, + ERROR_TELL_ERROR, + ERROR_COULDNOTWRITE, + ERROR_CACHE_ERROR, +}; + +class PropStream; + +class FileLoader +{ + public: + FileLoader() = default; + ~FileLoader(); + + // non-copyable + FileLoader(const FileLoader&) = delete; + FileLoader& operator=(const FileLoader&) = delete; + + bool openFile(const char* filename, const char* identifier); + const uint8_t* getProps(const NODE, size_t& size); + bool getProps(const NODE, PropStream& props); + NODE getChildNode(const NODE parent, uint32_t& type); + NODE getNextNode(const NODE prev, uint32_t& type); + + FILELOADER_ERRORS getError() const { + return lastError; + } + + protected: + enum SPECIAL_BYTES { + ESCAPE_CHAR = 0xFD, + NODE_START = 0xFE, + NODE_END = 0xFF, + }; + + bool parseNode(NODE node); + + inline bool readByte(int32_t& value); + inline bool readBytes(uint32_t size, int32_t pos); + inline bool safeSeek(uint32_t pos); + inline bool safeTell(int32_t& pos); + + protected: + struct cache { + uint8_t* data; + uint32_t loaded; + uint32_t base; + uint32_t size; + }; + + static constexpr int32_t CACHE_BLOCKS = 3; + cache cached_data[CACHE_BLOCKS] = {}; + + uint8_t* buffer = new uint8_t[1024]; + NODE root = nullptr; + FILE* file = nullptr; + + FILELOADER_ERRORS lastError = ERROR_NONE; + uint32_t buffer_size = 1024; + + uint32_t cache_size = 0; + static constexpr uint32_t NO_VALID_CACHE = std::numeric_limits::max(); + uint32_t cache_index = NO_VALID_CACHE; + uint32_t cache_offset = NO_VALID_CACHE; + + inline uint32_t getCacheBlock(uint32_t pos); + int32_t loadCacheBlock(uint32_t pos); +}; + +class PropStream +{ + public: + void init(const char* a, size_t size) { + p = a; + end = a + size; + } + + size_t size() const { + return end - p; + } + + template + bool read(T& ret) { + if (size() < sizeof(T)) { + return false; + } + + memcpy(&ret, p, sizeof(T)); + p += sizeof(T); + return true; + } + + bool readString(std::string& ret) { + uint16_t strLen; + if (!read(strLen)) { + return false; + } + + if (size() < strLen) { + return false; + } + + char* str = new char[strLen + 1]; + memcpy(str, p, strLen); + str[strLen] = 0; + ret.assign(str, strLen); + delete[] str; + p += strLen; + return true; + } + + bool skip(size_t n) { + if (size() < n) { + return false; + } + + p += n; + return true; + } + + protected: + const char* p = nullptr; + const char* end = nullptr; +}; + +class PropWriteStream +{ + public: + PropWriteStream() = default; + + // non-copyable + PropWriteStream(const PropWriteStream&) = delete; + PropWriteStream& operator=(const PropWriteStream&) = delete; + + const char* getStream(size_t& size) const { + size = buffer.size(); + return buffer.data(); + } + + void clear() { + buffer.clear(); + } + + template + void write(T add) { + char* addr = reinterpret_cast(&add); + std::copy(addr, addr + sizeof(T), std::back_inserter(buffer)); + } + + void writeString(const std::string& str) { + size_t strLength = str.size(); + if (strLength > std::numeric_limits::max()) { + write(0); + return; + } + + write(static_cast(strLength)); + std::copy(str.begin(), str.end(), std::back_inserter(buffer)); + } + + protected: + std::vector buffer; +}; + +#endif diff --git a/app/SabrehavenServer/src/game.cpp b/app/SabrehavenServer/src/game.cpp new file mode 100644 index 0000000..d4595e2 --- /dev/null +++ b/app/SabrehavenServer/src/game.cpp @@ -0,0 +1,4694 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "pugicast.h" + +#include "items.h" +#include "creature.h" +#include "monster.h" +#include "events.h" +#include "game.h" +#include "actions.h" +#include "iologindata.h" +#include "talkaction.h" +#include "spells.h" +#include "configmanager.h" +#include "server.h" +#include "globalevent.h" +#include "bed.h" +#include "scheduler.h" +#include "databasetasks.h" +#include "movement.h" + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Chat* g_chat; +extern TalkActions* g_talkActions; +extern Spells* g_spells; +extern Vocations g_vocations; +extern GlobalEvents* g_globalEvents; +extern Events* g_events; +extern CreatureEvents* g_creatureEvents; +extern Monsters g_monsters; +extern MoveEvents* g_moveEvents; + +Game::~Game() +{ + for (const auto& it : guilds) { + delete it.second; + } +} + +void Game::start(ServiceManager* manager) +{ + serviceManager = manager; + + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0))); + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); +} + +GameState_t Game::getGameState() const +{ + return gameState; +} + +void Game::setWorldType(WorldType_t type) +{ + worldType = type; +} + +void Game::setClientVersion(ClientVersion_t version) +{ + clientVersion = version; +} + +void Game::setGameState(GameState_t newState) +{ + if (gameState == GAME_STATE_SHUTDOWN) { + return; //this cannot be stopped + } + + if (gameState == newState) { + return; + } + + gameState = newState; + switch (newState) { + case GAME_STATE_INIT: { + loadExperienceStages(); + + groups.load(); + g_chat->load(); + + map.spawns.startup(); + + raids.loadFromXml(); + raids.startup(); + + quests.loadFromXml(); + + loadMotdNum(); + loadPlayersRecord(); + + g_globalEvents->startup(); + break; + } + + case GAME_STATE_SHUTDOWN: { + g_globalEvents->execute(GLOBALEVENT_SHUTDOWN); + + //kick all players that are still online + auto it = players.begin(); + while (it != players.end()) { + it->second->kickPlayer(true); + it = players.begin(); + } + + saveMotdNum(); + saveGameState(); + + g_dispatcher.addTask( + createTask(std::bind(&Game::shutdown, this))); + + g_scheduler.stop(); + g_databaseTasks.stop(); + g_dispatcher.stop(); + break; + } + + case GAME_STATE_CLOSED: { + /* kick all players without the CanAlwaysLogin flag */ + auto it = players.begin(); + while (it != players.end()) { + if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) { + it->second->kickPlayer(true); + it = players.begin(); + } else { + ++it; + } + } + + saveGameState(); + break; + } + + default: + break; + } +} + +void Game::saveGameState() +{ + if (gameState == GAME_STATE_NORMAL) { + setGameState(GAME_STATE_MAINTAIN); + } + + std::cout << "Saving server..." << std::endl; + + for (const auto& it : players) { + it.second->loginPosition = it.second->getPosition(); + IOLoginData::savePlayer(it.second); + } + + Map::save(); + + if (gameState == GAME_STATE_MAINTAIN) { + setGameState(GAME_STATE_NORMAL); + } +} + +bool Game::loadMainMap(const std::string& filename) +{ + Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); + Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); + return map.loadMap("data/world" + std::to_string(getClientVersion()) + "/" + filename + ".otbm", true); +} + +void Game::loadMap(const std::string& path) +{ + map.loadMap(path, false); +} + +Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const +{ + if (pos.x != 0xFFFF) { + return map.getTile(pos); + } + + //container + if (pos.y & 0x40) { + uint8_t from_cid = pos.y & 0x0F; + return player->getContainerByID(from_cid); + } + + //inventory + return player; +} + +Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, stackPosType_t type) const +{ + if (pos.x != 0xFFFF) { + Tile* tile = map.getTile(pos); + if (!tile) { + return nullptr; + } + + Thing* thing; + switch (type) { + case STACKPOS_LOOK: { + return tile->getTopVisibleThing(player); + } + + case STACKPOS_MOVE: { + Item* item = tile->getTopDownItem(); + if (item && item->isMoveable()) { + thing = item; + } else { + if (g_config.getBoolean(ConfigManager::UH_TRAP)) { + thing = tile->getBottomVisibleCreatureUH(player); + } + else { + thing = tile->getTopVisibleCreature(player); + } + } + break; + } + + case STACKPOS_USEITEM: { + thing = tile->getUseItem(index); + break; + } + + case STACKPOS_TOPDOWN_ITEM: { + thing = tile->getTopDownItem(); + break; + } + + case STACKPOS_USETARGET: { + if (g_config.getBoolean(ConfigManager::UH_TRAP)) { + thing = tile->getBottomCreatureUH(); + } + else { + thing = tile->getTopCreature(); + } + if (!thing) { + thing = tile->getUseItem(index); + } + break; + } + + default: { + thing = nullptr; + break; + } + } + + if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + //do extra checks here if the thing is accessable + if (thing && thing->getItem()) { + if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { + if (player->getPosition().x + 1 == tile->getPosition().x) { + thing = nullptr; + } + } else { // horizontal + if (player->getPosition().y + 1 == tile->getPosition().y) { + thing = nullptr; + } + } + } + } + return thing; + } + + //container + if (pos.y & 0x40) { + uint8_t fromCid = pos.y & 0x0F; + + Container* parentContainer = player->getContainerByID(fromCid); + if (!parentContainer) { + return nullptr; + } + + uint8_t slot = pos.z; + return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot); + } else if (pos.y == 0 && pos.z == 0) { + const ItemType& it = Item::items.getItemType(spriteId); + if (it.id == 0) { + return nullptr; + } + + int32_t subType; + if (it.isFluidContainer() && index < static_cast(sizeof(reverseFluidMap) / sizeof(uint8_t))) { + subType = reverseFluidMap[index]; + } + else { + subType = -1; + } + + return findItemOfType(player, it.id, true, subType); + } + + //inventory + slots_t slot = static_cast(pos.y); + return player->getInventoryItem(slot); +} + +void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) +{ + pos.x = 0; + pos.y = 0; + pos.z = 0; + stackpos = 0; + + Cylinder* topParent = item->getTopParent(); + if (topParent) { + if (Player* player = dynamic_cast(topParent)) { + pos.x = 0xFFFF; + + Container* container = dynamic_cast(item->getParent()); + if (container) { + pos.y = static_cast(0x40) | static_cast(player->getContainerID(container)); + pos.z = container->getThingIndex(item); + stackpos = pos.z; + } else { + pos.y = player->getThingIndex(item); + stackpos = pos.y; + } + } else if (Tile* tile = topParent->getTile()) { + pos = tile->getPosition(); + stackpos = tile->getThingIndex(item); + } + } +} + +Creature* Game::getCreatureByID(uint32_t id) +{ + if (id <= Player::playerAutoID) { + return getPlayerByID(id); + } else if (id <= Monster::monsterAutoID) { + return getMonsterByID(id); + } else if (id <= Npc::npcAutoID) { + return getNpcByID(id); + } + return nullptr; +} + +Monster* Game::getMonsterByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = monsters.find(id); + if (it == monsters.end()) { + return nullptr; + } + return it->second; +} + +Npc* Game::getNpcByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = npcs.find(id); + if (it == npcs.end()) { + return nullptr; + } + return it->second; +} + +Player* Game::getPlayerByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = players.find(id); + if (it == players.end()) { + return nullptr; + } + return it->second; +} + +Creature* Game::getCreatureByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + const std::string& lowerCaseName = asLowerCaseString(s); + + auto m_it = mappedPlayerNames.find(lowerCaseName); + if (m_it != mappedPlayerNames.end()) { + return m_it->second; + } + + for (const auto& it : npcs) { + if (lowerCaseName == asLowerCaseString(it.second->getName())) { + return it.second; + } + } + + for (const auto& it : monsters) { + if (lowerCaseName == asLowerCaseString(it.second->getName())) { + return it.second; + } + } + return nullptr; +} + +Npc* Game::getNpcByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + const char* npcName = s.c_str(); + for (const auto& it : npcs) { + if (strcasecmp(npcName, it.second->getName().c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +Player* Game::getPlayerByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + auto it = mappedPlayerNames.find(asLowerCaseString(s)); + if (it == mappedPlayerNames.end()) { + return nullptr; + } + return it->second; +} + +Player* Game::getPlayerByGUID(const uint32_t& guid) +{ + if (guid == 0) { + return nullptr; + } + + for (const auto& it : players) { + if (guid == it.second->getGUID()) { + return it.second; + } + } + return nullptr; +} + +ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player) +{ + size_t strlen = s.length(); + if (strlen == 0 || strlen > 20) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + + if (s.back() == '~') { + const std::string& query = asLowerCaseString(s.substr(0, strlen - 1)); + std::string result; + ReturnValue ret = wildcardTree.findOne(query, result); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + player = getPlayerByName(result); + } else { + player = getPlayerByName(s); + } + + if (!player) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + + return RETURNVALUE_NOERROR; +} + +Player* Game::getPlayerByAccount(uint32_t acc) +{ + for (const auto& it : players) { + if (it.second->getAccount() == acc) { + return it.second; + } + } + return nullptr; +} + +bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (creature->getParent() != nullptr) { + return false; + } + + if (!map.placeCreature(pos, creature, extendedPos, forced)) { + return false; + } + + creature->incrementReferenceCounter(); + creature->setID(); + creature->addList(); + return true; +} + +bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { + return false; + } + + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true); + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); + } + } + + for (Creature* spectator : spectators) { + spectator->onCreatureAppear(creature, true); + } + + creature->getParent()->postAddNotification(creature, nullptr, 0); + + addCreatureCheck(creature); + creature->onPlacedCreature(); + return true; +} + +bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) +{ + if (creature->isRemoved()) { + return false; + } + + Tile* tile = creature->getTile(); + + std::vector oldStackPosVector; + + SpectatorVec spectators; + map.getSpectators(spectators, tile->getPosition(), true); + for (Creature* spectator : spectators) { + if (Player* player = spectator->getPlayer()) { + oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); + } + } + + tile->removeCreature(creature); + + const Position& tilePosition = tile->getPosition(); + + //send to client + size_t i = 0; + for (Creature* spectator : spectators) { + if (Player* player = spectator->getPlayer()) { + player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); + } + } + + //event method + for (Creature* spectator : spectators) { + spectator->onRemoveCreature(creature, isLogout); + } + + creature->getParent()->postRemoveNotification(creature, nullptr, 0); + + creature->removeList(); + creature->setRemoved(); + ReleaseCreature(creature); + + removeCreatureCheck(creature); + + for (Creature* summon : creature->summons) { + summon->setSkillLoss(false); + removeCreature(summon); + } + return true; +} + +void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint8_t fromIndex = 0; + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = fromPos.z; + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (Creature* movingCreature = thing->getCreature()) { + Tile* tile = map.getTile(toPos); + if (!tile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) { + SchedulerTask* task = createSchedulerTask(1000, + std::bind(&Game::playerMoveCreatureByID, this, player->getID(), + movingCreature->getID(), movingCreature->getPosition(), tile->getPosition())); + player->setNextActionTask(task); + } else { + playerMoveCreature(player, movingCreature, movingCreature->getPosition(), tile); + } + } else if (thing->getItem()) { + Cylinder* toCylinder = internalGetCylinder(player, toPos); + if (!toCylinder) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, thing->getItem(), toCylinder); + } +} + +void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* movingCreature = getCreatureByID(movingCreatureId); + if (!movingCreature) { + return; + } + + Tile* toTile = map.getTile(toPos); + if (!toTile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile); +} + +void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile) +{ + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveCreatureByID, + this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + player->setNextActionTask(task); + return; + } + + player->setNextActionTask(nullptr); + + if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { + if (toTile->getHeight() > 1) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { + //need to walk to the creature first before moving it + std::forward_list listDir; + if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(1500, std::bind(&Game::playerMoveCreatureByID, this, + player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + if ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || + (movingCreature->isInGhostMode() && !player->isAccessPlayer())) { + player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + return; + } + + //check throw distance + const Position& movingCreaturePos = movingCreature->getPosition(); + const Position& toPos = toTile->getPosition(); + if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + return; + } + + if (player != movingCreature) { + if (toTile->hasFlag(TILESTATE_BLOCKPATH)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } else { + if (CreatureVector* tileCreatures = toTile->getCreatures()) { + for (Creature* tileCreature : *tileCreatures) { + if (!tileCreature->isInGhostMode()) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } + } + } + + Npc* movingNpc = movingCreature->getNpc(); + if (movingNpc && !Spawns::isInZone(movingNpc->getMasterPos(), movingNpc->getMasterRadius(), toPos)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } + } + } + + if (!g_events->eventPlayerOnMoveCreature(player, movingCreature, movingCreaturePos, toPos)) { + return; + } + + ReturnValue ret = internalMoveCreature(*movingCreature, *toTile); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + } +} + +ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags /*= 0*/) +{ + creature->setLastPosition(creature->getPosition()); + const Position& currentPos = creature->getPosition(); + Position destPos = getNextPosition(direction, currentPos); + + bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; + if (creature->getPlayer() && !diagonalMovement) { + //try go up + if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { + Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { + destPos.z--; + internalCreatureTurn(creature, DIRECTION_NORTH); + } + } + } + else { + //try go down + Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); + if (currentPos.z != 7 && (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)))) { + tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); + if (tmpTile && tmpTile->hasHeight(3)) { + destPos.z++; + internalCreatureTurn(creature, DIRECTION_SOUTH); + } + } + } + } + + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + Tile* toTile = map.getTile(destPos); + + Tile* toPos = map.getTile(destPos.x, destPos.y, destPos.z); + Tile* fromPos = map.getTile(currentPos.x, currentPos.y, currentPos.z); + + if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { + if (toTile) { + if (currentPos.z > destPos.z && toPos->getHeight() > 1); + // not possible + else if ((((toPos->getHeight() - fromPos->getHeight()) < 2)) || + (fromPos->hasHeight(3) && (currentPos.z == destPos.z)) || + ((currentPos.z < destPos.z) && (toPos->hasHeight(3) && (fromPos->getHeight() < 2)))) + ret = internalMoveCreature(*creature, *toTile, flags); + } + + if (ret != RETURNVALUE_NOERROR) { + if (Player* player = creature->getPlayer()) { + player->sendCancelMessage(ret); + player->sendCancelWalk(); + } + } + + return ret; + } + else { + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; + } + return internalMoveCreature(*creature, *toTile, flags); + } +} + +ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) +{ + //check if we can move the creature to the destination + ReturnValue ret = toTile.queryAdd(0, creature, 1, flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + map.moveCreature(creature, toTile); + if (creature.getParent() != &toTile) { + return RETURNVALUE_NOERROR; + } + + int32_t index = 0; + Item* toItem = nullptr; + Tile* subCylinder = nullptr; + Tile* toCylinder = &toTile; + Tile* fromCylinder = nullptr; + uint32_t n = 0; + + while ((subCylinder = toCylinder->queryDestination(index, creature, &toItem, flags)) != toCylinder) { + map.moveCreature(creature, *subCylinder); + + if (creature.getParent() != subCylinder) { + //could happen if a script move the creature + fromCylinder = nullptr; + break; + } + + fromCylinder = toCylinder; + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++n >= MAP_MAX_LAYERS) { + break; + } + } + + if (fromCylinder) { + const Position& fromPosition = fromCylinder->getPosition(); + const Position& toPosition = toCylinder->getPosition(); + if (fromPosition.z != toPosition.z && (fromPosition.x != toPosition.x || fromPosition.y != toPosition.y)) { + Direction dir = getDirectionTo(fromPosition, toPosition); + if ((dir & DIRECTION_DIAGONAL_MASK) == 0) { + internalCreatureTurn(&creature, dir); + } + } + } + + return RETURNVALUE_NOERROR; +} + +void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr, nullptr); +} + +void Game::playerMoveItem(Player* player, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder) +{ + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextActionTask(task); + return; + } + + player->setNextActionTask(nullptr); + + if (item == nullptr) { + uint8_t fromIndex = 0; + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = fromPos.z; + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); + if (!thing || !thing->getItem()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + item = thing->getItem(); + } + + if ((item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Cylinder* fromCylinder = internalGetCylinder(player, fromPos); + if (fromCylinder == nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (toCylinder == nullptr) { + toCylinder = internalGetCylinder(player, toPos); + if (toCylinder == nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (!item->isPushable()) { + player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + return; + } + + const Position& playerPos = player->getPosition(); + const Position& mapFromPos = fromCylinder->getTile()->getPosition(); + if (playerPos.z != mapFromPos.z) { + player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + return; + } + + if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) { + //need to walk to the item first before using it + std::forward_list listDir; + if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + const Tile* toCylinderTile = toCylinder->getTile(); + const Position& mapToPos = toCylinderTile->getPosition(); + + //hangable item specific code + if (item->isHangable() && toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + //destination supports hangable objects so need to move there first + bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL); + if (vertical) { + if (playerPos.x + 1 == mapToPos.x) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } else { // horizontal + if (playerPos.y + 1 == mapToPos.y) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) { + Position walkPos = mapToPos; + if (vertical) { + walkPos.x++; + } else { + walkPos.y++; + } + + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos) + && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { + //need to pickup the item first + Item* moveItem = nullptr; + + ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), itemPos, spriteId, itemStackPos, toPos, count)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + } + + if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) || + (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) || + (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + return; + } + + if (!canThrowObjectTo(mapFromPos, mapToPos)) { + player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); + return; + } + + if (!g_events->eventPlayerOnMoveItem(player, item, count, fromPos, toPos, fromCylinder, toCylinder)) { + return; + } + + uint8_t toIndex = 0; + if (toPos.x == 0xFFFF) { + if (toPos.y & 0x40) { + toIndex = toPos.z; + } else { + toIndex = static_cast(toPos.y); + } + } + + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + } else { + g_events->eventPlayerOnItemMoved(player, item, count, fromPos, toPos, fromCylinder, toCylinder); + } +} + +ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/) +{ + Item* toItem = nullptr; + + Cylinder* subCylinder; + int floorN = 0; + + while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem, flags)) != toCylinder) { + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++floorN >= MAP_MAX_LAYERS) { + break; + } + } + + //destination is the same as the source? + if (item == toItem) { + return RETURNVALUE_NOERROR; //silently ignore move + } + + //check if we can add this item + ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor); + if (ret == RETURNVALUE_NEEDEXCHANGE) { + //check if we can add it to source cylinder + ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem, toItem->getItemCount(), 0); + if (ret == RETURNVALUE_NOERROR) { + //check how much we can move + uint32_t maxExchangeQueryCount = 0; + ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); + + if (retExchangeMaxCount != RETURNVALUE_NOERROR && maxExchangeQueryCount == 0) { + return retExchangeMaxCount; + } + + if (toCylinder->queryRemove(*toItem, toItem->getItemCount(), flags) == RETURNVALUE_NOERROR) { + int32_t oldToItemIndex = toCylinder->getThingIndex(toItem); + toCylinder->removeThing(toItem, toItem->getItemCount()); + fromCylinder->addThing(toItem); + + if (oldToItemIndex != -1) { + toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex); + } + + int32_t newToItemIndex = fromCylinder->getThingIndex(toItem); + if (newToItemIndex != -1) { + fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex); + } + + ret = toCylinder->queryAdd(index, *item, count, flags); + toItem = nullptr; + } + } + } + + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + //check how much we can move + uint32_t maxQueryCount = 0; + ReturnValue retMaxCount = toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags); + if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) { + return retMaxCount; + } + + uint32_t m; + if (item->isStackable()) { + m = std::min(count, maxQueryCount); + } + else { + m = maxQueryCount; + } + + Item* moveItem = item; + + //check if we can remove this item + ret = fromCylinder->queryRemove(*item, m, flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (tradeItem) { + if (toCylinder->getItem() == tradeItem) { + return RETURNVALUE_NOTENOUGHROOM; + } + + Cylinder* tmpCylinder = toCylinder->getParent(); + while (tmpCylinder) { + if (tmpCylinder->getItem() == tradeItem) { + return RETURNVALUE_NOTENOUGHROOM; + } + + tmpCylinder = tmpCylinder->getParent(); + } + } + + //remove the item + int32_t itemIndex = fromCylinder->getThingIndex(item); + Item* updateItem = nullptr; + fromCylinder->removeThing(item, m); + + //update item(s) + if (item->isStackable()) { + uint32_t n; + + if (item->equals(toItem)) { + n = std::min(100 - toItem->getItemCount(), m); + toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + updateItem = toItem; + } + else { + n = 0; + } + + int32_t newCount = m - n; + if (newCount > 0) { + moveItem = item->clone(); + moveItem->setItemCount(newCount); + } else { + moveItem = nullptr; + } + + if (item->isRemoved()) { + ReleaseItem(item); + } + } + + //add item + if (moveItem /*m - n > 0*/) { + toCylinder->addThing(index, moveItem); + } + + if (itemIndex != -1) { + fromCylinder->postRemoveNotification(item, toCylinder, itemIndex); + } + + if (moveItem) { + int32_t moveItemIndex = toCylinder->getThingIndex(moveItem); + if (moveItemIndex != -1) { + toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex); + } + } + + if (updateItem) { + int32_t updateItemIndex = toCylinder->getThingIndex(updateItem); + if (updateItemIndex != -1) { + toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex); + } + } + + if (_moveItem) { + if (moveItem) { + *_moveItem = moveItem; + } else { + *_moveItem = item; + } + } + + //we could not move all, inform the player + if (item->isStackable() && maxQueryCount < count) { + return retMaxCount; + } + + return ret; +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, + uint32_t flags/* = 0*/, bool test/* = false*/) +{ + uint32_t remainderCount = 0; + return internalAddItem(toCylinder, item, index, flags, test, remainderCount); +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount) +{ + if (toCylinder == nullptr || item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Cylinder* destCylinder = toCylinder; + Item* toItem = nullptr; + toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags); + + //check if we can add this item + ReturnValue ret = toCylinder->queryAdd(index, *item, item->getItemCount(), flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + /* + Check if we can move add the whole amount, we do this by checking against the original cylinder, + since the queryDestination can return a cylinder that might only hold a part of the full amount. + */ + uint32_t maxQueryCount = 0; + ret = destCylinder->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), maxQueryCount, flags); + + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (test) { + return RETURNVALUE_NOERROR; + } + + if (item->isStackable() && item->equals(toItem)) { + uint32_t m = std::min(item->getItemCount(), maxQueryCount); + uint32_t n = std::min(100 - toItem->getItemCount(), m); + + toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + + int32_t count = m - n; + if (count > 0) { + if (item->getItemCount() != count) { + Item* remainderItem = item->clone(); + remainderItem->setItemCount(count); + if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RETURNVALUE_NOERROR) { + ReleaseItem(remainderItem); + remainderCount = count; + } + } + else { + toCylinder->addThing(index, item); + + int32_t itemIndex = toCylinder->getThingIndex(item); + if (itemIndex != -1) { + toCylinder->postAddNotification(item, nullptr, itemIndex); + } + } + } + else { + //fully merged with toItem, item will be destroyed + item->onRemoved(); + ReleaseItem(item); + + int32_t itemIndex = toCylinder->getThingIndex(toItem); + if (itemIndex != -1) { + toCylinder->postAddNotification(toItem, nullptr, itemIndex); + } + } + } + else { + toCylinder->addThing(index, item); + + int32_t itemIndex = toCylinder->getThingIndex(item); + if (itemIndex != -1) { + toCylinder->postAddNotification(item, nullptr, itemIndex); + } + } + + return RETURNVALUE_NOERROR; +} + +ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) +{ + Cylinder* cylinder = item->getParent(); + if (cylinder == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == -1) { + count = item->getItemCount(); + } + + //check if we can remove this item + ReturnValue ret = cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (!item->canRemove()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!test) { + int32_t index = cylinder->getThingIndex(item); + + //remove the item + cylinder->removeThing(item, count); + + if (item->isRemoved()) { + ReleaseItem(item); + } + + cylinder->postRemoveNotification(item, nullptr, index); + } + + item->onRemoved(); + return RETURNVALUE_NOERROR; +} + +ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= CONST_SLOT_WHEREEVER*/) +{ + uint32_t remainderCount = 0; + ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); + if (remainderCount != 0) { + Item* remainderItem = Item::CreateItem(item->getID(), remainderCount); + ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (remaindRet != RETURNVALUE_NOERROR) { + ReleaseItem(remainderItem); + } + } + + if (ret != RETURNVALUE_NOERROR && dropOnMap) { + ret = internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + return ret; +} + +Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch /*= true*/, int32_t subType /*= -1*/) const +{ + if (cylinder == nullptr) { + return nullptr; + } + + std::vector containers; + for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { + Thing* thing = cylinder->getThing(i); + if (!thing) { + continue; + } + + Item* item = thing->getItem(); + if (!item) { + continue; + } + + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } + + if (depthSearch) { + Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } + + Container* subContainer = item->getContainer(); + if (subContainer) { + containers.push_back(subContainer); + } + } + } + return nullptr; +} + +bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + if (cylinder == nullptr) { + return false; + } + + if (money == 0) { + return true; + } + + std::vector containers; + + std::multimap moneyMap; + uint64_t moneyCount = 0; + + for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { + Thing* thing = cylinder->getThing(i); + if (!thing) { + continue; + } + + Item* item = thing->getItem(); + if (!item) { + continue; + } + + Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } else { + const uint32_t worth = item->getWorth(); + if (worth != 0) { + moneyCount += worth; + moneyMap.emplace(worth, item); + } + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } else { + const uint32_t worth = item->getWorth(); + if (worth != 0) { + moneyCount += worth; + moneyMap.emplace(worth, item); + } + } + } + } + + if (moneyCount < money) { + return false; + } + + for (const auto& moneyEntry : moneyMap) { + Item* item = moneyEntry.second; + if (moneyEntry.first < money) { + internalRemoveItem(item); + money -= moneyEntry.first; + } else if (moneyEntry.first > money) { + const uint32_t worth = moneyEntry.first / item->getItemCount(); + const uint32_t removeCount = std::ceil(money / static_cast(worth)); + + addMoney(cylinder, (worth * removeCount) - money, flags); + internalRemoveItem(item, removeCount); + break; + } else { + internalRemoveItem(item); + break; + } + } + return true; +} + +void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + if (money == 0) { + return; + } + + uint32_t crystalCoins = money / 10000; + money -= crystalCoins * 10000; + while (crystalCoins > 0) { + const uint16_t count = std::min(100, crystalCoins); + + Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + crystalCoins -= count; + } + + uint16_t platinumCoins = money / 100; + if (platinumCoins != 0) { + Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + money -= platinumCoins * 100; + } + + if (money != 0) { + Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + } +} + +Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) +{ + if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite + return item; + } + + Cylinder* cylinder = item->getParent(); + if (cylinder == nullptr) { + return nullptr; + } + + int32_t itemIndex = cylinder->getThingIndex(item); + if (itemIndex == -1) { + return item; + } + + if (!item->canTransform()) { + return item; + } + + const ItemType& newType = Item::items[newId]; + if (newType.id == 0) { + return item; + } + + const ItemType& curType = Item::items[item->getID()]; + if (curType.alwaysOnTop != newType.alwaysOnTop) { + //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) + //Remove the old, and add the new + cylinder->removeThing(item, item->getItemCount()); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + + item->setID(newId); + if (newCount != -1) { + item->setSubType(newCount); + } + cylinder->addThing(item); + + Cylinder* newParent = item->getParent(); + if (newParent == nullptr) { + ReleaseItem(item); + return nullptr; + } + + newParent->postAddNotification(item, cylinder, newParent->getThingIndex(item)); + return item; + } + + if (curType.type == newType.type) { + //Both items has the same type so we can safely change id/subtype + if (newCount == 0 && (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) { + if (item->isStackable()) { + internalRemoveItem(item); + return nullptr; + } else { + int32_t newItemId = newId; + if (curType.id == newType.id) { + newItemId = curType.decayTo; + } + + if (newItemId < 0) { + internalRemoveItem(item); + return nullptr; + } else if (newItemId != newId) { + //Replacing the the old item with the new while maintaining the old position + Item* newItem = Item::CreateItem(newItemId, 1); + if (newItem == nullptr) { + return nullptr; + } + + cylinder->replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(nullptr); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + ReleaseItem(item); + return newItem; + } else { + return transformItem(item, newItemId); + } + } + } else { + cylinder->postRemoveNotification(item, cylinder, itemIndex); + uint16_t itemId = item->getID(); + int32_t count = item->getSubType(); + + if (curType.id != newType.id) { + if (newType.group != curType.group) { + item->setDefaultSubtype(); + } + + itemId = newId; + } + + if (newCount != -1 && newType.hasSubType()) { + count = newCount; + } + + cylinder->updateThing(item, itemId, count); + cylinder->postAddNotification(item, cylinder, itemIndex); + return item; + } + } + + //Replacing the the old item with the new while maintaining the old position + Item* newItem; + if (newCount == -1) { + newItem = Item::CreateItem(newId); + } else { + newItem = Item::CreateItem(newId, newCount); + } + + if (newItem == nullptr) { + return nullptr; + } + + cylinder->replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(nullptr); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + ReleaseItem(item); + + return newItem; +} + +ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/) +{ + if (newPos == thing->getPosition()) { + return RETURNVALUE_NOERROR; + } else if (thing->isRemoved()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Tile* toTile = map.getTile(newPos); + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (Creature* creature = thing->getCreature()) { + ReturnValue ret = toTile->queryAdd(0, *creature, 1, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + Position fromPos = creature->getPosition(); + if (Position::getOffsetX(fromPos, newPos) <= 0) { + if (Position::getOffsetX(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_EAST); + } else if (Position::getOffsetY(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_SOUTH); + } else if (Position::getOffsetY(fromPos, newPos) > 0) { + internalCreatureTurn(creature, DIRECTION_NORTH); + } + } else { + internalCreatureTurn(creature, DIRECTION_WEST); + } + + map.moveCreature(*creature, *toTile, !pushMove); + return RETURNVALUE_NOERROR; + } else if (Item* item = thing->getItem()) { + return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, flags); + } + return RETURNVALUE_NOTPOSSIBLE; +} + +//Implementation of player invoked events +void Game::playerMove(uint32_t playerId, Direction direction) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + player->setNextWalkActionTask(nullptr); + + player->startAutoWalk(std::forward_list { direction }); +} + +bool Game::playerBroadcastMessage(Player* player, const std::string& text) const +{ + if (!player->hasFlag(PlayerFlag_CanBroadcast)) { + return false; + } + + std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"." << std::endl; + + for (const auto& it : players) { + it.second->sendPrivateMessage(player, TALKTYPE_BROADCAST, text); + } + + return true; +} + +void Game::playerCreatePrivateChannel(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + ChatChannel* channel = g_chat->createChannel(*player, CHANNEL_PRIVATE); + if (!channel || !channel->addUser(*player)) { + return; + } + + player->sendCreatePrivateChannel(channel->getId(), channel->getName()); +} + +void Game::playerChannelInvite(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); + if (!channel) { + return; + } + + Player* invitePlayer = getPlayerByName(name); + if (!invitePlayer) { + return; + } + + if (player == invitePlayer) { + return; + } + + channel->invitePlayer(*player, *invitePlayer); +} + +void Game::playerChannelExclude(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); + if (!channel) { + return; + } + + Player* excludePlayer = getPlayerByName(name); + if (!excludePlayer) { + return; + } + + if (player == excludePlayer) { + return; + } + + channel->excludePlayer(*player, *excludePlayer); +} + +void Game::playerRequestChannels(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendChannelsDialog(); +} + +void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + ChatChannel* channel = g_chat->addUserToChannel(*player, channelId); + if (!channel) { + return; + } + + if (channel->getId() == CHANNEL_RULE_REP) { + player->sendRuleViolationsChannel(channel->getId()); + } else { + player->sendChannel(channel->getId(), channel->getName()); + } +} + +void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + g_chat->removeUserFromChannel(*player, channelId); +} + +void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!IOLoginData::formatPlayerName(receiver)) { + player->sendCancelMessage("A player with this name does not exist."); + return; + } + + if (player->getName() == receiver) { + player->sendCancelMessage("You cannot set up a private message channel with yourself."); + return; + } + + player->sendOpenPrivateChannel(receiver); +} + +void Game::playerReceivePing(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->receivePing(); +} + +void Game::playerReceivePingBack(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendPingBack(); +} + +void Game::playerAutoWalk(uint32_t playerId, const std::forward_list& listDir) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + player->setNextWalkTask(nullptr); + player->startAutoWalk(listDir); +} + +void Game::playerStopAutoWalk(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->stopWalk(); +} + +void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId, + const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); + if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + return; + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != fromSpriteId) || (!item->isDisguised() && item->getID() != fromSpriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + if (ret == RETURNVALUE_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + if (ret == RETURNVALUE_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && + !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = nullptr; + + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItemEx, this, + playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItemEx, this, + playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey); +} + +void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint8_t index, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + bool isHotkey = (pos.x == 0xFFFF && pos.y == 0 && pos.z == 0); + if (isHotkey && !g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + ReturnValue ret = g_actions->canUse(player, pos); + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId)); + player->setNextWalkActionTask(task); + return; + } + + ret = RETURNVALUE_THEREISNOWAY; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItem(player, pos, index, item, isHotkey); +} + +void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { + return; + } + + bool isHotkey = (fromPos.x == 0xFFFF && fromPos.y == 0 && fromPos.z == 0); + if (!g_config.getBoolean(ConfigManager::AIMBOT_HOTKEY_ENABLED)) { + if (creature->getPlayer() || isHotkey) { + player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT); + return; + } + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + Position toPos = creature->getPosition(); + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + if (ret == RETURNVALUE_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + if (ret == RETURNVALUE_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = nullptr; + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseWithCreature, this, + playerId, itemPos, itemStackPos, creatureId, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseWithCreature, this, + playerId, fromPos, fromStackPos, creatureId, spriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, isHotkey, creature); +} + +void Game::playerCloseContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->closeContainer(cid); + player->sendCloseContainer(cid); +} + +void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(cid); + if (!container) { + return; + } + + Container* parentContainer = dynamic_cast(container->getRealParent()); + if (!parentContainer) { + return; + } + + player->addContainer(cid, parentContainer); + player->sendContainer(cid, parentContainer, parentContainer->hasParent(), player->getContainerIndex(cid)); +} + +void Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(cid); + if (!container) { + return; + } + + player->sendContainer(cid, container, container->hasParent(), player->getContainerIndex(cid)); +} + +void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!thing) { + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || !item->isRotatable() || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRotateItem, this, + playerId, pos, stackPos, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + uint16_t newId = Item::items[item->getID()].rotateTo; + if (newId != 0) { + transformItem(item, newId); + } +} + +void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint16_t maxTextLength = 0; + uint32_t internalWindowTextId = 0; + + Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength); + if (text.length() > maxTextLength || windowTextId != internalWindowTextId) { + return; + } + + if (!writeItem || writeItem->isRemoved()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Cylinder* topParent = writeItem->getTopParent(); + + Player* owner = dynamic_cast(topParent); + if (owner && owner != player) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (!text.empty()) { + if (writeItem->getText() != text) { + writeItem->setText(text); + writeItem->setWriter(player->getName()); + writeItem->setDate(time(nullptr)); + } + } else { + writeItem->resetText(); + writeItem->resetWriter(); + writeItem->resetDate(); + } + + uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId; + if (newId != 0) { + transformItem(writeItem, newId); + } + + player->setWriteItem(nullptr); +} + +void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(containerId); + if (!container) { + return; + } + + if ((index % container->capacity()) != 0 || index >= container->size()) { + return; + } + + player->setContainerIndex(containerId, index); + player->sendContainer(containerId, container, container->hasParent(), index); +} + +void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint32_t internalWindowTextId; + uint32_t internalListId; + + House* house = player->getEditHouse(internalWindowTextId, internalListId); + if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && listId == 0) { + house->setAccessList(internalListId, text); + } + + player->setEditHouse(nullptr); +} + +void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* tradePartner = getPlayerByID(tradePlayerId); + if (!tradePartner || tradePartner == player) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "Sorry, not possible."); + return; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { + std::ostringstream ss; + ss << tradePartner->getName() << " tells you to move closer."; + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + return; + } + + Thing* tradeThing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!tradeThing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* tradeItem = tradeThing->getItem(); + if (!tradeItem->isPickupable() || (tradeItem->isDisguised() && tradeItem->getDisguiseId() != spriteId) || (!tradeItem->isDisguised() && tradeItem->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + const Position& playerPosition = player->getPosition(); + const Position& tradeItemPosition = tradeItem->getPosition(); + if (playerPosition.z != tradeItemPosition.z) { + player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + return; + } + + if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRequestTrade, this, + playerId, pos, stackPos, tradePlayerId, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + Container* tradeItemContainer = tradeItem->getContainer(); + if (tradeItemContainer) { + for (const auto& it : tradeItems) { + Item* item = it.first; + if (tradeItem == item) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + if (tradeItemContainer->isHoldingItem(item)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + } + } else { + for (const auto& it : tradeItems) { + Item* item = it.first; + if (tradeItem == item) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + } + } + + Container* tradeContainer = tradeItem->getContainer(); + if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "You can not trade more than 100 items."); + return; + } + + if (!g_events->eventPlayerOnTradeRequest(player, tradePartner, tradeItem)) { + return; + } + + internalStartTrade(player, tradePartner, tradeItem); +} + +bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem) +{ + if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { + player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING); + return false; + } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { + player->sendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING); + return false; + } + + player->tradePartner = tradePartner; + player->tradeItem = tradeItem; + player->tradeState = TRADE_INITIATED; + tradeItem->incrementReferenceCounter(); + tradeItems[tradeItem] = player->getID(); + + player->sendTradeItemRequest(player->getName(), tradeItem, true); + + if (tradePartner->tradeState == TRADE_NONE) { + std::ostringstream ss; + ss << player->getName() << " wants to trade with you."; + tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + tradePartner->tradeState = TRADE_ACKNOWLEDGE; + tradePartner->tradePartner = player; + } else { + Item* counterOfferItem = tradePartner->tradeItem; + player->sendTradeItemRequest(tradePartner->getName(), counterOfferItem, false); + tradePartner->sendTradeItemRequest(player->getName(), tradeItem, false); + } + + return true; +} + +void Game::playerAcceptTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) { + return; + } + + Player* tradePartner = player->tradePartner; + if (!tradePartner) { + return; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + return; + } + + player->setTradeState(TRADE_ACCEPT); + + if (tradePartner->getTradeState() == TRADE_ACCEPT) { + Item* playerTradeItem = player->tradeItem; + Item* partnerTradeItem = tradePartner->tradeItem; + + if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { + internalCloseTrade(player); + return; + } + + player->setTradeState(TRADE_TRANSFER); + tradePartner->setTradeState(TRADE_TRANSFER); + + auto it = tradeItems.find(playerTradeItem); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + it = tradeItems.find(partnerTradeItem); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + bool isSuccess = false; + + ReturnValue tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); + ReturnValue playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + tradePartnerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); + playerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + Cylinder* cylinder1 = playerTradeItem->getParent(); + Cylinder* cylinder2 = partnerTradeItem->getParent(); + + uint32_t count1 = playerTradeItem->getItemCount(); + uint32_t count2 = partnerTradeItem->getItemCount(); + + tradePartnerRet = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, playerTradeItem, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); + if (tradePartnerRet == RETURNVALUE_NOERROR) { + internalMoveItem(cylinder2, player, INDEX_WHEREEVER, partnerTradeItem, count2, nullptr, FLAG_IGNOREAUTOSTACK); + + playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); + + isSuccess = true; + } + } + } + + if (!isSuccess) { + std::string errorDescription; + + if (tradePartner->tradeItem) { + errorDescription = getTradeErrorDescription(tradePartnerRet, playerTradeItem); + tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + } + + if (player->tradeItem) { + errorDescription = getTradeErrorDescription(playerRet, partnerTradeItem); + player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + } + } + + player->setTradeState(TRADE_NONE); + player->tradeItem = nullptr; + player->tradePartner = nullptr; + player->sendTradeClose(); + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradeItem = nullptr; + tradePartner->tradePartner = nullptr; + tradePartner->sendTradeClose(); + } +} + +std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) +{ + if (item) { + if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { + std::ostringstream ss; + ss << "You do not have enough capacity to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object."; + } + + ss << std::endl << ' ' << item->getWeightDescription(); + return ss.str(); + } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { + std::ostringstream ss; + ss << "You do not have enough room to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object."; + } + + return ss.str(); + } + } + return "Trade could not be completed."; +} + +void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* tradePartner = player->tradePartner; + if (!tradePartner) { + return; + } + + Item* tradeItem; + if (lookAtCounterOffer) { + tradeItem = tradePartner->getTradeItem(); + } else { + tradeItem = player->getTradeItem(); + } + + if (!tradeItem) { + return; + } + + const Position& playerPosition = player->getPosition(); + const Position& tradeItemPosition = tradeItem->getPosition(); + + int32_t lookDistance = std::max(Position::getDistanceX(playerPosition, tradeItemPosition), + Position::getDistanceY(playerPosition, tradeItemPosition)); + + if (index == 0) { + g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem, lookDistance); + return; + } + + Container* tradeContainer = tradeItem->getContainer(); + if (!tradeContainer) { + return; + } + + std::vector containers {tradeContainer}; + size_t i = 0; + while (i < containers.size()) { + const Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } + + if (--index == 0) { + g_events->eventPlayerOnLookInTrade(player, tradePartner, item, lookDistance); + return; + } + } + } +} + +void Game::playerCloseTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + internalCloseTrade(player); +} + +void Game::internalCloseTrade(Player* player) +{ + Player* tradePartner = player->tradePartner; + if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { + return; + } + + if (player->getTradeItem()) { + auto it = tradeItems.find(player->getTradeItem()); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + player->tradeItem = nullptr; + } + + player->setTradeState(TRADE_NONE); + player->tradePartner = nullptr; + + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + player->sendTradeClose(); + + if (tradePartner) { + if (tradePartner->getTradeItem()) { + auto it = tradeItems.find(tradePartner->getTradeItem()); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + tradePartner->tradeItem = nullptr; + } + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradePartner = nullptr; + + tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + tradePartner->sendTradeClose(); + } +} + +void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_LOOK); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Position thingPos = thing->getPosition(); + if (!player->canSee(thingPos)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Position playerPos = player->getPosition(); + + int32_t lookDistance; + if (thing != player) { + lookDistance = std::max(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos)); + if (playerPos.z != thingPos.z) { + lookDistance += 15; + } + } else { + lookDistance = -1; + } + + g_events->eventPlayerOnLook(player, pos, thing, stackPos, lookDistance); +} + +void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + if (!player->canSeeCreature(creature)) { + return; + } + + const Position& creaturePos = creature->getPosition(); + if (!player->canSee(creaturePos)) { + return; + } + + int32_t lookDistance; + if (creature != player) { + const Position& playerPos = player->getPosition(); + lookDistance = std::max(Position::getDistanceX(playerPos, creaturePos), Position::getDistanceY(playerPos, creaturePos)); + if (playerPos.z != creaturePos.z) { + lookDistance += 15; + } + } else { + lookDistance = -1; + } + + g_events->eventPlayerOnLookInBattleList(player, creature, lookDistance); +} + +void Game::playerCancelAttackAndFollow(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + playerSetAttackedCreature(playerId, 0); + playerFollowCreature(playerId, 0); + player->stopWalk(); +} + +void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->getAttackedCreature() && creatureId == 0) { + player->setAttackedCreature(nullptr); + player->sendCancelTarget(); + return; + } + + Creature* attackCreature = getCreatureByID(creatureId); + if (!attackCreature) { + player->setAttackedCreature(nullptr); + player->sendCancelTarget(); + return; + } + + ReturnValue ret = Combat::canTargetCreature(player, attackCreature); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + player->sendCancelTarget(); + player->setAttackedCreature(nullptr); + return; + } + + player->setAttackedCreature(attackCreature); + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); +} + +void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->setAttackedCreature(nullptr); + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); + player->setFollowCreature(getCreatureByID(creatureId)); +} + +void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->setFightMode(fightMode); + player->setChaseMode(chaseMode); + player->setSecureMode(secureMode); +} + +void Game::playerRequestAddVip(uint32_t playerId, const std::string& name) +{ + if (name.length() > 20) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* vipPlayer = getPlayerByName(name); + if (!vipPlayer) { + uint32_t guid; + bool specialVip; + std::string formattedName = name; + if (!IOLoginData::getGuidByNameEx(guid, specialVip, formattedName)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name does not exist."); + return; + } + + if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); + return; + } + + player->addVIP(guid, formattedName, VIPSTATUS_OFFLINE); + } else { + if (vipPlayer->hasFlag(PlayerFlag_SpecialVIP) && !player->hasFlag(PlayerFlag_SpecialVIP)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); + return; + } + + if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) { + player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_ONLINE); + } else { + player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_OFFLINE); + } + } +} + +void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->removeVIP(guid); +} + +void Game::playerTurn(uint32_t playerId, Direction dir) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!g_events->eventPlayerOnTurn(player, dir)) { + return; + } + + player->resetIdleTime(); + internalCreatureTurn(player, dir); +} + +void Game::playerRequestOutfit(uint32_t playerId) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendOutfitWindow(); +} + +void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->canWear(outfit.lookType, outfit.lookAddons)) { + player->defaultOutfit = outfit; + + if (player->hasCondition(CONDITION_OUTFIT)) { + return; + } + + internalCreatureChangeOutfit(player, outfit); + } +} + +void Game::playerShowQuestLog(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendQuestLog(); +} + +void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Quest* quest = quests.getQuestByID(questId); + if (!quest) { + return; + } + + player->sendQuestLine(quest); +} + +void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + + if (playerSaySpell(player, type, text)) { + return; + } + + uint32_t muteTime = player->isMuted(); + if (muteTime > 0) { + std::ostringstream ss; + ss << "You are still muted for " << muteTime << " seconds."; + player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + return; + } + + if (!text.empty() && text.front() == '/' && player->isAccessPlayer()) { + return; + } + + player->removeMessageBuffer(); + + switch (type) { + case TALKTYPE_SAY: + internalCreatureSay(player, TALKTYPE_SAY, text, false); + break; + + case TALKTYPE_WHISPER: + playerWhisper(player, text); + break; + + case TALKTYPE_YELL: + playerYell(player, text); + break; + + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: + playerSpeakTo(player, type, receiver, text); + break; + + case TALKTYPE_CHANNEL_O: + case TALKTYPE_CHANNEL_Y: + case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: + if (channelId == CHANNEL_RULE_REP) { + playerSay(playerId, 0, TALKTYPE_SAY, receiver, text); + } else { + g_chat->talkToChannel(*player, type, text, channelId); + } + break; + + case TALKTYPE_BROADCAST: + playerBroadcastMessage(player, text); + break; + + case TALKTYPE_RVR_CHANNEL: + playerReportRuleViolationReport(player, text); + break; + + case TALKTYPE_RVR_CONTINUE: + playerContinueRuleViolationReport(player, text); + break; + + default: + break; + } +} + +bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& text) +{ + std::string words = text; + + TalkActionResult_t result = g_talkActions->playerSaySpell(player, type, words); + if (result == TALKACTION_BREAK) { + return true; + } + + result = g_spells->playerSaySpell(player, words); + if (result == TALKACTION_BREAK) { + return internalCreatureSay(player, TALKTYPE_SAY, text, false); + } else if (result == TALKACTION_FAILED) { + return true; + } + + return false; +} + +void Game::playerWhisper(Player* player, const std::string& text) +{ + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + + //send to client + for (Creature* spectator : spectators) { + if (Player* spectatorPlayer = spectator->getPlayer()) { + if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { + spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); + } else { + spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, text); + } + } + } + + //event method + for (Creature* spectator : spectators) { + spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); + } +} + +bool Game::playerYell(Player* player, const std::string& text) +{ + if (player->getLevel() == 1) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You may not yell as long as you are on level 1."); + return false; + } + + if (player->hasCondition(CONDITION_YELLTICKS)) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + return false; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0); + player->addCondition(condition); + } + + internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); + return true; +} + +bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, + const std::string& text) +{ + Player* toPlayer = getPlayerByName(receiver); + if (!toPlayer) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); + return false; + } + + if (type == TALKTYPE_PRIVATE_RED && (!player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER)) { + type = TALKTYPE_PRIVATE; + } + + toPlayer->sendPrivateMessage(player, type, text); + toPlayer->onCreatureSay(player, type, text); + + if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); + } else { + std::ostringstream ss; + ss << "Message sent to " << toPlayer->getName() << '.'; + player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + } + return true; +} + +//-- +bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const +{ + return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); +} + +bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +{ + return map.isSightClear(fromPos, toPos, floorCheck); +} + +bool Game::internalCreatureTurn(Creature* creature, Direction dir) +{ + if (creature->getDirection() == dir) { + return false; + } + + creature->setDirection(dir); + + //send to client + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureTurn(creature); + } + return true; +} + +bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) +{ + if (text.empty()) { + return false; + } + + if (!pos) { + pos = &creature->getPosition(); + } + + SpectatorVec spectators; + + if (!spectatorsPtr || spectatorsPtr->empty()) { + // This somewhat complex construct ensures that the cached SpectatorVec + // is used if available and if it can be used, else a local vector is + // used (hopefully the compiler will optimize away the construction of + // the temporary when it's not used). + if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { + map.getSpectators(spectators, *pos, false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + } else { + map.getSpectators(spectators, *pos, true, false, 30, 30, 30, 30); + } + } else { + spectators = (*spectatorsPtr); + } + + //send to client + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { + tmpPlayer->sendCreatureSay(creature, type, text, pos); + } + } + } + + //event method + for (Creature* spectator : spectators) { + spectator->onCreatureSay(creature, type, text); + } + return true; +} + +void Game::checkCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->onWalk(); + cleanup(); + } +} + +void Game::updateCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->goToFollowCreature(); + } +} + +void Game::checkCreatureAttack(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->onAttacking(0); + } +} + +void Game::checkMonsterExtraAttack(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + if (Monster* monster = creature->getMonster()) { + monster->doExtraMeleeAttack(); + } + } +} + +void Game::addCreatureCheck(Creature* creature) +{ + creature->creatureCheck = true; + + if (creature->inCheckCreaturesVector) { + // already in a vector + return; + } + + creature->inCheckCreaturesVector = true; + checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].push_back(creature); + creature->incrementReferenceCounter(); +} + +void Game::removeCreatureCheck(Creature* creature) +{ + if (creature->inCheckCreaturesVector) { + creature->creatureCheck = false; + } +} + +void Game::checkCreatures(size_t index) +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT))); + + auto& checkCreatureList = checkCreatureLists[index]; + auto it = checkCreatureList.begin(), end = checkCreatureList.end(); + while (it != end) { + Creature* creature = *it; + if (creature->creatureCheck) { + if (creature->getHealth() > 0) { + creature->onThink(EVENT_CREATURE_THINK_INTERVAL); + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + } else { + creature->onDeath(); + } + ++it; + } else { + creature->inCheckCreaturesVector = false; + it = checkCreatureList.erase(it); + ReleaseCreature(creature); + } + } + + cleanup(); +} + +void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) +{ + creature->setSpeed(varSpeedDelta); + + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), false, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); + } +} + +void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit) +{ + if (!g_events->eventCreatureOnChangeOutfit(creature, outfit)) { + return; + } + + creature->setCurrentOutfit(outfit); + + if (creature->isInvisible()) { + return; + } + + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); + } +} + +void Game::internalCreatureChangeVisible(Creature* creature, bool visible) +{ + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); + } +} + +void Game::changeLight(const Creature* creature) +{ + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureLight(creature); + } +} + +bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field) +{ + if (damage.type == COMBAT_NONE) { + return true; + } + + if (target->getPlayer() && target->isInGhostMode()) { + return true; + } + + if (damage.value > 0) { + return false; + } + + static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, const Position& targetPos) { + if (blockType == BLOCK_DEFENSE) { + addMagicEffect(targetPos, CONST_ME_POFF); + } else if (blockType == BLOCK_ARMOR) { + addMagicEffect(targetPos, CONST_ME_BLOCKHIT); + } else if (blockType == BLOCK_IMMUNITY) { + uint8_t hitEffect = 0; + switch (combatType) { + case COMBAT_UNDEFINEDDAMAGE: { + return; + } + case COMBAT_ENERGYDAMAGE: + case COMBAT_FIREDAMAGE: + case COMBAT_PHYSICALDAMAGE: { + hitEffect = CONST_ME_BLOCKHIT; + break; + } + case COMBAT_EARTHDAMAGE: { + hitEffect = CONST_ME_GREEN_RINGS; + break; + } + default: { + hitEffect = CONST_ME_POFF; + break; + } + } + addMagicEffect(targetPos, hitEffect); + } + }; + + BlockType_t primaryBlockType; + if (damage.type != COMBAT_NONE) { + damage.value = -damage.value; + primaryBlockType = target->blockHit(attacker, damage.type, damage.value, checkDefense, checkArmor, field); + + damage.value = -damage.value; + sendBlockEffect(primaryBlockType, damage.type, target->getPosition()); + } else { + primaryBlockType = BLOCK_NONE; + } + + return (primaryBlockType != BLOCK_NONE); +} + +void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect) +{ + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: { + Item* splash = nullptr; + switch (target->getRace()) { + case RACE_VENOM: + color = TEXTCOLOR_LIGHTGREEN; + effect = CONST_ME_HITBYPOISON; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_SLIME); + break; + case RACE_BLOOD: + color = TEXTCOLOR_RED; + effect = CONST_ME_DRAWBLOOD; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); + break; + case RACE_UNDEAD: + color = TEXTCOLOR_LIGHTGREY; + effect = CONST_ME_HITAREA; + break; + case RACE_FIRE: + color = TEXTCOLOR_ORANGE; + effect = CONST_ME_DRAWBLOOD; + break; + default: + color = TEXTCOLOR_NONE; + effect = CONST_ME_NONE; + break; + } + + if (splash) { + internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + startDecay(splash); + } + + break; + } + + case COMBAT_ENERGYDAMAGE: { + color = TEXTCOLOR_LIGHTBLUE; + effect = CONST_ME_ENERGYHIT; + break; + } + + case COMBAT_EARTHDAMAGE: { + color = TEXTCOLOR_LIGHTGREEN; + effect = CONST_ME_GREEN_RINGS; + break; + } + + case COMBAT_FIREDAMAGE: { + color = TEXTCOLOR_ORANGE; + effect = CONST_ME_HITBYFIRE; + break; + } + + case COMBAT_LIFEDRAIN: { + color = TEXTCOLOR_RED; + effect = CONST_ME_MAGIC_RED; + break; + } + + case COMBAT_DROWNDAMAGE: { + color = TEXTCOLOR_LIGHTBLUE; + effect = CONST_ME_LOSEENERGY; + break; + } + default: { + color = TEXTCOLOR_NONE; + effect = CONST_ME_NONE; + break; + } + } +} + +bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) +{ + const Position& targetPos = target->getPosition(); + if (damage.value > 0) { + if (target->getHealth() <= 0) { + return false; + } + + if (damage.origin != ORIGIN_NONE) { + const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); + if (!events.empty()) { + for (CreatureEvent* creatureEvent : events) { + creatureEvent->executeHealthChange(target, attacker, damage); + } + damage.origin = ORIGIN_NONE; + return combatChangeHealth(attacker, target, damage); + } + } + + int32_t realHealthChange = target->getHealth(); + target->gainHealth(attacker, damage.value); + realHealthChange = target->getHealth() - realHealthChange; + + if (realHealthChange > 0 && !target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_MAGIC_BLUE); + } + } + else { + if (Monster* monster = target->getMonster()) { + // makes monsters aggressive when damaged + // basically stands for UNDERATTACK stance under CipSoft servers + // the attacker must be valid everytime (avoid field ticks damage to trigger condition) + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && attacker) { + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_AGGRESSIVE, 3000); + monster->addCondition(condition, true); + } + } + + if (!target->isAttackable()) { + if (!target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_POFF); + } + return true; + } + + Player* attackerPlayer; + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } + else { + attackerPlayer = nullptr; + } + damage.value = std::abs(damage.value); + + int32_t healthChange = damage.value; + if (healthChange == 0) { + return true; + } + Player* targetPlayer = target->getPlayer(); + SpectatorVec spectators; + if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { + int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); + if (manaDamage != 0) { + if (damage.origin != ORIGIN_NONE) { + const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); + if (!events.empty()) { + for (CreatureEvent* creatureEvent : events) { + creatureEvent->executeManaChange(target, attacker, damage); + } + healthChange = damage.value; + if (healthChange == 0) { + return true; + } + manaDamage = std::min(targetPlayer->getMana(), healthChange); + } + } + targetPlayer->drainMana(attacker, manaDamage); + map.getSpectators(spectators, targetPos, true, true); + addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); + + std::string damageString = std::to_string(manaDamage); + + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; + } + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; + } + else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + for (Creature* spectator : spectators) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + } + + damage.value -= manaDamage; + if (damage.value < 0) { + damage.value = 0; + } + } + } + + int32_t realDamage = damage.value; + if (realDamage == 0) { + return true; + } + + int32_t targetHealth = target->getHealth(); + if (damage.value >= targetHealth) { + damage.value = targetHealth; + } + + realDamage = damage.value; + if (realDamage == 0) { + return true; + } + + if (spectators.empty()) { + map.getSpectators(spectators, targetPos, true, true); + } + + TextColor_t color = TEXTCOLOR_NONE; + uint8_t hitEffect; + if (damage.value) { + combatGetTypeInfo(damage.type, target, color, hitEffect); + if (hitEffect != CONST_ME_NONE) { + addMagicEffect(spectators, targetPos, hitEffect); + } + } + + if (color != TEXTCOLOR_NONE) { + std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint"); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << "."; + } + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " due to your own attack."; + } + else { + ss << "You lose " << damageString << " due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + std::string realDamageStr = std::to_string(realDamage); + for (Creature* spectator : spectators) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); + } + } + + if (realDamage >= targetHealth) { + for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { + if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { + return false; + } + } + } + + target->drainHealth(attacker, realDamage); + addCreatureHealth(spectators, target); + } + + return true; +} + +bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage) +{ + Player* targetPlayer = target->getPlayer(); + if (!targetPlayer) { + return true; + } + int32_t manaChange = damage.value; + if (manaChange > 0) { + if (damage.origin != ORIGIN_NONE) { + const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); + if (!events.empty()) { + for (CreatureEvent* creatureEvent : events) { + creatureEvent->executeManaChange(target, attacker, damage); + } + damage.origin = ORIGIN_NONE; + return combatChangeMana(attacker, target, damage); + } + } + targetPlayer->changeMana(manaChange); + } + else { + const Position& targetPos = target->getPosition(); + if (!target->isAttackable()) { + if (!target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_POFF); + } + return false; + } + + Player* attackerPlayer; + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } + else { + attackerPlayer = nullptr; + } + + int32_t manaLoss = std::min(targetPlayer->getMana(), -manaChange); + BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); + if (blockType != BLOCK_NONE) { + addMagicEffect(targetPos, CONST_ME_POFF); + return false; + } + + if (manaLoss <= 0) { + return true; + } + + if (damage.origin != ORIGIN_NONE) { + const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); + if (!events.empty()) { + for (CreatureEvent* creatureEvent : events) { + creatureEvent->executeManaChange(target, attacker, damage); + } + damage.origin = ORIGIN_NONE; + return combatChangeMana(attacker, target, damage); + } + } + + targetPlayer->drainMana(attacker, manaLoss); + + std::string damageString = std::to_string(manaLoss); + + SpectatorVec spectators; + map.getSpectators(spectators, targetPos, false, true); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; + } + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; + } + else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + for (Creature* spectator : spectators) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + } + } + + return true; +} + +void Game::addCreatureHealth(const Creature* target) +{ + SpectatorVec spectators; + map.getSpectators(spectators, target->getPosition(), true, true); + addCreatureHealth(spectators, target); +} + +void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* target) +{ + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureHealth(target); + } + } +} + +void Game::addMagicEffect(const Position& pos, uint8_t effect) +{ + SpectatorVec spectators; + map.getSpectators(spectators, pos, true, true); + addMagicEffect(spectators, pos, effect); +} + +void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect) +{ + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendMagicEffect(pos, effect); + } + } +} + +void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +{ + SpectatorVec spectators; + map.getSpectators(spectators, fromPos, false, true); + map.getSpectators(spectators, toPos, false, true); + addDistanceEffect(spectators, fromPos, toPos, effect); +} + +void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) +{ + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); + } + } +} + +void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + SpectatorVec spectators; + map.getSpectators(spectators, pos, false, true); + addAnimatedText(spectators, pos, color, text); +} + +void Game::addAnimatedText(const SpectatorVec& spectators, const Position& pos, uint8_t color, const std::string& text) +{ + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendAnimatedText(pos, color, text); + } + } +} + +void Game::addMonsterSayText(const Position& pos, const std::string& text) +{ + SpectatorVec spectators; + map.getSpectators(spectators, pos, false, true); + + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureSay(tmpPlayer, TALKTYPE_MONSTER_SAY, text, &pos); + } + } +} + +void Game::startDecay(Item* item) +{ + if (!item || !item->canDecay()) { + return; + } + + ItemDecayState_t decayState = item->getDecaying(); + if (decayState == DECAYING_TRUE) { + return; + } + + if (item->getDuration() > 0) { + item->incrementReferenceCounter(); + item->setDecaying(DECAYING_TRUE); + toDecayItems.push_front(item); + } else { + internalDecayItem(item); + } +} + +void Game::internalDecayItem(Item* item) +{ + const ItemType& it = Item::items[item->getID()]; + if (it.decayTo != 0) { + Item* newItem = transformItem(item, it.decayTo); + startDecay(newItem); + } else { + ReturnValue ret = internalRemoveItem(item); + if (ret != RETURNVALUE_NOERROR) { + std::cout << "[Debug - Game::internalDecayItem] internalDecayItem failed, error code: " << static_cast(ret) << ", item id: " << item->getID() << std::endl; + } + } +} + +void Game::checkDecay() +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); + + size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS; + + auto it = decayItems[bucket].begin(), end = decayItems[bucket].end(); + while (it != end) { + Item* item = *it; + if (!item->canDecay()) { + item->setDecaying(DECAYING_FALSE); + ReleaseItem(item); + it = decayItems[bucket].erase(it); + continue; + } + + int32_t duration = item->getDuration(); + int32_t decreaseTime = std::min(EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS, duration); + + duration -= decreaseTime; + item->decreaseDuration(decreaseTime); + + if (duration <= 0) { + it = decayItems[bucket].erase(it); + internalDecayItem(item); + ReleaseItem(item); + } else if (duration < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + it = decayItems[bucket].erase(it); + size_t newBucket = (bucket + ((duration + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAY_BUCKETS; + if (newBucket == bucket) { + internalDecayItem(item); + ReleaseItem(item); + } else { + decayItems[newBucket].push_back(item); + } + } else { + ++it; + } + } + + lastBucket = bucket; + cleanup(); +} + +void Game::checkLight() +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + + lightHour += lightHourDelta; + + if (lightHour > 1440) { + lightHour -= 1440; + } + + if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNRISE; + } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNSET; + } + + int32_t newLightLevel = lightLevel; + bool lightChange = false; + + switch (lightState) { + case LIGHT_STATE_SUNRISE: { + newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + case LIGHT_STATE_SUNSET: { + newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + default: + break; + } + + if (newLightLevel <= LIGHT_LEVEL_NIGHT) { + lightLevel = LIGHT_LEVEL_NIGHT; + lightState = LIGHT_STATE_NIGHT; + } else if (newLightLevel >= LIGHT_LEVEL_DAY) { + lightLevel = LIGHT_LEVEL_DAY; + lightState = LIGHT_STATE_DAY; + } else { + lightLevel = newLightLevel; + } + + if (lightChange) { + LightInfo lightInfo = getWorldLightInfo(); + + for (const auto& it : players) { + it.second->sendWorldLight(lightInfo); + } + } +} + +LightInfo Game::getWorldLightInfo() const +{ + return { lightLevel, 0xD7 }; +} + +void Game::shutdown() +{ + std::cout << "Shutting down..." << std::flush; + + g_scheduler.shutdown(); + g_databaseTasks.shutdown(); + g_dispatcher.shutdown(); + map.spawns.clear(); + raids.clear(); + + cleanup(); + + if (serviceManager) { + serviceManager->stop(); + } + + ConnectionManager::getInstance().closeAll(); + + std::cout << " done!" << std::endl; +} + +void Game::cleanup() +{ + //free memory + for (auto creature : ToReleaseCreatures) { + creature->decrementReferenceCounter(); + } + ToReleaseCreatures.clear(); + + for (auto item : ToReleaseItems) { + item->decrementReferenceCounter(); + } + ToReleaseItems.clear(); + + for (Item* item : toDecayItems) { + const uint32_t dur = item->getDuration(); + if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + decayItems[lastBucket].push_back(item); + } else { + decayItems[(lastBucket + 1 + dur / 1000) % EVENT_DECAY_BUCKETS].push_back(item); + } + } + toDecayItems.clear(); +} + +void Game::ReleaseCreature(Creature* creature) +{ + ToReleaseCreatures.push_back(creature); +} + +void Game::ReleaseItem(Item* item) +{ + ToReleaseItems.push_back(item); +} + +void Game::broadcastMessage(const std::string& text, MessageClasses type) const +{ + std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl; + for (const auto& it : players) { + it.second->sendTextMessage(type, text); + } +} + +void Game::updateCreatureSkull(const Creature* creature) +{ + if (getWorldType() != WORLD_TYPE_PVP) { + return; + } + + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureSkull(creature); + } +} + +void Game::updatePlayerShield(Player* player) +{ + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureShield(player); + } +} + +void Game::updatePremium(Account& account) +{ + bool save = false; + time_t timeNow = time(nullptr); + + if (account.premiumDays != 0 && account.premiumDays != std::numeric_limits::max()) { + if (account.lastDay == 0) { + account.lastDay = timeNow; + save = true; + } else { + uint32_t days = (timeNow - account.lastDay) / 86400; + if (days > 0) { + if (days >= account.premiumDays) { + account.premiumDays = 0; + account.lastDay = 0; + } else { + account.premiumDays -= days; + time_t remainder = (timeNow - account.lastDay) % 86400; + account.lastDay = timeNow - remainder; + } + + save = true; + } + } + } else if (account.lastDay != 0) { + account.lastDay = 0; + save = true; + } + + if (save && !IOLoginData::saveAccount(account)) { + std::cout << "> ERROR: Failed to save account: " << account.id << "!" << std::endl; + } +} + +void Game::loadMotdNum() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); + if (result) { + motdNum = result->getNumber("value"); + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); + } + + result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); + if (result) { + motdHash = result->getString("value"); + if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { + ++motdNum; + } + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); + } +} + +void Game::saveMotdNum() const +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; + db->executeQuery(query.str()); + + query.str(std::string()); + query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; + db->executeQuery(query.str()); +} + +void Game::checkPlayersRecord() +{ + const size_t playersOnline = getPlayersOnline(); + if (playersOnline > playersRecord) { + uint32_t previousRecord = playersRecord; + playersRecord = playersOnline; + + for (const auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { + it.second->executeRecord(playersRecord, previousRecord); + } + updatePlayersRecord(); + } +} + +void Game::updatePlayersRecord() const +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; + db->executeQuery(query.str()); +} + +void Game::loadPlayersRecord() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); + if (result) { + playersRecord = result->getNumber("value"); + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); + } +} + +uint64_t Game::getExperienceStage(uint32_t level) +{ + if (!stagesEnabled) { + return g_config.getNumber(ConfigManager::RATE_EXPERIENCE); + } + + if (useLastStageLevel && level >= lastStageLevel) { + return stages[lastStageLevel]; + } + + return stages[level]; +} + +bool Game::loadExperienceStages() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/stages.xml"); + if (!result) { + printXMLError("Error - Game::loadExperienceStages", "data/XML/stages.xml", result); + return false; + } + + for (auto stageNode : doc.child("stages").children()) { + if (strcasecmp(stageNode.name(), "config") == 0) { + stagesEnabled = stageNode.attribute("enabled").as_bool(); + } else { + uint32_t minLevel, maxLevel, multiplier; + + pugi::xml_attribute minLevelAttribute = stageNode.attribute("minlevel"); + if (minLevelAttribute) { + minLevel = pugi::cast(minLevelAttribute.value()); + } else { + minLevel = 1; + } + + pugi::xml_attribute maxLevelAttribute = stageNode.attribute("maxlevel"); + if (maxLevelAttribute) { + maxLevel = pugi::cast(maxLevelAttribute.value()); + } else { + maxLevel = 0; + lastStageLevel = minLevel; + useLastStageLevel = true; + } + + pugi::xml_attribute multiplierAttribute = stageNode.attribute("multiplier"); + if (multiplierAttribute) { + multiplier = pugi::cast(multiplierAttribute.value()); + } else { + multiplier = 1; + } + + if (useLastStageLevel) { + stages[lastStageLevel] = multiplier; + } else { + for (uint32_t i = minLevel; i <= maxLevel; ++i) { + stages[i] = multiplier; + } + } + } + } + return true; +} + +void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) +{ + if (playerId == invitedId) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + if (!invitedPlayer || invitedPlayer->isInviting(player)) { + return; + } + + if (invitedPlayer->getParty()) { + std::ostringstream ss; + ss << invitedPlayer->getName() << " is already in a party."; + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + + Party* party = player->getParty(); + if (!party) { + party = new Party(player); + } else if (party->getLeader() != player) { + return; + } + + party->invitePlayer(*invitedPlayer); +} + +void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* leader = getPlayerByID(leaderId); + if (!leader || !leader->isInviting(player)) { + return; + } + + Party* party = leader->getParty(); + if (!party || party->getLeader() != leader) { + return; + } + + if (player->getParty()) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party."); + return; + } + + party->joinParty(*player); +} + +void Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || party->getLeader() != player) { + return; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + if (!invitedPlayer || !player->isInviting(invitedPlayer)) { + return; + } + + party->revokeInvitation(*invitedPlayer); +} + +void Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || party->getLeader() != player) { + return; + } + + Player* newLeader = getPlayerByID(newLeaderId); + if (!newLeader || !player->isPartner(newLeader)) { + return; + } + + party->passPartyLeadership(newLeader); +} + +void Game::playerLeaveParty(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return; + } + + party->leaveParty(player); +} + +void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return; + } + + party->setSharedExperience(player, sharedExpActive); +} + +void Game::playerProcessRuleViolationReport(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + return; + } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + auto it = ruleViolations.find(reporter->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + if (!ruleViolation.pending) { + return; + } + + ruleViolation.gamemasterId = player->getID(); + ruleViolation.pending = false; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendRemoveRuleViolationReport(reporter->getName()); + } + } + } +} + +void Game::playerCloseRuleViolationReport(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + closeRuleViolationReport(reporter); +} + +void Game::playerCancelRuleViolationReport(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + cancelRuleViolationReport(player); +} + +void Game::playerReportRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it != ruleViolations.end()) { + player->sendCancelMessage("You already have a pending rule violation report. Close it before starting a new one."); + return; + } + + RuleViolation ruleViolation = RuleViolation(player->getID(), text); + ruleViolations[player->getID()] = ruleViolation; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendToChannel(player, TALKTYPE_RVR_CHANNEL, text, CHANNEL_RULE_REP); + } + } + } +} + +void Game::playerContinueRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& rvr = it->second; + Player* toPlayer = getPlayerByID(rvr.gamemasterId); + if (!toPlayer) { + return; + } + + toPlayer->sendCreatureSay(player, TALKTYPE_RVR_CONTINUE, text, 0); + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Message sent to Counsellor."); +} + +void Game::kickPlayer(uint32_t playerId, bool displayEffect) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->kickPlayer(displayEffect); +} + +void Game::playerReportBug(uint32_t playerId, const std::string& message) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + const Position& position = player->getPosition(); + g_events->eventPlayerOnReportBug(player, message, position); +} + +void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + // TODO: move debug assertions to database + FILE* file = fopen("client_assertions.txt", "a"); + if (file) { + fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str()); + fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str()); + fclose(file); + } +} + +void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + for (CreatureEvent* creatureEvent : player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE)) { + creatureEvent->executeExtendedOpcode(player, opcode, buffer); + } +} + +void Game::closeRuleViolationReport(Player* player) +{ + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + ruleViolations.erase(it); + player->sendLockRuleViolationReport(); + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); + } + } + } +} + +void Game::cancelRuleViolationReport(Player* player) +{ + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + Player* gamemaster = getPlayerByID(ruleViolation.gamemasterId); + if (!ruleViolation.pending && gamemaster) { + // Send to the responder + gamemaster->sendRuleViolationCancel(player->getName()); + } + + // Send to channel + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); + } + } + } + + // Erase it + ruleViolations.erase(it); +} + +void Game::forceAddCondition(uint32_t creatureId, Condition* condition) +{ + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + delete condition; + return; + } + + creature->addCondition(condition, true); +} + +void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) +{ + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + creature->removeCondition(type, true); +} + +void Game::addPlayer(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames[lowercase_name] = player; + wildcardTree.insert(lowercase_name); + players[player->getID()] = player; +} + +void Game::removePlayer(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames.erase(lowercase_name); + wildcardTree.remove(lowercase_name); + players.erase(player->getID()); +} + +void Game::addNpc(Npc* npc) +{ + npcs[npc->getID()] = npc; +} + +void Game::removeNpc(Npc* npc) +{ + npcs.erase(npc->getID()); +} + +void Game::addMonster(Monster* monster) +{ + monsters[monster->getID()] = monster; +} + +void Game::removeMonster(Monster* monster) +{ + monsters.erase(monster->getID()); +} + +Guild* Game::getGuild(uint32_t id) const +{ + auto it = guilds.find(id); + if (it == guilds.end()) { + return nullptr; + } + return it->second; +} + +void Game::addGuild(Guild* guild) +{ + guilds[guild->getId()] = guild; +} + +void Game::removeGuild(uint32_t guildId) +{ + guilds.erase(guildId); +} + +void Game::internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable) +{ + if (stackable) { + for (Item* item : itemList) { + if (item->getItemCount() > amount) { + internalRemoveItem(item, amount); + break; + } else { + amount -= item->getItemCount(); + internalRemoveItem(item); + } + } + } else { + for (Item* item : itemList) { + internalRemoveItem(item); + } + } +} + +BedItem* Game::getBedBySleeper(uint32_t guid) const +{ + auto it = bedSleepersMap.find(guid); + if (it == bedSleepersMap.end()) { + return nullptr; + } + return it->second; +} + +void Game::setBedSleeper(BedItem* bed, uint32_t guid) +{ + bedSleepersMap[guid] = bed; +} + +void Game::removeBedSleeper(uint32_t guid) +{ + auto it = bedSleepersMap.find(guid); + if (it != bedSleepersMap.end()) { + bedSleepersMap.erase(it); + } +} + +bool Game::reload(ReloadTypes_t reloadType) +{ + switch (reloadType) { + case RELOAD_TYPE_ACTIONS: return g_actions->reload(); + case RELOAD_TYPE_CHAT: return g_chat->load(); + case RELOAD_TYPE_CONFIG: return g_config.reload(); + case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); + case RELOAD_TYPE_EVENTS: return g_events->load(); + case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); + case RELOAD_TYPE_ITEMS: return Item::items.reload(); + case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); + case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); + case RELOAD_TYPE_NPCS: { + Npcs::reload(); + return true; + } + + case RELOAD_TYPE_QUESTS: return quests.reload(); + case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); + + case RELOAD_TYPE_SPELLS: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + } + else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + } + return true; + } + + case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); + + default: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + return false; + } + else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + return false; + } + + g_actions->reload(); + g_config.reload(); + g_creatureEvents->reload(); + g_monsters.reload(); + g_moveEvents->reload(); + Npcs::reload(); + raids.reload() && raids.startup(); + g_talkActions->reload(); + Item::items.reload(); + quests.reload(); + g_globalEvents->reload(); + g_events->load(); + g_chat->load(); + return true; + } + } +} diff --git a/app/SabrehavenServer/src/game.h b/app/SabrehavenServer/src/game.h new file mode 100644 index 0000000..d47d04d --- /dev/null +++ b/app/SabrehavenServer/src/game.h @@ -0,0 +1,567 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F +#define FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F + +#include "account.h" +#include "combat.h" +#include "groups.h" +#include "map.h" +#include "position.h" +#include "item.h" +#include "container.h" +#include "player.h" +#include "raids.h" +#include "npc.h" +#include "wildcardtree.h" +#include "quests.h" + +class ServiceManager; +class Creature; +class Monster; +class Npc; +class CombatInfo; + +enum stackPosType_t { + STACKPOS_MOVE, + STACKPOS_LOOK, + STACKPOS_TOPDOWN_ITEM, + STACKPOS_USEITEM, + STACKPOS_USETARGET, +}; + +enum WorldType_t { + WORLD_TYPE_NO_PVP = 1, + WORLD_TYPE_PVP = 2, + WORLD_TYPE_PVP_ENFORCED = 3, +}; + +enum GameState_t { + GAME_STATE_STARTUP, + GAME_STATE_INIT, + GAME_STATE_NORMAL, + GAME_STATE_CLOSED, + GAME_STATE_SHUTDOWN, + GAME_STATE_CLOSING, + GAME_STATE_MAINTAIN, +}; + +enum LightState_t { + LIGHT_STATE_DAY, + LIGHT_STATE_NIGHT, + LIGHT_STATE_SUNSET, + LIGHT_STATE_SUNRISE, +}; + +struct RuleViolation { + RuleViolation() = default; + RuleViolation(uint32_t _reporterId, const std::string& _text) : + reporterId(_reporterId), + gamemasterId(0), + text(_text), + pending(true) + { + } + + uint32_t reporterId; + uint32_t gamemasterId; + std::string text; + bool pending; +}; + +static constexpr int32_t EVENT_LIGHTINTERVAL = 10000; +static constexpr int32_t EVENT_DECAYINTERVAL = 250; +static constexpr int32_t EVENT_DECAY_BUCKETS = 4; + +/** + * Main Game class. + * This class is responsible to control everything that happens + */ + +class Game +{ + public: + Game() = default; + ~Game(); + + // non-copyable + Game(const Game&) = delete; + Game& operator=(const Game&) = delete; + + void start(ServiceManager* manager); + + void forceAddCondition(uint32_t creatureId, Condition* condition); + void forceRemoveCondition(uint32_t creatureId, ConditionType_t type); + + bool loadMainMap(const std::string& filename); + void loadMap(const std::string& path); + + /** + * Get the map size - info purpose only + * \param width width of the map + * \param height height of the map + */ + void getMapDimensions(uint32_t& width, uint32_t& height) const { + width = map.width; + height = map.height; + } + + void setWorldType(WorldType_t type); + WorldType_t getWorldType() const { + return worldType; + } + + void setClientVersion(ClientVersion_t version); + ClientVersion_t getClientVersion() const { + return clientVersion; + } + + Cylinder* internalGetCylinder(Player* player, const Position& pos) const; + Thing* internalGetThing(Player* player, const Position& pos, int32_t index, + uint32_t spriteId, stackPosType_t type) const; + static void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos); + + static std::string getTradeErrorDescription(ReturnValue ret, Item* item); + + /** + * Returns a creature based on the unique creature identifier + * \param id is the unique creature id to get a creature pointer to + * \returns A Creature pointer to the creature + */ + Creature* getCreatureByID(uint32_t id); + + /** + * Returns a monster based on the unique creature identifier + * \param id is the unique monster id to get a monster pointer to + * \returns A Monster pointer to the monster + */ + Monster* getMonsterByID(uint32_t id); + + /** + * Returns a npc based on the unique creature identifier + * \param id is the unique npc id to get a npc pointer to + * \returns A NPC pointer to the npc + */ + Npc* getNpcByID(uint32_t id); + + /** + * Returns a player based on the unique creature identifier + * \param id is the unique player id to get a player pointer to + * \returns A Pointer to the player + */ + Player* getPlayerByID(uint32_t id); + + /** + * Returns a creature based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the creature + */ + Creature* getCreatureByName(const std::string& s); + + /** + * Returns a npc based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the npc + */ + Npc* getNpcByName(const std::string& s); + + /** + * Returns a player based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the player + */ + Player* getPlayerByName(const std::string& s); + + /** + * Returns a player based on guid + * \returns A Pointer to the player + */ + Player* getPlayerByGUID(const uint32_t& guid); + + /** + * Returns a player based on a string name identifier, with support for the "~" wildcard. + * \param s is the name identifier, with or without wildcard + * \param player will point to the found player (if any) + * \return "RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE" or "RETURNVALUE_NAMEISTOOAMBIGIOUS" + */ + ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player); + + /** + * Returns a player based on an account number identifier + * \param acc is the account identifier + * \returns A Pointer to the player + */ + Player* getPlayerByAccount(uint32_t acc); + + /* Place Creature on the map without sending out events to the surrounding. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); + + /** + * Place Creature on the map. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param force If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool force = false); + + /** + * Remove Creature from the map. + * Removes the Creature the map + * \param c Creature to remove + */ + bool removeCreature(Creature* creature, bool isLogout = true); + + void addCreatureCheck(Creature* creature); + static void removeCreatureCheck(Creature* creature); + + size_t getPlayersOnline() const { + return players.size(); + } + size_t getMonstersOnline() const { + return monsters.size(); + } + size_t getNpcsOnline() const { + return npcs.size(); + } + uint32_t getPlayersRecord() const { + return playersRecord; + } + + LightInfo getWorldLightInfo() const; + + ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); + ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); + + ReturnValue internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = nullptr, Item* tradeItem = nullptr); + + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER, + uint32_t flags = 0, bool test = false); + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount); + ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0); + + ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, slots_t slot = CONST_SLOT_WHEREEVER); + + /** + * Find an item of a certain type + * \param cylinder to search the item + * \param itemId is the item to remove + * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 + * meaning it's not used + * \param depthSearch if true it will check child containers aswell + * \returns A pointer to the item to an item and nullptr if not found + */ + Item* findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch = true, int32_t subType = -1) const; + + /** + * Remove/Add item(s) with a monetary value + * \param cylinder to remove the money from + * \param money is the amount to remove + * \param flags optional flags to modifiy the default behaviour + * \returns true if the removal was successful + */ + bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Add item(s) with monetary value + * \param cylinder which will receive money + * \param money the amount to give + * \param flags optional flags to modify default behavior + */ + void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Transform one item to another type/count + * \param item is the item to transform + * \param newId is the new itemid + * \param newCount is the new count value, use default value (-1) to not change it + * \returns true if the tranformation was successful + */ + Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1); + + /** + * Teleports an object to another position + * \param thing is the object to teleport + * \param newPos is the new position + * \param pushMove force teleport if false + * \param flags optional flags to modify default behavior + * \returns true if the teleportation was successful + */ + ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0); + + /** + * Turn a creature to a different direction. + * \param creature Creature to change the direction + * \param dir Direction to turn to + */ + bool internalCreatureTurn(Creature* creature, Direction dir); + + /** + * Creature wants to say something. + * \param creature Creature pointer + * \param type Type of message + * \param text The text to say + */ + bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr); + + void loadPlayersRecord(); + void checkPlayersRecord(); + + void kickPlayer(uint32_t playerId, bool displayEffect); + void playerReportBug(uint32_t playerId, const std::string& message); + void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment); + + bool internalStartTrade(Player* player, Player* partner, Item* tradeItem); + void internalCloseTrade(Player* player); + bool playerBroadcastMessage(Player* player, const std::string& text) const; + void broadcastMessage(const std::string& text, MessageClasses type) const; + + //Implementation of player invoked events + void playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count); + void playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos); + void playerMoveCreature(Player* playerId, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile); + void playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count); + void playerMoveItem(Player* player, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder); + void playerMove(uint32_t playerId, Direction direction); + void playerCreatePrivateChannel(uint32_t playerId); + void playerChannelInvite(uint32_t playerId, const std::string& name); + void playerChannelExclude(uint32_t playerId, const std::string& name); + void playerRequestChannels(uint32_t playerId); + void playerOpenChannel(uint32_t playerId, uint16_t channelId); + void playerCloseChannel(uint32_t playerId, uint16_t channelId); + void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver); + void playerReceivePing(uint32_t playerId); + void playerReceivePingBack(uint32_t playerId); + void playerAutoWalk(uint32_t playerId, const std::forward_list& listDir); + void playerStopAutoWalk(uint32_t playerId); + void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, + uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId); + void playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId); + void playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId); + void playerCloseContainer(uint32_t playerId, uint8_t cid); + void playerMoveUpContainer(uint32_t playerId, uint8_t cid); + void playerUpdateContainer(uint32_t playerId, uint8_t cid); + void playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId); + void playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text); + void playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index); + void playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text); + void playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId); + void playerAcceptTrade(uint32_t playerId); + void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index); + void playerCloseTrade(uint32_t playerId); + void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); + void playerFollowCreature(uint32_t playerId, uint32_t creatureId); + void playerCancelAttackAndFollow(uint32_t playerId); + void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode); + void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos); + void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + void playerRequestAddVip(uint32_t playerId, const std::string& name); + void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); + void playerTurn(uint32_t playerId, Direction dir); + void playerRequestOutfit(uint32_t playerId); + void playerShowQuestLog(uint32_t playerId); + void playerShowQuestLine(uint32_t playerId, uint16_t questId); + void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text); + void playerChangeOutfit(uint32_t playerId, Outfit_t outfit); + void playerInviteToParty(uint32_t playerId, uint32_t invitedId); + void playerJoinParty(uint32_t playerId, uint32_t leaderId); + void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); + void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); + void playerLeaveParty(uint32_t playerId); + void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); + void playerProcessRuleViolationReport(uint32_t playerId, const std::string& name); + void playerCloseRuleViolationReport(uint32_t playerId, const std::string& name); + void playerCancelRuleViolationReport(uint32_t playerId); + void playerReportRuleViolationReport(Player* player, const std::string& text); + void playerContinueRuleViolationReport(Player* player, const std::string& text); + void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer); + + void closeRuleViolationReport(Player* player); + void cancelRuleViolationReport(Player* player); + + static void updatePremium(Account& account); + + void cleanup(); + void shutdown(); + void ReleaseCreature(Creature* creature); + void ReleaseItem(Item* item); + + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; + bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor) const; + + void changeSpeed(Creature* creature, int32_t varSpeedDelta); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit); + void internalCreatureChangeVisible(Creature* creature, bool visible); + void changeLight(const Creature* creature); + void updateCreatureSkull(const Creature* player); + void updatePlayerShield(Player* player); + + GameState_t getGameState() const; + void setGameState(GameState_t newState); + void saveGameState(); + + //Events + void checkCreatureWalk(uint32_t creatureId); + void updateCreatureWalk(uint32_t creatureId); + void checkCreatureAttack(uint32_t creatureId); + void checkMonsterExtraAttack(uint32_t creatureId); + void checkCreatures(size_t index); + void checkLight(); + + bool combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field); + + void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect); + + bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage); + bool combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage); + + //animation help functions + void addCreatureHealth(const Creature* target); + static void addCreatureHealth(const SpectatorVec& list, const Creature* target); + void addMagicEffect(const Position& pos, uint8_t effect); + static void addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect); + void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); + static void addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect); + void addAnimatedText(const Position& pos, uint8_t color, const std::string& text); + static void addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text); + void addMonsterSayText(const Position& pos, const std::string& text); + + void startDecay(Item* item); + int32_t getLightHour() const { + return lightHour; + } + + bool loadExperienceStages(); + uint64_t getExperienceStage(uint32_t level); + + void loadMotdNum(); + void saveMotdNum() const; + const std::string& getMotdHash() const { return motdHash; } + uint32_t getMotdNum() const { return motdNum; } + void incrementMotdNum() { motdNum++; } + + const std::unordered_map& getRuleViolationReports() const { return ruleViolations; } + const std::unordered_map& getPlayers() const { return players; } + const std::map& getNpcs() const { return npcs; } + + void addPlayer(Player* player); + void removePlayer(Player* player); + + void addNpc(Npc* npc); + void removeNpc(Npc* npc); + + void addMonster(Monster* npc); + void removeMonster(Monster* npc); + + Guild* getGuild(uint32_t id) const; + void addGuild(Guild* guild); + void removeGuild(uint32_t guildId); + + void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); + + BedItem* getBedBySleeper(uint32_t guid) const; + void setBedSleeper(BedItem* bed, uint32_t guid); + void removeBedSleeper(uint32_t guid); + bool reload(ReloadTypes_t reloadType); + Groups groups; + Map map; + Raids raids; + Quests quests; + + protected: + bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); + void playerWhisper(Player* player, const std::string& text); + bool playerYell(Player* player, const std::string& text); + bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text); + + void checkDecay(); + void internalDecayItem(Item* item); + + //list of reported rule violations, for correct channel listing + std::unordered_map ruleViolations; + + std::unordered_map players; + std::unordered_map mappedPlayerNames; + std::unordered_map guilds; + std::map stages; + + std::list decayItems[EVENT_DECAY_BUCKETS]; + std::list checkCreatureLists[EVENT_CREATURECOUNT]; + + std::forward_list toDecayItems; + + std::vector ToReleaseCreatures; + std::vector ToReleaseItems; + + size_t lastBucket = 0; + + WildcardTreeNode wildcardTree { false }; + + std::map npcs; + std::map monsters; + + //list of items that are in trading state, mapped to the player + std::map tradeItems; + + std::map bedSleepersMap; + + static constexpr int32_t LIGHT_LEVEL_DAY = 250; + static constexpr int32_t LIGHT_LEVEL_NIGHT = 40; + static constexpr int32_t SUNSET = 1305; + static constexpr int32_t SUNRISE = 430; + + GameState_t gameState = GAME_STATE_NORMAL; + WorldType_t worldType = WORLD_TYPE_PVP; + ClientVersion_t clientVersion; + + LightState_t lightState = LIGHT_STATE_DAY; + uint8_t lightLevel = LIGHT_LEVEL_DAY; + int32_t lightHour = SUNRISE + (SUNSET - SUNRISE) / 2; + // (1440 minutes/day)/(3600 seconds/day)*10 seconds event interval + int32_t lightHourDelta = 1400 * 10 / 3600; + + ServiceManager* serviceManager = nullptr; + + void updatePlayersRecord() const; + uint32_t playersRecord = 0; + + std::string motdHash; + uint32_t motdNum = 0; + + uint32_t lastStageLevel = 0; + bool stagesEnabled = false; + bool useLastStageLevel = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/globalevent.cpp b/app/SabrehavenServer/src/globalevent.cpp new file mode 100644 index 0000000..96c9d71 --- /dev/null +++ b/app/SabrehavenServer/src/globalevent.cpp @@ -0,0 +1,337 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "globalevent.h" +#include "tools.h" +#include "scheduler.h" +#include "pugicast.h" + +extern ConfigManager g_config; + +GlobalEvents::GlobalEvents() : + scriptInterface("GlobalEvent Interface") +{ + scriptInterface.initState(); +} + +GlobalEvents::~GlobalEvents() +{ + clear(); +} + +void GlobalEvents::clearMap(GlobalEventMap& map) +{ + for (const auto& it : map) { + delete it.second; + } + map.clear(); +} + +void GlobalEvents::clear() +{ + g_scheduler.stopEvent(thinkEventId); + thinkEventId = 0; + g_scheduler.stopEvent(timerEventId); + timerEventId = 0; + + clearMap(thinkMap); + clearMap(serverMap); + clearMap(timerMap); + + scriptInterface.reInitState(); +} + +Event* GlobalEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "globalevent") != 0) { + return nullptr; + } + return new GlobalEvent(&scriptInterface); +} + +bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&) +{ + GlobalEvent* globalEvent = static_cast(event); //event is guaranteed to be a GlobalEvent + if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { + auto result = timerMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + if (timerEventId == 0) { + timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this))); + } + return true; + } + } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) { + auto result = serverMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + return true; + } + } else { // think event + auto result = thinkMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + if (thinkEventId == 0) { + thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this))); + } + return true; + } + } + + std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl; + return false; +} + +void GlobalEvents::startup() const +{ + execute(GLOBALEVENT_STARTUP); +} + +void GlobalEvents::timer() +{ + time_t now = time(nullptr); + + int64_t nextScheduledTime = std::numeric_limits::max(); + + auto it = timerMap.begin(); + while (it != timerMap.end()) { + GlobalEvent* globalEvent = it->second; + + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + if (nextExecutionTime > 0) { + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + ++it; + continue; + } + + if (!globalEvent->executeEvent()) { + it = timerMap.erase(it); + continue; + } + + nextExecutionTime = 86400; + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + + ++it; + } + + if (nextScheduledTime != std::numeric_limits::max()) { + timerEventId = g_scheduler.addEvent(createSchedulerTask(std::max(1000, nextScheduledTime * 1000), + std::bind(&GlobalEvents::timer, this))); + } +} + +void GlobalEvents::think() +{ + int64_t now = OTSYS_TIME(); + + int64_t nextScheduledTime = std::numeric_limits::max(); + for (const auto& it : thinkMap) { + GlobalEvent* globalEvent = it.second; + + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + if (nextExecutionTime > 0) { + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + continue; + } + + if (!globalEvent->executeEvent()) { + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl; + } + + nextExecutionTime = globalEvent->getInterval(); + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + } + + if (nextScheduledTime != std::numeric_limits::max()) { + thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, std::bind(&GlobalEvents::think, this))); + } +} + +void GlobalEvents::execute(GlobalEvent_t type) const +{ + for (const auto& it : serverMap) { + GlobalEvent* globalEvent = it.second; + if (globalEvent->getEventType() == type) { + globalEvent->executeEvent(); + } + } +} + +GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) +{ + switch (type) { + case GLOBALEVENT_NONE: return thinkMap; + case GLOBALEVENT_TIMER: return timerMap; + case GLOBALEVENT_STARTUP: + case GLOBALEVENT_SHUTDOWN: + case GLOBALEVENT_RECORD: { + GlobalEventMap retMap; + for (const auto& it : serverMap) { + if (it.second->getEventType() == type) { + retMap[it.first] = it.second; + } + } + return retMap; + } + default: return GlobalEventMap(); + } +} + +GlobalEvent::GlobalEvent(LuaScriptInterface* interface) : Event(interface) {} + +bool GlobalEvent::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - GlobalEvent::configureEvent] Missing name for a globalevent" << std::endl; + return false; + } + + name = nameAttribute.as_string(); + eventType = GLOBALEVENT_NONE; + + pugi::xml_attribute attr; + if ((attr = node.attribute("time"))) { + std::vector params = vectorAtoi(explodeString(attr.as_string(), ":")); + + int32_t hour = params.front(); + if (hour < 0 || hour > 23) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + + interval |= hour << 16; + + int32_t min = 0; + int32_t sec = 0; + if (params.size() > 1) { + min = params[1]; + if (min < 0 || min > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + + if (params.size() > 2) { + sec = params[2]; + if (sec < 0 || sec > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + } + } + + time_t current_time = time(nullptr); + tm* timeinfo = localtime(¤t_time); + timeinfo->tm_hour = hour; + timeinfo->tm_min = min; + timeinfo->tm_sec = sec; + + time_t difference = static_cast(difftime(mktime(timeinfo), current_time)); + if (difference < 0) { + difference += 86400; + } + + nextExecution = current_time + difference; + eventType = GLOBALEVENT_TIMER; + } else if ((attr = node.attribute("type"))) { + const char* value = attr.value(); + if (strcasecmp(value, "startup") == 0) { + eventType = GLOBALEVENT_STARTUP; + } else if (strcasecmp(value, "shutdown") == 0) { + eventType = GLOBALEVENT_SHUTDOWN; + } else if (strcasecmp(value, "record") == 0) { + eventType = GLOBALEVENT_RECORD; + } else { + std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl; + return false; + } + } else if ((attr = node.attribute("interval"))) { + interval = std::max(SCHEDULER_MINTICKS, pugi::cast(attr.value())); + nextExecution = OTSYS_TIME() + interval; + } else { + std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name << std::endl; + return false; + } + return true; +} + +std::string GlobalEvent::getScriptEventName() const +{ + switch (eventType) { + case GLOBALEVENT_STARTUP: return "onStartup"; + case GLOBALEVENT_SHUTDOWN: return "onShutdown"; + case GLOBALEVENT_RECORD: return "onRecord"; + case GLOBALEVENT_TIMER: return "onTime"; + default: return "onThink"; + } +} + +bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) +{ + //onRecord(current, old) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + lua_pushnumber(L, current); + lua_pushnumber(L, old); + return scriptInterface->callFunction(2); +} + +bool GlobalEvent::executeEvent() +{ + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + int32_t params = 0; + if (eventType == GLOBALEVENT_NONE || eventType == GLOBALEVENT_TIMER) { + lua_pushnumber(L, interval); + params = 1; + } + + return scriptInterface->callFunction(params); +} diff --git a/app/SabrehavenServer/src/globalevent.h b/app/SabrehavenServer/src/globalevent.h new file mode 100644 index 0000000..b4ba1cb --- /dev/null +++ b/app/SabrehavenServer/src/globalevent.h @@ -0,0 +1,114 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C +#define FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C +#include "baseevents.h" + +#include "const.h" + +enum GlobalEvent_t { + GLOBALEVENT_NONE, + GLOBALEVENT_TIMER, + + GLOBALEVENT_STARTUP, + GLOBALEVENT_SHUTDOWN, + GLOBALEVENT_RECORD, +}; + +class GlobalEvent; +typedef std::map GlobalEventMap; + +class GlobalEvents final : public BaseEvents +{ + public: + GlobalEvents(); + ~GlobalEvents(); + + // non-copyable + GlobalEvents(const GlobalEvents&) = delete; + GlobalEvents& operator=(const GlobalEvents&) = delete; + + void startup() const; + + void timer(); + void think(); + void execute(GlobalEvent_t type) const; + + GlobalEventMap getEventMap(GlobalEvent_t type); + static void clearMap(GlobalEventMap& map); + + protected: + std::string getScriptBaseName() const final { + return "globalevents"; + } + void clear() final; + + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + LuaScriptInterface& getScriptInterface() final { + return scriptInterface; + } + LuaScriptInterface scriptInterface; + + GlobalEventMap thinkMap, serverMap, timerMap; + int32_t thinkEventId = 0, timerEventId = 0; +}; + +class GlobalEvent final : public Event +{ + public: + explicit GlobalEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) final; + + bool executeRecord(uint32_t current, uint32_t old); + bool executeEvent(); + + GlobalEvent_t getEventType() const { + return eventType; + } + + const std::string& getName() const { + return name; + } + + uint32_t getInterval() const { + return interval; + } + + int64_t getNextExecution() const { + return nextExecution; + } + void setNextExecution(int64_t time) { + nextExecution = time; + } + + protected: + GlobalEvent_t eventType = GLOBALEVENT_NONE; + + std::string getScriptEventName() const final; + + std::string name; + int64_t nextExecution = 0; + uint32_t interval = 0; +}; + +#endif diff --git a/app/SabrehavenServer/src/groups.cpp b/app/SabrehavenServer/src/groups.cpp new file mode 100644 index 0000000..8b4460d --- /dev/null +++ b/app/SabrehavenServer/src/groups.cpp @@ -0,0 +1,57 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "groups.h" + +#include "pugicast.h" +#include "tools.h" + +bool Groups::load() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/groups.xml"); + if (!result) { + printXMLError("Error - Groups::load", "data/XML/groups.xml", result); + return false; + } + + for (auto groupNode : doc.child("groups").children()) { + Group group; + group.id = pugi::cast(groupNode.attribute("id").value()); + group.name = groupNode.attribute("name").as_string(); + group.flags = pugi::cast(groupNode.attribute("flags").value()); + group.access = groupNode.attribute("access").as_bool(); + group.maxDepotItems = pugi::cast(groupNode.attribute("maxdepotitems").value()); + group.maxVipEntries = pugi::cast(groupNode.attribute("maxvipentries").value()); + groups.push_back(group); + } + return true; +} + +Group* Groups::getGroup(uint16_t id) +{ + for (Group& group : groups) { + if (group.id == id) { + return &group; + } + } + return nullptr; +} diff --git a/app/SabrehavenServer/src/groups.h b/app/SabrehavenServer/src/groups.h new file mode 100644 index 0000000..e16adfb --- /dev/null +++ b/app/SabrehavenServer/src/groups.h @@ -0,0 +1,41 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 +#define FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 + +struct Group { + std::string name; + uint64_t flags; + uint32_t maxDepotItems; + uint32_t maxVipEntries; + uint16_t id; + bool access; +}; + +class Groups { + public: + bool load(); + Group* getGroup(uint16_t id); + + private: + std::vector groups; +}; + +#endif diff --git a/app/SabrehavenServer/src/guild.cpp b/app/SabrehavenServer/src/guild.cpp new file mode 100644 index 0000000..e6bed21 --- /dev/null +++ b/app/SabrehavenServer/src/guild.cpp @@ -0,0 +1,66 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "guild.h" + +#include "game.h" + +extern Game g_game; + +void Guild::addMember(Player* player) +{ + membersOnline.push_back(player); +} + +void Guild::removeMember(Player* player) +{ + membersOnline.remove(player); + + if (membersOnline.empty()) { + g_game.removeGuild(id); + delete this; + } +} + +GuildRank* Guild::getRankById(uint32_t rankId) +{ + for (auto& rank : ranks) { + if (rank.id == rankId) { + return &rank; + } + } + return nullptr; +} + +const GuildRank* Guild::getRankByLevel(uint8_t level) const +{ + for (const auto& rank : ranks) { + if (rank.level == level) { + return &rank; + } + } + return nullptr; +} + +void Guild::addRank(uint32_t rankId, const std::string& rankName, uint8_t level) +{ + ranks.emplace_back(rankId, rankName, level); +} diff --git a/app/SabrehavenServer/src/guild.h b/app/SabrehavenServer/src/guild.h new file mode 100644 index 0000000..3085ec9 --- /dev/null +++ b/app/SabrehavenServer/src/guild.h @@ -0,0 +1,70 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC +#define FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC + +class Player; + +struct GuildRank { + uint32_t id; + std::string name; + uint8_t level; + + GuildRank(uint32_t id, std::string name, uint8_t level) : + id(id), name(std::move(name)), level(level) {} +}; + +class Guild +{ + public: + Guild(uint32_t id, std::string name) : name(std::move(name)), id(id) {} + + void addMember(Player* player); + void removeMember(Player* player); + + uint32_t getId() const { + return id; + } + const std::string& getName() const { + return name; + } + const std::list& getMembersOnline() const { + return membersOnline; + } + uint32_t getMemberCount() const { + return memberCount; + } + void setMemberCount(uint32_t count) { + memberCount = count; + } + + GuildRank* getRankById(uint32_t id); + const GuildRank* getRankByLevel(uint8_t level) const; + void addRank(uint32_t id, const std::string& name, uint8_t level); + + private: + std::list membersOnline; + std::vector ranks; + std::string name; + uint32_t id; + uint32_t memberCount = 0; +}; + +#endif diff --git a/app/SabrehavenServer/src/house.cpp b/app/SabrehavenServer/src/house.cpp new file mode 100644 index 0000000..244f2a1 --- /dev/null +++ b/app/SabrehavenServer/src/house.cpp @@ -0,0 +1,691 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "pugicast.h" + +#include "house.h" +#include "iologindata.h" +#include "game.h" +#include "configmanager.h" +#include "bed.h" + +extern ConfigManager g_config; +extern Game g_game; + +House::House(uint32_t houseId) : id(houseId) {} + +void House::addTile(HouseTile* tile) +{ + tile->setFlag(TILESTATE_PROTECTIONZONE); + houseTiles.push_back(tile); +} + +void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/) +{ + if (updateDatabase && owner != guid) { + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; + db->executeQuery(query.str()); + } + + if (isLoaded && owner == guid) { + return; + } + + isLoaded = true; + + if (owner != 0) { + //send items to depot + if (player) { + transferToDepot(player); + } else { + transferToDepot(); + } + + for (HouseTile* tile : houseTiles) { + if (const CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + kickPlayer(nullptr, (*creatures)[i]->getPlayer()); + } + } + } + + // Remove players from beds + for (BedItem* bed : bedsList) { + if (bed->getSleeper() != 0) { + bed->wakeUp(nullptr); + } + } + + //clean access lists + owner = 0; + setAccessList(SUBOWNER_LIST, ""); + setAccessList(GUEST_LIST, ""); + + for (Door* door : doorSet) { + door->setAccessList(""); + } + + //reset paid date + paidUntil = 0; + rentWarnings = 0; + } + + if (guid != 0) { + std::string name = IOLoginData::getNameByGuid(guid); + if (!name.empty()) { + owner = guid; + ownerName = name; + } + } + + updateDoorDescription(); +} + +void House::updateDoorDescription() const +{ + std::ostringstream ss; + if (owner != 0) { + ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house."; + } else { + ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; + } + + for (const auto& it : doorSet) { + it->setSpecialDescription(ss.str()); + } +} + +AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) +{ + if (!player) { + return HOUSE_OWNER; + } + + if (player->hasFlag(PlayerFlag_CanEditHouses)) { + return HOUSE_OWNER; + } + + if (player->getGUID() == owner) { + return HOUSE_OWNER; + } + + if (subOwnerList.isInList(player)) { + return HOUSE_SUBOWNER; + } + + if (guestList.isInList(player)) { + return HOUSE_GUEST; + } + + return HOUSE_NOT_INVITED; +} + +bool House::kickPlayer(Player* player, Player* target) +{ + if (!target) { + return false; + } + + HouseTile* houseTile = dynamic_cast(target->getTile()); + if (!houseTile || houseTile->getHouse() != this) { + return false; + } + + if (getHouseAccessLevel(player) < getHouseAccessLevel(target) || target->hasFlag(PlayerFlag_CanEditHouses)) { + return false; + } + + Position oldPosition = target->getPosition(); + if (g_game.internalTeleport(target, getEntryPosition()) == RETURNVALUE_NOERROR) { + g_game.addMagicEffect(oldPosition, CONST_ME_POFF); + g_game.addMagicEffect(getEntryPosition(), CONST_ME_TELEPORT); + } + return true; +} + +void House::setAccessList(uint32_t listId, const std::string& textlist) +{ + if (listId == GUEST_LIST) { + guestList.parseList(textlist); + } else if (listId == SUBOWNER_LIST) { + subOwnerList.parseList(textlist); + } else { + Door* door = getDoorByNumber(listId); + if (door) { + door->setAccessList(textlist); + } + + // We dont have kick anyone + return; + } + + //kick uninvited players + for (HouseTile* tile : houseTiles) { + if (CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + Player* player = (*creatures)[i]->getPlayer(); + if (player && !isInvited(player)) { + kickPlayer(nullptr, player); + } + } + } + } +} + +bool House::transferToDepot() const +{ + if (townId == 0 || owner == 0) { + return false; + } + + Player* player = g_game.getPlayerByGUID(owner); + if (player) { + transferToDepot(player); + } else { + Player tmpPlayer(nullptr); + if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) { + return false; + } + + transferToDepot(&tmpPlayer); + IOLoginData::savePlayer(&tmpPlayer); + } + + return true; +} + +bool House::transferToDepot(Player* player) const +{ + if (townId == 0 || owner == 0) { + return false; + } + + ItemList moveItemList; + for (HouseTile* tile : houseTiles) { + if (const TileItemVector* items = tile->getItemList()) { + for (Item* item : *items) { + if (item->isPickupable()) { + moveItemList.push_back(item); + } else { + Container* container = item->getContainer(); + if (container) { + for (Item* containerItem : container->getItemList()) { + moveItemList.push_back(containerItem); + } + } + } + } + } + } + + for (Item* item : moveItemList) { + g_game.internalMoveItem(item->getParent(), player->getDepotLocker(getTownId(), true), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + } + + return true; +} + +bool House::getAccessList(uint32_t listId, std::string& list) const +{ + if (listId == GUEST_LIST) { + guestList.getList(list); + return true; + } else if (listId == SUBOWNER_LIST) { + subOwnerList.getList(list); + return true; + } + + Door* door = getDoorByNumber(listId); + if (!door) { + return false; + } + + return door->getAccessList(list); +} + +bool House::isInvited(const Player* player) +{ + return getHouseAccessLevel(player) != HOUSE_NOT_INVITED; +} + +void House::addDoor(Door* door) +{ + door->incrementReferenceCounter(); + doorSet.insert(door); + door->setHouse(this); + updateDoorDescription(); +} + +void House::removeDoor(Door* door) +{ + auto it = doorSet.find(door); + if (it != doorSet.end()) { + door->decrementReferenceCounter(); + doorSet.erase(it); + } +} + +void House::addBed(BedItem* bed) +{ + bedsList.push_back(bed); + bed->setHouse(this); +} + +Door* House::getDoorByNumber(uint32_t doorId) const +{ + for (Door* door : doorSet) { + if (door->getDoorId() == doorId) { + return door; + } + } + return nullptr; +} + +Door* House::getDoorByPosition(const Position& pos) +{ + for (Door* door : doorSet) { + if (door->getPosition() == pos) { + return door; + } + } + return nullptr; +} + +bool House::canEditAccessList(uint32_t listId, const Player* player) +{ + switch (getHouseAccessLevel(player)) { + case HOUSE_OWNER: + return true; + + case HOUSE_SUBOWNER: + return listId == GUEST_LIST; + + default: + return false; + } +} + +HouseTransferItem* House::getTransferItem() +{ + if (transferItem != nullptr) { + return nullptr; + } + + transfer_container.setParent(nullptr); + transferItem = HouseTransferItem::createHouseTransferItem(this); + transfer_container.addThing(transferItem); + return transferItem; +} + +void House::resetTransferItem() +{ + if (transferItem) { + Item* tmpItem = transferItem; + transferItem = nullptr; + transfer_container.setParent(nullptr); + + transfer_container.removeThing(tmpItem, tmpItem->getItemCount()); + g_game.ReleaseItem(tmpItem); + } +} + +HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house) +{ + HouseTransferItem* transferItem = new HouseTransferItem(house); + transferItem->incrementReferenceCounter(); + transferItem->setID(ITEM_DOCUMENT_RO); + transferItem->setSubType(1); + std::ostringstream ss; + ss << "It is a house transfer document for '" << house->getName() << "'."; + transferItem->setSpecialDescription(ss.str()); + return transferItem; +} + +void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner) +{ + if (event == ON_TRADE_TRANSFER) { + if (house) { + house->executeTransfer(this, owner); + } + + g_game.internalRemoveItem(this, 1); + } else if (event == ON_TRADE_CANCEL) { + if (house) { + house->resetTransferItem(); + } + } +} + +bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) +{ + if (transferItem != item) { + return false; + } + + setOwner(newOwner->getGUID()); + transferItem = nullptr; + return true; +} + +void AccessList::parseList(const std::string& list) +{ + playerList.clear(); + guildList.clear(); + allowEveryone = false; + this->list = list; + if (list.empty()) { + return; + } + + std::istringstream listStream(list); + std::string line; + + int lineNo = 1; + while (getline(listStream, line)) { + if (++lineNo > 100) { + break; + } + + trimString(line); + trim_left(line, '\t'); + trim_right(line, '\t'); + trimString(line); + + if (line.empty() || line.front() == '#' || line.length() > 100) { + continue; + } + + toLowerCaseString(line); + + std::string::size_type at_pos = line.find("@"); + if (at_pos != std::string::npos) { + addGuild(line.substr(at_pos + 1)); + } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) { + continue; // regexp no longer supported + } else { + addPlayer(line); + } + } +} + +void AccessList::addPlayer(const std::string& name) +{ + Player* player = g_game.getPlayerByName(name); + if (player) { + playerList.insert(player->getGUID()); + } else { + uint32_t guid = IOLoginData::getGuidByName(name); + if (guid != 0) { + playerList.insert(guid); + } + } +} + +void AccessList::addGuild(const std::string& name) +{ + uint32_t guildId = IOGuild::getGuildIdByName(name); + if (guildId != 0) { + guildList.insert(guildId); + } +} + +bool AccessList::isInList(const Player* player) +{ + if (allowEveryone) { + return true; + } + + auto playerIt = playerList.find(player->getGUID()); + if (playerIt != playerList.end()) { + return true; + } + + const Guild* guild = player->getGuild(); + return guild && guildList.find(guild->getId()) != guildList.end(); +} + +void AccessList::getList(std::string& list) const +{ + list = this->list; +} + +Door::Door(uint16_t type) : Item(type) {} + +Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_HOUSEDOORID) { + uint8_t doorId; + if (!propStream.read(doorId)) { + return ATTR_READ_ERROR; + } + + setDoorId(doorId); + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +void Door::setHouse(House* house) +{ + if (this->house != nullptr) { + return; + } + + this->house = house; + + if (!accessList) { + accessList.reset(new AccessList()); + } +} + +bool Door::canUse(const Player* player) +{ + if (!house) { + return true; + } + + if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) { + return true; + } + + return accessList->isInList(player); +} + +void Door::setAccessList(const std::string& textlist) +{ + if (!accessList) { + accessList.reset(new AccessList()); + } + + accessList->parseList(textlist); +} + +bool Door::getAccessList(std::string& list) const +{ + if (!house) { + return false; + } + + accessList->getList(list); + return true; +} + +void Door::onRemoved() +{ + Item::onRemoved(); + + if (house) { + house->removeDoor(this); + } +} + +House* Houses::getHouseByPlayerId(uint32_t playerId) +{ + for (const auto& it : houseMap) { + if (it.second->getOwner() == playerId) { + return it.second; + } + } + return nullptr; +} + +bool Houses::loadHousesXML(const std::string& filename) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Houses::loadHousesXML", filename, result); + return false; + } + + for (auto houseNode : doc.child("houses").children()) { + pugi::xml_attribute houseIdAttribute = houseNode.attribute("houseid"); + if (!houseIdAttribute) { + return false; + } + + int32_t houseId = pugi::cast(houseIdAttribute.value()); + + House* house = getHouse(houseId); + if (!house) { + std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << houseId << std::endl; + return false; + } + + house->setName(houseNode.attribute("name").as_string()); + + Position entryPos( + pugi::cast(houseNode.attribute("entryx").value()), + pugi::cast(houseNode.attribute("entryy").value()), + pugi::cast(houseNode.attribute("entryz").value()) + ); + if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) { + std::cout << "[Warning - Houses::loadHousesXML] House entry not set" + << " - Name: " << house->getName() + << " - House id: " << houseId << std::endl; + } + house->setEntryPos(entryPos); + + house->setRent(pugi::cast(houseNode.attribute("rent").value())); + house->setTownId(pugi::cast(houseNode.attribute("townid").value())); + + house->setOwner(0, false); + } + return true; +} + +void Houses::payHouses(RentPeriod_t rentPeriod) const +{ + if (rentPeriod == RENTPERIOD_NEVER) { + return; + } + + time_t currentTime = time(nullptr); + for (const auto& it : houseMap) { + House* house = it.second; + if (house->getOwner() == 0) { + continue; + } + + const uint32_t rent = house->getRent(); + if (rent == 0 || house->getPaidUntil() > currentTime) { + continue; + } + + const uint32_t ownerId = house->getOwner(); + Town* town = g_game.map.towns.getTown(house->getTownId()); + if (!town) { + continue; + } + + Player player(nullptr); + if (!IOLoginData::loadPlayerById(&player, ownerId)) { + // Player doesn't exist, reset house owner + house->setOwner(0); + continue; + } + + if (player.getBankBalance() >= rent) { + player.setBankBalance(player.getBankBalance() - rent); + + time_t paidUntil = currentTime; + switch (rentPeriod) { + case RENTPERIOD_DAILY: + paidUntil += 24 * 60 * 60; + break; + case RENTPERIOD_WEEKLY: + paidUntil += 24 * 60 * 60 * 7; + break; + case RENTPERIOD_MONTHLY: + paidUntil += 24 * 60 * 60 * 30; + break; + case RENTPERIOD_YEARLY: + paidUntil += 24 * 60 * 60 * 365; + break; + default: + break; + } + + house->setPaidUntil(paidUntil); + } else { + if (house->getPayRentWarnings() < 7) { + int32_t daysLeft = 7 - house->getPayRentWarnings(); + + Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED); + std::string period; + + switch (rentPeriod) { + case RENTPERIOD_DAILY: + period = "daily"; + break; + + case RENTPERIOD_WEEKLY: + period = "weekly"; + break; + + case RENTPERIOD_MONTHLY: + period = "monthly"; + break; + + case RENTPERIOD_YEARLY: + period = "annual"; + break; + + default: + break; + } + + std::ostringstream ss; + ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house."; + letter->setText(ss.str()); + g_game.internalAddItem(player.getDepotLocker(house->getTownId(), true), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + house->setPayRentWarnings(house->getPayRentWarnings() + 1); + } else { + house->setOwner(0, true, &player); + } + } + + IOLoginData::savePlayer(&player); + } +} diff --git a/app/SabrehavenServer/src/house.h b/app/SabrehavenServer/src/house.h new file mode 100644 index 0000000..54fa7df --- /dev/null +++ b/app/SabrehavenServer/src/house.h @@ -0,0 +1,314 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 +#define FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 + +#include + +#include "container.h" +#include "housetile.h" +#include "position.h" + +class House; +class BedItem; +class Player; + +class AccessList +{ + public: + void parseList(const std::string& list); + void addPlayer(const std::string& name); + void addGuild(const std::string& name); + + bool isInList(const Player* player); + + void getList(std::string& list) const; + + private: + std::string list; + std::unordered_set playerList; + std::unordered_set guildList; // TODO: include ranks + bool allowEveryone = false; +}; + +class Door final : public Item +{ + public: + explicit Door(uint16_t type); + + // non-copyable + Door(const Door&) = delete; + Door& operator=(const Door&) = delete; + + Door* getDoor() final { + return this; + } + const Door* getDoor() const final { + return this; + } + + House* getHouse() { + return house; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream&) const final {} + + void setDoorId(uint32_t doorId) { + setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); + } + uint32_t getDoorId() const { + return getIntAttr(ITEM_ATTRIBUTE_DOORID); + } + + bool canUse(const Player* player); + + void setAccessList(const std::string& textlist); + bool getAccessList(std::string& list) const; + + void onRemoved() final; + + protected: + void setHouse(House* house); + + private: + House* house = nullptr; + std::unique_ptr accessList; + friend class House; +}; + +enum AccessList_t { + GUEST_LIST = 0x100, + SUBOWNER_LIST = 0x101, +}; + +enum AccessHouseLevel_t { + HOUSE_NOT_INVITED = 0, + HOUSE_GUEST = 1, + HOUSE_SUBOWNER = 2, + HOUSE_OWNER = 3, +}; + +typedef std::list HouseTileList; +typedef std::list HouseBedItemList; + +class HouseTransferItem final : public Item +{ + public: + static HouseTransferItem* createHouseTransferItem(House* house); + + explicit HouseTransferItem(House* house) : Item(0), house(house) {} + + void onTradeEvent(TradeEvents_t event, Player* owner) final; + bool canTransform() const final { + return false; + } + + protected: + House* house; +}; + +class House +{ + public: + explicit House(uint32_t houseId); + + void addTile(HouseTile* tile); + void updateDoorDescription() const; + + bool canEditAccessList(uint32_t listId, const Player* player); + // listId special values: + // GUEST_LIST guest list + // SUBOWNER_LIST subowner list + void setAccessList(uint32_t listId, const std::string& textlist); + bool getAccessList(uint32_t listId, std::string& list) const; + + bool isInvited(const Player* player); + + AccessHouseLevel_t getHouseAccessLevel(const Player* player); + bool kickPlayer(Player* player, Player* target); + + void setEntryPos(Position pos) { + posEntry = pos; + } + const Position& getEntryPosition() const { + return posEntry; + } + + void setName(std::string houseName) { + this->houseName = houseName; + } + const std::string& getName() const { + return houseName; + } + + void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr); + uint32_t getOwner() const { + return owner; + } + + void setPaidUntil(time_t paid) { + paidUntil = paid; + } + time_t getPaidUntil() const { + return paidUntil; + } + + void setRent(uint32_t rent) { + this->rent = rent; + } + uint32_t getRent() const { + return rent; + } + + void setPayRentWarnings(uint32_t warnings) { + rentWarnings = warnings; + } + uint32_t getPayRentWarnings() const { + return rentWarnings; + } + + void setTownId(uint32_t townId) { + this->townId = townId; + } + uint32_t getTownId() const { + return townId; + } + + uint32_t getId() const { + return id; + } + + void addDoor(Door* door); + void removeDoor(Door* door); + Door* getDoorByNumber(uint32_t doorId) const; + Door* getDoorByPosition(const Position& pos); + + HouseTransferItem* getTransferItem(); + void resetTransferItem(); + bool executeTransfer(HouseTransferItem* item, Player* player); + + const HouseTileList& getTiles() const { + return houseTiles; + } + + const std::set& getDoors() const { + return doorSet; + } + + + void addBed(BedItem* bed); + const HouseBedItemList& getBeds() const { + return bedsList; + } + uint32_t getBedCount() { + return static_cast(std::ceil(bedsList.size() / 2.)); //each bed takes 2 sqms of space, ceil is just for bad maps + } + + private: + bool transferToDepot() const; + bool transferToDepot(Player* player) const; + + AccessList guestList; + AccessList subOwnerList; + + Container transfer_container{ITEM_LOCKER1}; + + HouseTileList houseTiles; + std::set doorSet; + HouseBedItemList bedsList; + + std::string houseName; + std::string ownerName; + + HouseTransferItem* transferItem = nullptr; + + time_t paidUntil = 0; + + uint32_t id; + uint32_t owner = 0; + uint32_t rentWarnings = 0; + uint32_t rent = 0; + uint32_t townId = 0; + + Position posEntry = {}; + + bool isLoaded = false; +}; + +typedef std::map HouseMap; + +enum RentPeriod_t { + RENTPERIOD_DAILY, + RENTPERIOD_WEEKLY, + RENTPERIOD_MONTHLY, + RENTPERIOD_YEARLY, + RENTPERIOD_NEVER, +}; + +class Houses +{ + public: + Houses() = default; + ~Houses() { + for (const auto& it : houseMap) { + delete it.second; + } + } + + // non-copyable + Houses(const Houses&) = delete; + Houses& operator=(const Houses&) = delete; + + House* addHouse(uint32_t id) { + auto it = houseMap.find(id); + if (it != houseMap.end()) { + return it->second; + } + + House* house = new House(id); + houseMap[id] = house; + return house; + } + + House* getHouse(uint32_t houseId) { + auto it = houseMap.find(houseId); + if (it == houseMap.end()) { + return nullptr; + } + return it->second; + } + + House* getHouseByPlayerId(uint32_t playerId); + + bool loadHousesXML(const std::string& filename); + + void payHouses(RentPeriod_t rentPeriod) const; + + const HouseMap& getHouses() const { + return houseMap; + } + + private: + HouseMap houseMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/housetile.cpp b/app/SabrehavenServer/src/housetile.cpp new file mode 100644 index 0000000..f3fccf5 --- /dev/null +++ b/app/SabrehavenServer/src/housetile.cpp @@ -0,0 +1,122 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "housetile.h" +#include "house.h" +#include "game.h" + +extern Game g_game; + +HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* house) : + DynamicTile(x, y, z), house(house) {} + +void HouseTile::addThing(int32_t index, Thing* thing) +{ + Tile::addThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::internalAddThing(uint32_t index, Thing* thing) +{ + Tile::internalAddThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::updateHouse(Item* item) +{ + if (item->getParent() != this) { + return; + } + + Door* door = item->getDoor(); + if (door) { + if (door->getDoorId() != 0) { + house->addDoor(door); + } + } else { + BedItem* bed = item->getBed(); + if (bed) { + house->addBed(bed); + } + } +} + +ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor/* = nullptr*/) const +{ + if (const Creature* creature = thing.getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + return RETURNVALUE_PLAYERISNOTINVITED; + } + } else { + return RETURNVALUE_NOTPOSSIBLE; + } + } else if (thing.getItem() && actor) { + Player* actorPlayer = actor->getPlayer(); + if (!house->isInvited(actorPlayer)) { + return RETURNVALUE_CANNOTTHROW; + } + } + return Tile::queryAdd(index, thing, count, flags, actor); +} + +Tile* HouseTile::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) +{ + if (const Creature* creature = thing.getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + const Position& entryPos = house->getEntryPosition(); + Tile* destTile = g_game.map.getTile(entryPos); + if (!destTile) { + std::cout << "Error: [HouseTile::queryDestination] House entry not correct" + << " - Name: " << house->getName() + << " - House id: " << house->getId() + << " - Tile not found: " << entryPos << std::endl; + + destTile = g_game.map.getTile(player->getTemplePosition()); + if (!destTile) { + destTile = &(Tile::nullptr_tile); + } + } + + index = -1; + *destItem = nullptr; + return destTile; + } + } + } + + return Tile::queryDestination(index, thing, destItem, flags); +} diff --git a/app/SabrehavenServer/src/housetile.h b/app/SabrehavenServer/src/housetile.h new file mode 100644 index 0000000..17ef634 --- /dev/null +++ b/app/SabrehavenServer/src/housetile.h @@ -0,0 +1,52 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B +#define FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B + +#include "tile.h" + +class House; + +class HouseTile final : public DynamicTile +{ + public: + HouseTile(int32_t x, int32_t y, int32_t z, House* house); + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(int32_t index, Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + + House* getHouse() { + return house; + } + + private: + void updateHouse(Item* item); + + House* house; +}; + +#endif diff --git a/app/SabrehavenServer/src/ioguild.cpp b/app/SabrehavenServer/src/ioguild.cpp new file mode 100644 index 0000000..67b91af --- /dev/null +++ b/app/SabrehavenServer/src/ioguild.cpp @@ -0,0 +1,83 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "ioguild.h" +#include "database.h" + +uint32_t IOGuild::getGuildIdByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(name); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("id"); +} + +void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList) +{ + std::ostringstream query; + query << "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = " << guildId << " OR `guild2` = " << guildId << ") AND `status` IN (1, 4)"; + + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return; + } + + do { + uint32_t guild1 = result->getNumber("guild1"); + if (guildId != guild1) { + guildWarList.push_back(guild1); + } else { + guildWarList.push_back(result->getNumber("guild2")); + } + } while (result->next()); +} + +uint64_t IOGuild::getGuildBalance(uint32_t id) +{ + std::ostringstream query; + query << "SELECT `balance` FROM `guilds` WHERE `id` = " << id; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return 0; + } + + return result->getNumber("balance"); +} + +bool IOGuild::increaseGuildBankBalance(uint32_t guid, uint64_t bankBalance) +{ + std::ostringstream query; + query << "UPDATE `guilds` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; + return Database::getInstance()->executeQuery(query.str()); +} + +bool IOGuild::decreaseGuildBankBalance(uint32_t guid, uint64_t bankBalance) +{ + std::ostringstream query; + query << "UPDATE `guilds` SET `balance` = `balance` - " << bankBalance << " WHERE `id` = " << guid; + return Database::getInstance()->executeQuery(query.str()); +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/ioguild.h b/app/SabrehavenServer/src/ioguild.h new file mode 100644 index 0000000..6414ae5 --- /dev/null +++ b/app/SabrehavenServer/src/ioguild.h @@ -0,0 +1,36 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF +#define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF + +typedef std::vector GuildWarList; + +class IOGuild +{ + public: + static uint32_t getGuildIdByName(const std::string& name); + static void getWarList(uint32_t guildId, GuildWarList& guildWarList); + + static uint64_t getGuildBalance(uint32_t id); + static bool increaseGuildBankBalance(uint32_t guid, uint64_t bankBalance); + static bool decreaseGuildBankBalance(uint32_t guid, uint64_t bankBalance); +}; + +#endif diff --git a/app/SabrehavenServer/src/iologindata.cpp b/app/SabrehavenServer/src/iologindata.cpp new file mode 100644 index 0000000..ae1da42 --- /dev/null +++ b/app/SabrehavenServer/src/iologindata.cpp @@ -0,0 +1,987 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iologindata.h" +#include "configmanager.h" +#include "game.h" + +extern ConfigManager g_config; +extern Game g_game; + +Account IOLoginData::loadAccount(uint32_t accno) +{ + Account account; + + std::ostringstream query; + query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accno; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return account; + } + + account.id = result->getNumber("id"); + account.name = result->getNumber("name"); + account.accountType = static_cast(result->getNumber("type")); + account.premiumDays = result->getNumber("premdays"); + account.lastDay = result->getNumber("lastday"); + return account; +} + +bool IOLoginData::saveAccount(const Account& acc) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = " << acc.premiumDays << ", `lastday` = " << acc.lastDay << " WHERE `id` = " << acc.id; + return Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::loginserverAuthentication(uint32_t accountName, const std::string& password, Account& account) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `name` = " << accountName; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (transformToSHA1(password) != result->getString("password")) { + return false; + } + + account.id = result->getNumber("id"); + account.name = result->getNumber("name"); + account.accountType = static_cast(result->getNumber("type")); + account.premiumDays = result->getNumber("premdays"); + account.lastDay = result->getNumber("lastday"); + + query.str(std::string()); + query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id; + result = db->storeQuery(query.str()); + if (result) { + do { + if (result->getNumber("deletion") == 0) { + account.characters.push_back(result->getString("name")); + } + } while (result->next()); + std::sort(account.characters.begin(), account.characters.end()); + } + return true; +} + +uint32_t IOLoginData::gameworldAuthentication(uint32_t accountName, const std::string& password, std::string& characterName) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `password` FROM `accounts` WHERE `name` = " << accountName; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + + if (transformToSHA1(password) != result->getString("password")) { + return 0; + } + + uint32_t accountId = result->getNumber("id"); + + query.str(std::string()); + query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName); + result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + + if (result->getNumber("account_id") != accountId || result->getNumber("deletion") != 0) { + return 0; + } + characterName = result->getString("name"); + return accountId; +} + +AccountType_t IOLoginData::getAccountType(uint32_t accountId) +{ + std::ostringstream query; + query << "SELECT `type` FROM `accounts` WHERE `id` = " << accountId; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return ACCOUNT_TYPE_NORMAL; + } + return static_cast(result->getNumber("type")); +} + +void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `type` = " << static_cast(accountType) << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) +{ + if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + return; + } + + std::ostringstream query; + if (login) { + query << "INSERT INTO `players_online` VALUES (" << guid << ')'; + } else { + query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; + } + Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::preloadPlayer(Player* player, const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `account_id`, `group_id`, `deletion`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type`"; + if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + query << ", (SELECT `premdays` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `premium_days`"; + } + query << " FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (result->getNumber("deletion") != 0) { + return false; + } + + player->setGUID(result->getNumber("id")); + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + if (!group) { + std::cout << "[Error - IOLoginData::preloadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist." << std::endl; + return false; + } + player->setGroup(group); + player->accountNumber = result->getNumber("account_id"); + player->accountType = static_cast(result->getNumber("account_type")); + if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + player->premiumDays = result->getNumber("premium_days"); + } else { + player->premiumDays = std::numeric_limits::max(); + } + return true; +} + +bool IOLoginData::loadPlayerById(Player* player, uint32_t id) +{ + std::ostringstream query; + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `fake_player` FROM `players` WHERE `id` = " << id; + return loadPlayer(player, Database::getInstance()->storeQuery(query.str())); +} + +bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `fake_player` FROM `players` WHERE `name` = " << db->escapeString(name); + return loadPlayer(player, db->storeQuery(query.str())); +} + +bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) +{ + if (!result) { + return false; + } + + Database* db = Database::getInstance(); + + uint32_t accno = result->getNumber("account_id"); + Account acc = loadAccount(accno); + + player->setGUID(result->getNumber("id")); + player->name = result->getString("name"); + player->accountNumber = accno; + + player->accountType = acc.accountType; + + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + player->premiumDays = std::numeric_limits::max(); + } else { + player->premiumDays = acc.premiumDays; + } + + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + if (!group) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist" << std::endl; + return false; + } + player->setGroup(group); + + player->bankBalance = result->getNumber("balance"); + + player->setSex(static_cast(result->getNumber("sex"))); + player->level = std::max(1, result->getNumber("level")); + + uint64_t experience = result->getNumber("experience"); + + uint64_t currExpCount = Player::getExpForLevel(player->level); + uint64_t nextExpCount = Player::getExpForLevel(player->level + 1); + if (experience < currExpCount || experience > nextExpCount) { + experience = currExpCount; + } + + player->experience = experience; + + if (currExpCount < nextExpCount) { + player->levelPercent = Player::getPercentLevel(player->experience - currExpCount, nextExpCount - currExpCount); + } else { + player->levelPercent = 0; + } + + player->soul = result->getNumber("soul"); + player->capacity = std::max(400, result->getNumber("cap")) * 100; + player->blessings = result->getNumber("blessings"); + + unsigned long conditionsSize; + const char* conditions = result->getStream("conditions", conditionsSize); + PropStream propStream; + propStream.init(conditions, conditionsSize); + + Condition* condition = Condition::createCondition(propStream); + while (condition) { + if (condition->unserialize(propStream)) { + player->storedConditionList.push_front(condition); + } else { + delete condition; + } + condition = Condition::createCondition(propStream); + } + + if (!player->setVocation(result->getNumber("vocation"))) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " << result->getNumber("vocation") << " which doesn't exist" << std::endl; + return false; + } + + player->mana = result->getNumber("mana"); + player->manaMax = result->getNumber("manamax"); + player->magLevel = result->getNumber("maglevel"); + + uint64_t nextManaCount = player->vocation->getReqMana(player->magLevel + 1); + uint64_t manaSpent = result->getNumber("manaspent"); + if (manaSpent > nextManaCount) { + manaSpent = 0; + } + + player->manaSpent = manaSpent; + player->magLevelPercent = Player::getPercentLevel(player->manaSpent, nextManaCount); + + player->health = result->getNumber("health"); + player->healthMax = result->getNumber("healthmax"); + + player->defaultOutfit.lookType = result->getNumber("looktype"); + player->defaultOutfit.lookHead = result->getNumber("lookhead"); + player->defaultOutfit.lookBody = result->getNumber("lookbody"); + player->defaultOutfit.lookLegs = result->getNumber("looklegs"); + player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); + player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); + player->currentOutfit = player->defaultOutfit; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + player->playerKillerEnd = result->getNumber("skulltime"); + + uint16_t skull = result->getNumber("skull"); + if (skull == SKULL_RED) { + player->skull = SKULL_RED; + } + + if (player->playerKillerEnd == 0) { + player->skull = SKULL_NONE; + } + } + + player->loginPosition.x = result->getNumber("posx"); + player->loginPosition.y = result->getNumber("posy"); + player->loginPosition.z = result->getNumber("posz"); + + player->lastLoginSaved = result->getNumber("lastlogin"); + player->lastLogout = result->getNumber("lastlogout"); + player->isFakePlayer = result->getNumber("fake_player") == 1; + + player->offlineTrainingTime = result->getNumber("offlinetraining_time") * 1000; + player->offlineTrainingSkill = result->getNumber("offlinetraining_skill"); + + Town* town = g_game.map.towns.getTown(result->getNumber("town_id")); + if (!town) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber("town_id") << " which doesn't exist" << std::endl; + return false; + } + + player->town = town; + + const Position& loginPos = player->loginPosition; + if (loginPos.x == 0 && loginPos.y == 0 && loginPos.z == 0) { + player->loginPosition = player->getTemplePosition(); + } + + player->staminaMinutes = result->getNumber("stamina"); + + static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", "skill_dist", "skill_shielding", "skill_fishing"}; + static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", "skill_fishing_tries"}; + static constexpr size_t size = sizeof(skillNames) / sizeof(std::string); + for (uint8_t i = 0; i < size; ++i) { + uint16_t skillLevel = result->getNumber(skillNames[i]); + uint64_t skillTries = result->getNumber(skillNameTries[i]); + uint64_t nextSkillTries = player->vocation->getReqSkillTries(i, skillLevel + 1); + if (skillTries > nextSkillTries) { + skillTries = 0; + } + + player->skills[i].level = skillLevel; + player->skills[i].tries = skillTries; + player->skills[i].percent = Player::getPercentLevel(skillTries, nextSkillTries); + } + + std::ostringstream query; + + query << "SELECT `date` FROM `player_murders` WHERE `player_id` = " << player->getGUID() << " ORDER BY `date` ASC"; + if ((result = db->storeQuery(query.str()))) { + do { + player->murderTimeStamps.push_back(result->getNumber("date")); + } while (result->next()); + } + + query.str(std::string()); + query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + uint32_t guildId = result->getNumber("guild_id"); + uint32_t playerRankId = result->getNumber("rank_id"); + player->guildNick = result->getString("nick"); + + Guild* guild = g_game.getGuild(guildId); + if (!guild) { + query.str(std::string()); + query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; + if ((result = db->storeQuery(query.str()))) { + guild = new Guild(guildId, result->getString("name")); + g_game.addGuild(guild); + + query.str(std::string()); + query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId; + + if ((result = db->storeQuery(query.str()))) { + do { + guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); + } while (result->next()); + } + } + } + + if (guild) { + player->guild = guild; + const GuildRank* rank = guild->getRankById(playerRankId); + if (!rank) { + query.str(std::string()); + query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; + + if ((result = db->storeQuery(query.str()))) { + guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); + } + + rank = guild->getRankById(playerRankId); + if (!rank) { + player->guild = nullptr; + } + } + + player->guildRank = rank; + + IOGuild::getWarList(guildId, player->guildWarList); + + query.str(std::string()); + query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; + if ((result = db->storeQuery(query.str()))) { + guild->setMemberCount(result->getNumber("members")); + } + } + } + + query.str(std::string()); + query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + do { + player->learnedInstantSpellList.emplace_front(result->getString("name")); + } while (result->next()); + } + + //load inventory items + ItemMap itemMap; + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + if (pid >= 1 && pid <= 10) { + player->internalAddThing(pid, item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load depot items + itemMap.clear(); + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + Container* itemContainer = item->getContainer(); + if (itemContainer) { + DepotLocker* locker = itemContainer->getDepotLocker(); + if (locker) { + DepotLocker* existingLocker = player->getDepotLocker(pid, false); + if (!existingLocker) { + player->depotLockerMap[pid] = locker; + } + } + } + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load storage map + query.str(std::string()); + query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + do { + player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); + } while (result->next()); + } + + //load vip + query.str(std::string()); + query << "SELECT `player_id` FROM `account_viplist` WHERE `account_id` = " << player->getAccount(); + if ((result = db->storeQuery(query.str()))) { + do { + player->addVIPInternal(result->getNumber("player_id")); + } while (result->next()); + } + + player->updateBaseSpeed(); + player->updateInventoryWeight(); + player->updateItemsLight(true); + return true; +} + +bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +{ + std::ostringstream ss; + + typedef std::pair containerBlock; + std::list queue; + + int32_t runningId = 100; + + Database* db = Database::getInstance(); + for (const auto& it : itemList) { + int32_t pid = it.first; + Item* item = it.second; + ++runningId; + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + if (!query_insert.addRow(ss)) { + return false; + } + + if (Container* container = item->getContainer()) { + queue.emplace_back(container, runningId); + } + } + + while (!queue.empty()) { + const containerBlock& cb = queue.front(); + Container* container = cb.first; + int32_t parentId = cb.second; + queue.pop_front(); + + for (Item* item : container->getItemList()) { + ++runningId; + + Container* subContainer = item->getContainer(); + if (subContainer) { + queue.emplace_back(subContainer, runningId); + } + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + if (!query_insert.addRow(ss)) { + return false; + } + } + } + return query_insert.execute(); +} + +bool IOLoginData::savePlayer(Player* player) +{ + if (player->getHealth() <= 0) { + player->changeHealth(1); + } + + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (result->getNumber("save") == 0) { + query.str(std::string()); + query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID(); + return db->executeQuery(query.str()); + } + + //serialize conditions + PropWriteStream propWriteStream; + for (Condition* condition : player->conditions) { + if (condition->isPersistent()) { + condition->serialize(propWriteStream); + propWriteStream.write(CONDITIONATTR_END); + } + } + + size_t conditionsSize; + const char* conditions = propWriteStream.getStream(conditionsSize); + + //First, an UPDATE query to write the player itself + query.str(std::string()); + query << "UPDATE `players` SET "; + query << "`level` = " << player->level << ','; + query << "`group_id` = " << player->group->id << ','; + query << "`vocation` = " << player->getVocationId() << ','; + query << "`health` = " << player->health << ','; + query << "`healthmax` = " << player->healthMax << ','; + query << "`experience` = " << player->experience << ','; + query << "`lookbody` = " << static_cast(player->defaultOutfit.lookBody) << ','; + query << "`lookfeet` = " << static_cast(player->defaultOutfit.lookFeet) << ','; + query << "`lookhead` = " << static_cast(player->defaultOutfit.lookHead) << ','; + query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ','; + query << "`looktype` = " << player->defaultOutfit.lookType << ','; + query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ','; + query << "`maglevel` = " << player->magLevel << ','; + query << "`mana` = " << player->mana << ','; + query << "`manamax` = " << player->manaMax << ','; + query << "`manaspent` = " << player->manaSpent << ','; + query << "`soul` = " << static_cast(player->soul) << ','; + query << "`town_id` = " << player->town->getID() << ','; + + const Position& loginPosition = player->getLoginPosition(); + query << "`posx` = " << loginPosition.getX() << ','; + query << "`posy` = " << loginPosition.getY() << ','; + query << "`posz` = " << loginPosition.getZ() << ','; + + query << "`cap` = " << (player->capacity / 100) << ','; + query << "`sex` = " << player->sex << ','; + + if (player->lastLoginSaved != 0) { + query << "`lastlogin` = " << player->lastLoginSaved << ','; + } + + if (player->lastIP != 0) { + query << "`lastip` = " << player->lastIP << ','; + } + + query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ','; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + query << "`skulltime` = " << player->getPlayerKillerEnd() << ','; + + Skulls_t skull = SKULL_NONE; + if (player->skull == SKULL_RED) { + skull = SKULL_RED; + } + + query << "`skull` = " << static_cast(skull) << ','; + } + + query << "`lastlogout` = " << player->getLastLogout() << ','; + query << "`balance` = " << player->bankBalance << ','; + query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ','; + query << "`offlinetraining_skill` = " << player->getOfflineTrainingSkill() << ','; + query << "`stamina` = " << player->getStaminaMinutes() << ','; + + query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ','; + query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ','; + query << "`skill_club` = " << player->skills[SKILL_CLUB].level << ','; + query << "`skill_club_tries` = " << player->skills[SKILL_CLUB].tries << ','; + query << "`skill_sword` = " << player->skills[SKILL_SWORD].level << ','; + query << "`skill_sword_tries` = " << player->skills[SKILL_SWORD].tries << ','; + query << "`skill_axe` = " << player->skills[SKILL_AXE].level << ','; + query << "`skill_axe_tries` = " << player->skills[SKILL_AXE].tries << ','; + query << "`skill_dist` = " << player->skills[SKILL_DISTANCE].level << ','; + query << "`skill_dist_tries` = " << player->skills[SKILL_DISTANCE].tries << ','; + query << "`skill_shielding` = " << player->skills[SKILL_SHIELD].level << ','; + query << "`skill_shielding_tries` = " << player->skills[SKILL_SHIELD].tries << ','; + query << "`skill_fishing` = " << player->skills[SKILL_FISHING].level << ','; + query << "`skill_fishing_tries` = " << player->skills[SKILL_FISHING].tries << ','; + + if (!player->isOffline()) { + query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; + } + query << "`blessings` = " << static_cast(player->blessings); + query << " WHERE `id` = " << player->getGUID(); + + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery(query.str())) { + return false; + } + + // learned spells + query.str(std::string()); + query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); + for (const std::string& spellName : player->learnedInstantSpellList) { + query << player->getGUID() << ',' << db->escapeString(spellName); + if (!spellsQuery.addRow(query)) { + return false; + } + } + + if (!spellsQuery.execute()) { + return false; + } + + query.str(std::string()); + query << "DELETE FROM `player_murders` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert murdersQuery("INSERT INTO `player_murders`(`id`, `player_id`, `date`) VALUES "); + for (time_t timestamp : player->murderTimeStamps) { + query << "NULL," << player->getGUID() << ',' << timestamp; + if (!murdersQuery.addRow(query)) { + return false; + } + } + + if (!murdersQuery.execute()) { + return false; + } + + //item saving + query.str(std::string()); + query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + + ItemBlockList itemList; + for (int32_t slotId = 1; slotId <= 10; ++slotId) { + Item* item = player->inventory[slotId]; + if (item) { + itemList.emplace_back(slotId, item); + } + } + + if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { + return false; + } + + //save depot items + query.str(std::string()); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + itemList.clear(); + + for (const auto& it : player->depotLockerMap) { + itemList.emplace_back(it.first, it.second); + } + + if (!saveItems(player, itemList, depotQuery, propWriteStream)) { + return false; + } + + query.str(std::string()); + query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); + player->genReservedStorageRange(); + + for (const auto& it : player->storageMap) { + query << player->getGUID() << ',' << it.first << ',' << it.second; + if (!storageQuery.addRow(query)) { + return false; + } + } + + if (!storageQuery.execute()) { + return false; + } + + //End the transaction + return transaction.commit(); +} + +std::string IOLoginData::getNameByGuid(uint32_t guid) +{ + std::ostringstream query; + query << "SELECT `name` FROM `players` WHERE `id` = " << guid; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return std::string(); + } + return result->getString("name"); +} + +uint32_t IOLoginData::getGuidByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("id"); +} + +// Return 0 means player not found, 1 player is without vocation, 2 player with vocation +uint16_t IOLoginData::canTransferMoneyToByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `vocation` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("vocation") == 0 ? 1 : 2; +} + +bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + name = result->getString("name"); + guid = result->getNumber("id"); + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + + uint64_t flags; + if (group) { + flags = group->flags; + } else { + flags = 0; + } + + specialVip = (flags & PlayerFlag_SpecialVIP) != 0; + return true; +} + +bool IOLoginData::formatPlayerName(std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + name = result->getString("name"); + return true; +} + +void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) +{ + do { + uint32_t sid = result->getNumber("sid"); + uint32_t pid = result->getNumber("pid"); + uint16_t type = result->getNumber("itemtype"); + uint16_t count = result->getNumber("count"); + + unsigned long attrSize; + const char* attr = result->getStream("attributes", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + Item* item = Item::CreateItem(type, count); + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + } + + std::pair pair(item, pid); + itemMap[sid] = pair; + } + } while (result->next()); +} + +void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) +{ + std::ostringstream query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::increaseBankBalance(std::string name, uint64_t bankBalance) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `name` = " << db->escapeString(name); + db->executeQuery(query.str()); +} + +bool IOLoginData::hasBiddedOnHouse(uint32_t guid) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1"; + return db->storeQuery(query.str()).get() != nullptr; +} + +std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) +{ + std::forward_list entries; + + std::ostringstream query; + query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name` FROM `account_viplist` WHERE `account_id` = " << accountId; + + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (result) { + do { + entries.emplace_front( + result->getNumber("player_id"), + result->getString("name") + ); + } while (result->next()); + } + return entries; +} + +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "INSERT INTO `account_viplist` (`account_id`, `player_id`) VALUES (" << accountId << ',' << guid << ')'; + db->executeQuery(query.str()); +} + +void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) +{ + std::ostringstream query; + query << "DELETE FROM `account_viplist` WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::addPremiumDays(uint32_t accountId, int32_t addDays) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = `premdays` + " << addDays << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::removePremiumDays(uint32_t accountId, int32_t removeDays) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} diff --git a/app/SabrehavenServer/src/iologindata.h b/app/SabrehavenServer/src/iologindata.h new file mode 100644 index 0000000..6bfbecd --- /dev/null +++ b/app/SabrehavenServer/src/iologindata.h @@ -0,0 +1,70 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF +#define FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF + +#include "account.h" +#include "player.h" +#include "database.h" + +typedef std::list> ItemBlockList; + +class IOLoginData +{ + public: + static Account loadAccount(uint32_t accno); + static bool saveAccount(const Account& acc); + + static bool loginserverAuthentication(uint32_t accountName, const std::string& password, Account& account); + static uint32_t gameworldAuthentication(uint32_t accountName, const std::string& password, std::string& characterName); + + static AccountType_t getAccountType(uint32_t accountId); + static void setAccountType(uint32_t accountId, AccountType_t accountType); + static void updateOnlineStatus(uint32_t guid, bool login); + static bool preloadPlayer(Player* player, const std::string& name); + + static bool loadPlayerById(Player* player, uint32_t id); + static bool loadPlayerByName(Player* player, const std::string& name); + static bool loadPlayer(Player* player, DBResult_ptr result); + static bool savePlayer(Player* player); + static uint32_t getGuidByName(const std::string& name); + static uint16_t canTransferMoneyToByName(const std::string& name); + static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); + static std::string getNameByGuid(uint32_t guid); + static bool formatPlayerName(std::string& name); + static void increaseBankBalance(uint32_t guid, uint64_t bankBalance); + static void increaseBankBalance(const std::string name, uint64_t bankBalance); + static bool hasBiddedOnHouse(uint32_t guid); + + static std::forward_list getVIPEntries(uint32_t accountId); + static void addVIPEntry(uint32_t accountId, uint32_t guid); + static void removeVIPEntry(uint32_t accountId, uint32_t guid); + + static void addPremiumDays(uint32_t accountId, int32_t addDays); + static void removePremiumDays(uint32_t accountId, int32_t removeDays); + + protected: + typedef std::map> ItemMap; + + static void loadItems(ItemMap& itemMap, DBResult_ptr result); + static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream); +}; + +#endif diff --git a/app/SabrehavenServer/src/iomap.cpp b/app/SabrehavenServer/src/iomap.cpp new file mode 100644 index 0000000..197bf97 --- /dev/null +++ b/app/SabrehavenServer/src/iomap.cpp @@ -0,0 +1,462 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomap.h" + +#include "bed.h" + +/* + OTBM_ROOTV1 + | + |--- OTBM_MAP_DATA + | | + | |--- OTBM_TILE_AREA + | | |--- OTBM_TILE + | | |--- OTBM_TILE_SQUARE (not implemented) + | | |--- OTBM_TILE_REF (not implemented) + | | |--- OTBM_HOUSETILE + | | + | |--- OTBM_SPAWNS (not implemented) + | | |--- OTBM_SPAWN_AREA (not implemented) + | | |--- OTBM_MONSTER (not implemented) + | | + | |--- OTBM_TOWNS + | | |--- OTBM_TOWN + | | + | |--- OTBM_WAYPOINTS + | |--- OTBM_WAYPOINT + | + |--- OTBM_ITEM_DEF (not implemented) +*/ + +Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z) +{ + if (!ground) { + return new StaticTile(x, y, z); + } + + Tile* tile; + if ((item && item->isBlocking()) || ground->isBlocking()) { + tile = new StaticTile(x, y, z); + } else { + tile = new DynamicTile(x, y, z); + } + + tile->internalAddThing(ground); + ground->startDecaying(); + ground = nullptr; + return tile; +} + +bool IOMap::loadMap(Map* map, const std::string& identifier) +{ + int64_t start = OTSYS_TIME(); + + FileLoader f; + if (!f.openFile(identifier.c_str(), "OTBM")) { + std::ostringstream ss; + ss << "Could not open the file " << identifier << '.'; + setLastErrorString(ss.str()); + return false; + } + + uint32_t type; + PropStream propStream; + + NODE root = f.getChildNode(nullptr, type); + if (!f.getProps(root, propStream)) { + setLastErrorString("Could not read root property."); + return false; + } + + OTBM_root_header root_header; + if (!propStream.read(root_header)) { + setLastErrorString("Could not read header."); + return false; + } + + uint32_t headerVersion = root_header.version; + if (headerVersion <= 0) { + //In otbm version 1 the count variable after splashes/fluidcontainers and stackables + //are saved as attributes instead, this solves alot of problems with items + //that is changed (stackable/charges/fluidcontainer/splash) during an update. + setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + return false; + } + + if (headerVersion > 2) { + setLastErrorString("Unknown OTBM version detected."); + return false; + } + + std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl; + map->width = root_header.width; + map->height = root_header.height; + + NODE nodeMap = f.getChildNode(root, type); + if (type != OTBM_MAP_DATA) { + setLastErrorString("Could not read data node."); + return false; + } + + if (!f.getProps(nodeMap, propStream)) { + setLastErrorString("Could not read map data attributes."); + return false; + } + + std::string mapDescription; + std::string tmp; + + uint8_t attribute; + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_DESCRIPTION: + if (!propStream.readString(mapDescription)) { + setLastErrorString("Invalid description tag."); + return false; + } + break; + + case OTBM_ATTR_EXT_SPAWN_FILE: + if (!propStream.readString(tmp)) { + setLastErrorString("Invalid spawn tag."); + return false; + } + + map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1); + map->spawnfile += tmp; + break; + + case OTBM_ATTR_EXT_HOUSE_FILE: + if (!propStream.readString(tmp)) { + setLastErrorString("Invalid house tag."); + return false; + } + + map->housefile = identifier.substr(0, identifier.rfind('/') + 1); + map->housefile += tmp; + break; + + default: + setLastErrorString("Unknown header node."); + return false; + } + } + + NODE nodeMapData = f.getChildNode(nodeMap, type); + while (nodeMapData != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Invalid map node."); + return false; + } + + if (type == OTBM_TILE_AREA) { + if (!f.getProps(nodeMapData, propStream)) { + setLastErrorString("Invalid map node."); + return false; + } + + OTBM_Destination_coords area_coord; + if (!propStream.read(area_coord)) { + setLastErrorString("Invalid map node."); + return false; + } + + uint16_t base_x = area_coord.x; + uint16_t base_y = area_coord.y; + uint16_t z = area_coord.z; + + NODE nodeTile = f.getChildNode(nodeMapData, type); + while (nodeTile != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Could not read node data."); + return false; + } + + if (type != OTBM_TILE && type != OTBM_HOUSETILE) { + setLastErrorString("Unknown tile node."); + return false; + } + + if (!f.getProps(nodeTile, propStream)) { + setLastErrorString("Could not read node data."); + return false; + } + + OTBM_Tile_coords tile_coord; + if (!propStream.read(tile_coord)) { + setLastErrorString("Could not read tile position."); + return false; + } + + uint16_t x = base_x + tile_coord.x; + uint16_t y = base_y + tile_coord.y; + + bool isHouseTile = false; + House* house = nullptr; + Tile* tile = nullptr; + Item* ground_item = nullptr; + uint32_t tileflags = TILESTATE_NONE; + + if (type == OTBM_HOUSETILE) { + uint32_t houseId; + if (!propStream.read(houseId)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; + setLastErrorString(ss.str()); + return false; + } + + house = map->houses.addHouse(houseId); + if (!house) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId; + setLastErrorString(ss.str()); + return false; + } + + tile = new HouseTile(x, y, z, house); + house->addTile(static_cast(tile)); + isHouseTile = true; + } + + //read tile attributes + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_TILE_FLAGS: { + uint32_t flags; + if (!propStream.read(flags)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags."; + setLastErrorString(ss.str()); + return false; + } + + if ((flags & OTBM_TILEFLAG_PROTECTIONZONE) != 0) { + tileflags |= TILESTATE_PROTECTIONZONE; + } else if ((flags & OTBM_TILEFLAG_NOPVPZONE) != 0) { + tileflags |= TILESTATE_NOPVPZONE; + } else if ((flags & OTBM_TILEFLAG_PVPZONE) != 0) { + tileflags |= TILESTATE_PVPZONE; + } + + if ((flags & OTBM_TILEFLAG_REFRESH) != 0) { + tileflags |= TILESTATE_REFRESH; + } + + if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) { + tileflags |= TILESTATE_NOLOGOUT; + } + break; + } + + case OTBM_ATTR_ITEM: { + Item* item = Item::CreateItem(propStream); + if (!item) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (isHouseTile && item->isMoveable()) { + //std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + delete item; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, x, y, z); + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } + } + break; + } + + default: + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute."; + setLastErrorString(ss.str()); + return false; + } + } + + NODE nodeItem = f.getChildNode(nodeTile, type); + while (nodeItem) { + if (type != OTBM_ITEM) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type."; + setLastErrorString(ss.str()); + return false; + } + + PropStream stream; + if (!f.getProps(nodeItem, stream)) { + setLastErrorString("Invalid item node."); + return false; + } + + Item* item = Item::CreateItem(stream); + if (!item) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (!item->unserializeItemNode(f, nodeItem, stream)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.'; + setLastErrorString(ss.str()); + delete item; + return false; + } + + if (isHouseTile && item->isMoveable()) { + //std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + delete item; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, x, y, z); + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } + } + + nodeItem = f.getNextNode(nodeItem, type); + } + + if (!tile) { + tile = createTile(ground_item, nullptr, x, y, z); + } + + tile->setFlag(static_cast(tileflags)); + + map->setTile(x, y, z, tile); + + nodeTile = f.getNextNode(nodeTile, type); + } + } else if (type == OTBM_TOWNS) { + NODE nodeTown = f.getChildNode(nodeMapData, type); + while (nodeTown != NO_NODE) { + if (type != OTBM_TOWN) { + setLastErrorString("Unknown town node."); + return false; + } + + if (!f.getProps(nodeTown, propStream)) { + setLastErrorString("Could not read town data."); + return false; + } + + uint32_t townId; + if (!propStream.read(townId)) { + setLastErrorString("Could not read town id."); + return false; + } + + Town* town = map->towns.getTown(townId); + if (!town) { + town = new Town(townId); + map->towns.addTown(townId, town); + } + + std::string townName; + if (!propStream.readString(townName)) { + setLastErrorString("Could not read town name."); + return false; + } + + town->setName(townName); + + OTBM_Destination_coords town_coords; + if (!propStream.read(town_coords)) { + setLastErrorString("Could not read town coordinates."); + return false; + } + + town->setTemplePos(Position(town_coords.x, town_coords.y, town_coords.z)); + + nodeTown = f.getNextNode(nodeTown, type); + } + } else if (type == OTBM_WAYPOINTS) { + NODE nodeWaypoint = f.getChildNode(nodeMapData, type); + while (nodeWaypoint != NO_NODE) { + if (type != OTBM_WAYPOINT) { + setLastErrorString("Unknown waypoint node."); + return false; + } + + if (!f.getProps(nodeWaypoint, propStream)) { + setLastErrorString("Could not read waypoint data."); + return false; + } + + std::string name; + if (!propStream.readString(name)) { + setLastErrorString("Could not read waypoint name."); + return false; + } + + OTBM_Destination_coords waypoint_coords; + if (!propStream.read(waypoint_coords)) { + setLastErrorString("Could not read waypoint coordinates."); + return false; + } + + map->waypoints[name] = Position(waypoint_coords.x, waypoint_coords.y, waypoint_coords.z); + + nodeWaypoint = f.getNextNode(nodeWaypoint, type); + } + } else { + setLastErrorString("Unknown map node."); + return false; + } + + nodeMapData = f.getNextNode(nodeMapData, type); + } + + std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return true; +} diff --git a/app/SabrehavenServer/src/iomap.h b/app/SabrehavenServer/src/iomap.h new file mode 100644 index 0000000..5062e98 --- /dev/null +++ b/app/SabrehavenServer/src/iomap.h @@ -0,0 +1,161 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 +#define FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 + +#include "item.h" +#include "map.h" +#include "house.h" +#include "spawn.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +enum OTBM_AttrTypes_t { + OTBM_ATTR_DESCRIPTION = 1, + OTBM_ATTR_EXT_FILE = 2, + OTBM_ATTR_TILE_FLAGS = 3, + OTBM_ATTR_ACTION_ID = 4, + OTBM_ATTR_MOVEMENT_ID = 5, + OTBM_ATTR_TEXT = 6, + OTBM_ATTR_DESC = 7, + OTBM_ATTR_TELE_DEST = 8, + OTBM_ATTR_ITEM = 9, + OTBM_ATTR_DEPOT_ID = 10, + OTBM_ATTR_EXT_SPAWN_FILE = 11, + OTBM_ATTR_RUNE_CHARGES = 12, + OTBM_ATTR_EXT_HOUSE_FILE = 13, + OTBM_ATTR_HOUSEDOORID = 14, + OTBM_ATTR_COUNT = 15, + OTBM_ATTR_DURATION = 16, + OTBM_ATTR_DECAYING_STATE = 17, + OTBM_ATTR_WRITTENDATE = 18, + OTBM_ATTR_WRITTENBY = 19, + OTBM_ATTR_SLEEPERGUID = 20, + OTBM_ATTR_SLEEPSTART = 21, + OTBM_ATTR_CHARGES = 22, + OTBM_ATTR_KEYNUMBER = 23, + OTBM_ATTR_KEYHOLENUMBER = 24, + OTBM_ATTR_DOORQUESTNUMBER = 25, + OTBM_ATTR_DOORQUESTVALUE = 26, + OTBM_ATTR_DOORLEVEL = 27, + OTBM_ATTR_CHESTQUESTNUMBER = 28, +}; + +enum OTBM_NodeTypes_t { + OTBM_ROOTV1 = 1, + OTBM_MAP_DATA = 2, + OTBM_ITEM_DEF = 3, + OTBM_TILE_AREA = 4, + OTBM_TILE = 5, + OTBM_ITEM = 6, + OTBM_TILE_SQUARE = 7, + OTBM_TILE_REF = 8, + OTBM_SPAWNS = 9, + OTBM_SPAWN_AREA = 10, + OTBM_MONSTER = 11, + OTBM_TOWNS = 12, + OTBM_TOWN = 13, + OTBM_HOUSETILE = 14, + OTBM_WAYPOINTS = 15, + OTBM_WAYPOINT = 16, +}; + +enum OTBM_TileFlag_t : uint32_t { + OTBM_TILEFLAG_PROTECTIONZONE = 1 << 0, + OTBM_TILEFLAG_NOPVPZONE = 1 << 2, + OTBM_TILEFLAG_NOLOGOUT = 1 << 3, + OTBM_TILEFLAG_PVPZONE = 1 << 4, + OTBM_TILEFLAG_REFRESH = 1 << 5, +}; + +#pragma pack(1) + +struct OTBM_root_header { + uint32_t version; + uint16_t width; + uint16_t height; + uint32_t majorVersionItems; + uint32_t minorVersionItems; +}; + +struct OTBM_Destination_coords { + uint16_t x; + uint16_t y; + uint8_t z; +}; + +struct OTBM_Tile_coords { + uint8_t x; + uint8_t y; +}; + +#pragma pack() + +class IOMap +{ + static Tile* createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z); + + public: + bool loadMap(Map* map, const std::string& identifier); + + /* Load the spawns + * \param map pointer to the Map class + * \returns Returns true if the spawns were loaded successfully + */ + static bool loadSpawns(Map* map) { + if (map->spawnfile.empty()) { + //OTBM file doesn't tell us about the spawnfile, + //lets guess it is mapname-spawn.xml. + map->spawnfile = g_config.getString(ConfigManager::MAP_NAME); + map->spawnfile += "-spawn.xml"; + } + + return map->spawns.loadFromXml(map->spawnfile); + } + + /* Load the houses (not house tile-data) + * \param map pointer to the Map class + * \returns Returns true if the houses were loaded successfully + */ + static bool loadHouses(Map* map) { + if (map->housefile.empty()) { + //OTBM file doesn't tell us about the housefile, + //lets guess it is mapname-house.xml. + map->housefile = g_config.getString(ConfigManager::MAP_NAME); + map->housefile += "-house.xml"; + } + + return map->houses.loadHousesXML(map->housefile); + } + + const std::string& getLastErrorString() const { + return errorString; + } + + void setLastErrorString(std::string error) { + errorString = error; + } + + protected: + std::string errorString; +}; + +#endif diff --git a/app/SabrehavenServer/src/iomapserialize.cpp b/app/SabrehavenServer/src/iomapserialize.cpp new file mode 100644 index 0000000..ef6183f --- /dev/null +++ b/app/SabrehavenServer/src/iomapserialize.cpp @@ -0,0 +1,396 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomapserialize.h" +#include "game.h" +#include "bed.h" + +extern Game g_game; + +void IOMapSerialize::loadHouseItems(Map* map) +{ + int64_t start = OTSYS_TIME(); + + DBResult_ptr result = Database::getInstance()->storeQuery("SELECT `data` FROM `tile_store`"); + if (!result) { + return; + } + + do { + unsigned long attrSize; + const char* attr = result->getStream("data", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + uint16_t x, y; + uint8_t z; + if (!propStream.read(x) || !propStream.read(y) || !propStream.read(z)) { + continue; + } + + Tile* tile = map->getTile(x, y, z); + if (!tile) { + continue; + } + + uint32_t item_count; + if (!propStream.read(item_count)) { + continue; + } + + while (item_count--) { + loadItem(propStream, tile); + } + } while (result->next()); + std::cout << "> Loaded house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; +} + +bool IOMapSerialize::saveHouseItems() +{ + int64_t start = OTSYS_TIME(); + Database* db = Database::getInstance(); + std::ostringstream query; + + //Start the transaction + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + //clear old tile data + if (!db->executeQuery("DELETE FROM `tile_store`")) { + return false; + } + + DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + + PropWriteStream stream; + for (const auto& it : g_game.map.houses.getHouses()) { + //save house items + House* house = it.second; + for (HouseTile* tile : house->getTiles()) { + saveTile(stream, tile); + + size_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + if (attributesSize > 0) { + query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize); + if (!stmt.addRow(query)) { + return false; + } + stream.clear(); + } + + static const Direction directions[] = { DIRECTION_EAST, DIRECTION_WEST, DIRECTION_NORTH, DIRECTION_SOUTH }; + for (Direction direction : directions) { + Position position = getNextPosition(direction, tile->getPosition()); + Tile* nextTile = g_game.map.getTile(position); + if (nextTile && nextTile->hasFlag(TILESTATE_BLOCKSOLID) && !nextTile->hasFlag(TILESTATE_PROTECTIONZONE)) { + saveTile(stream, nextTile); + + size_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + if (attributesSize > 0) { + query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize); + if (!stmt.addRow(query)) { + return false; + } + stream.clear(); + } + + nextTile->setFlag(TILESTATE_PROTECTIONZONE); + } + } + } + } + + if (!stmt.execute()) { + return false; + } + + //End the transaction + bool success = transaction.commit(); + std::cout << "> Saved house items in: " << + (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + return success; +} + +bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) +{ + while (container->serializationCount > 0) { + if (!loadItem(propStream, container)) { + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + return false; + } + container->serializationCount--; + } + + uint8_t endAttr; + if (!propStream.read(endAttr) || endAttr != 0) { + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + return false; + } + return true; +} + +bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) +{ + uint16_t id; + if (!propStream.read(id)) { + return false; + } + + Tile* tile = nullptr; + if (parent->getParent() == nullptr) { + tile = parent->getTile(); + } + + const ItemType& iType = Item::items[id]; + if (iType.moveable || !tile) { + //create a new item + Item* item = Item::CreateItem(id); + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + if (container && !loadContainer(propStream, container)) { + delete item; + return false; + } + + parent->internalAddThing(item); + item->startDecaying(); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + delete item; + return false; + } + } + } else { + // Stationary items like doors/beds/blackboards/bookcases + Item* item = nullptr; + if (const TileItemVector* items = tile->getItemList()) { + for (Item* findItem : *items) { + if (findItem->getID() == id) { + item = findItem; + break; + } else if (iType.isDoor() && findItem->getDoor()) { + item = findItem; + break; + } else if (iType.isBed() && findItem->getBed()) { + item = findItem; + break; + } else if (!iType.moveable && iType.changeUse && findItem->getID() == iType.transformToOnUse) { + item = findItem; + break; + } + } + } + + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + if (container && !loadContainer(propStream, container)) { + return false; + } + + g_game.transformItem(item, id); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + } + } else { + //The map changed since the last save, just read the attributes + std::unique_ptr dummy(Item::CreateItem(id)); + if (dummy) { + dummy->unserializeAttr(propStream); + Container* container = dummy->getContainer(); + if (container) { + if (!loadContainer(propStream, container)) { + return false; + } + } else if (BedItem* bedItem = dynamic_cast(dummy.get())) { + uint32_t sleeperGUID = bedItem->getSleeper(); + if (sleeperGUID != 0) { + g_game.removeBedSleeper(sleeperGUID); + } + } + } + } + } + return true; +} + +void IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item) +{ + const Container* container = item->getContainer(); + + // Write ID & props + stream.write(item->getID()); + item->serializeAttr(stream); + + if (container) { + // Hack our way into the attributes + stream.write(ATTR_CONTAINER_ITEMS); + stream.write(container->size()); + for (auto it = container->getReversedItems(), end = container->getReversedEnd(); it != end; ++it) { + saveItem(stream, *it); + } + } + + stream.write(0x00); // attr end +} + +void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) +{ + const TileItemVector* tileItems = tile->getItemList(); + if (!tileItems) { + return; + } + + std::forward_list items; + uint16_t count = 0; + for (Item* item : *tileItems) { + const ItemType& it = Item::items[item->getID()]; + + // Note that these are NEGATED, ie. these are the items that will be saved. + if (!(it.moveable || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || it.isHangable || item->getBed())) { + continue; + } + + items.push_front(item); + ++count; + } + + if (!items.empty()) { + const Position& tilePosition = tile->getPosition(); + stream.write(tilePosition.x); + stream.write(tilePosition.y); + stream.write(tilePosition.z); + + stream.write(count); + for (const Item* item : items) { + saveItem(stream, item); + } + } +} + +bool IOMapSerialize::loadHouseInfo() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + if (!result) { + return false; + } + + do { + House* house = g_game.map.houses.getHouse(result->getNumber("id")); + if (house) { + house->setOwner(result->getNumber("owner"), false); + house->setPaidUntil(result->getNumber("paid")); + house->setPayRentWarnings(result->getNumber("warnings")); + } + } while (result->next()); + + result = db->storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); + if (result) { + do { + House* house = g_game.map.houses.getHouse(result->getNumber("house_id")); + if (house) { + house->setAccessList(result->getNumber("listid"), result->getString("list")); + } + } while (result->next()); + } + return true; +} + +bool IOMapSerialize::saveHouseInfo() +{ + Database* db = Database::getInstance(); + + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery("DELETE FROM `house_lists`")) { + return false; + } + + std::ostringstream query; + for (const auto& it : g_game.map.houses.getHouses()) { + House* house = it.second; + query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getId(); + DBResult_ptr result = db->storeQuery(query.str()); + if (result) { + query.str(std::string()); + query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db->escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId(); + } else { + query.str(std::string()); + query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db->escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')'; + } + + db->executeQuery(query.str()); + query.str(std::string()); + } + + DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES "); + + for (const auto& it : g_game.map.houses.getHouses()) { + House* house = it.second; + + std::string listText; + if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { + query << house->getId() << ',' << GUEST_LIST << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + + if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { + query << house->getId() << ',' << SUBOWNER_LIST << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + + for (Door* door : house->getDoors()) { + if (door->getAccessList(listText) && !listText.empty()) { + query << house->getId() << ',' << door->getDoorId() << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + } + } + + if (!stmt.execute()) { + return false; + } + + return transaction.commit(); +} diff --git a/app/SabrehavenServer/src/iomapserialize.h b/app/SabrehavenServer/src/iomapserialize.h new file mode 100644 index 0000000..1a87c28 --- /dev/null +++ b/app/SabrehavenServer/src/iomapserialize.h @@ -0,0 +1,42 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D +#define FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D + +#include "database.h" +#include "map.h" + +class IOMapSerialize +{ + public: + static void loadHouseItems(Map* map); + static bool saveHouseItems(); + static bool loadHouseInfo(); + static bool saveHouseInfo(); + + protected: + static void saveItem(PropWriteStream& stream, const Item* item); + static void saveTile(PropWriteStream& stream, const Tile* tile); + + static bool loadContainer(PropStream& propStream, Container* container); + static bool loadItem(PropStream& propStream, Cylinder* parent); +}; + +#endif diff --git a/app/SabrehavenServer/src/item.cpp b/app/SabrehavenServer/src/item.cpp new file mode 100644 index 0000000..929f22c --- /dev/null +++ b/app/SabrehavenServer/src/item.cpp @@ -0,0 +1,1304 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "item.h" +#include "container.h" +#include "teleport.h" +#include "mailbox.h" +#include "house.h" +#include "game.h" +#include "bed.h" + +#include "actions.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Vocations g_vocations; + +Items Item::items; + +Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) +{ + Item* newItem = nullptr; + + const ItemType& it = Item::items[type]; + if (it.group == ITEM_GROUP_DEPRECATED) { + return nullptr; + } + + if (it.stackable && count == 0) { + count = 1; + } + + if (it.id != 0) { + if (it.isDepot()) { + newItem = new DepotLocker(type); + } + else if (it.isContainer() || it.isChest()) { + newItem = new Container(type); + } + else if (it.isTeleport()) { + newItem = new Teleport(type); + } + else if (it.isMagicField()) { + newItem = new MagicField(type); + } + else if (it.isDoor()) { + newItem = new Door(type); + } + else if (it.isMailbox()) { + newItem = new Mailbox(type); + } + else if (it.isBed()) { + newItem = new BedItem(type); + } + else { + newItem = new Item(type, count); + } + + newItem->incrementReferenceCounter(); + } + + return newItem; +} + +Container* Item::CreateItemAsContainer(const uint16_t type, uint16_t size) +{ + const ItemType& it = Item::items[type]; + if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || it.isDepot() || it.isSplash() || it.isDoor()) { + return nullptr; + } + + Container* newItem = new Container(type, size); + newItem->incrementReferenceCounter(); + return newItem; +} + +Item* Item::CreateItem(PropStream& propStream) +{ + uint16_t id; + if (!propStream.read(id)) { + return nullptr; + } + + switch (id) { + case ITEM_FIREFIELD_PVP_FULL: + id = ITEM_FIREFIELD_PERSISTENT_FULL; + break; + + case ITEM_FIREFIELD_PVP_MEDIUM: + id = ITEM_FIREFIELD_PERSISTENT_MEDIUM; + break; + + case ITEM_FIREFIELD_PVP_SMALL: + id = ITEM_FIREFIELD_PERSISTENT_SMALL; + break; + + case ITEM_ENERGYFIELD_PVP: + id = ITEM_ENERGYFIELD_PERSISTENT; + break; + + case ITEM_POISONFIELD_PVP: + id = ITEM_POISONFIELD_PERSISTENT; + break; + + case ITEM_MAGICWALL: + id = ITEM_MAGICWALL_PERSISTENT; + break; + + case ITEM_WILDGROWTH: + id = ITEM_WILDGROWTH_PERSISTENT; + break; + + default: + break; + } + + return Item::CreateItem(id, 0); +} + +Item::Item(const uint16_t type, uint16_t count /*= 0*/) : + id(type) +{ + const ItemType& it = items[id]; + + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(count); + } + else if (it.stackable) { + if (count != 0) { + setItemCount(count); + } + else if (it.charges != 0) { + setItemCount(it.charges); + } + } + else if (it.charges != 0) { + if (count != 0) { + setCharges(count); + } + else { + setCharges(it.charges); + } + } + else if (it.isKey()) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, count); + } + + setDefaultDuration(); +} + +Item::Item(const Item& i) : + Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap) +{ + if (i.attributes) { + attributes.reset(new ItemAttributes(*i.attributes)); + } +} + +Item* Item::clone() const +{ + Item* item = Item::CreateItem(id, count); + if (attributes) { + item->attributes.reset(new ItemAttributes(*attributes)); + } + return item; +} + +bool Item::equals(const Item* otherItem) const +{ + if (!otherItem || id != otherItem->id) { + return false; + } + + if (!attributes) { + return !otherItem->attributes; + } + + const auto& otherAttributes = otherItem->attributes; + if (!otherAttributes || attributes->attributeBits != otherAttributes->attributeBits) { + return false; + } + + const auto& attributeList = attributes->attributes; + const auto& otherAttributeList = otherAttributes->attributes; + for (const auto& attribute : attributeList) { + if (ItemAttributes::isStrAttrType(attribute.type)) { + for (const auto& otherAttribute : otherAttributeList) { + if (attribute.type == otherAttribute.type && *attribute.value.string != *otherAttribute.value.string) { + return false; + } + } + } + else { + for (const auto& otherAttribute : otherAttributeList) { + if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) { + return false; + } + } + } + } + return true; +} + +void Item::setDefaultSubtype() +{ + const ItemType& it = items[id]; + + setItemCount(1); + + if (it.charges != 0) { + if (it.stackable) { + setItemCount(it.charges); + } + else { + setCharges(it.charges); + } + } +} + +void Item::onRemoved() +{ + ScriptEnvironment::removeTempItem(this); +} + +void Item::setID(uint16_t newid) +{ + const ItemType& prevIt = Item::items[id]; + id = newid; + + const ItemType& it = Item::items[newid]; + uint32_t newDuration = it.decayTime * 1000; + + if (newDuration == 0 && !it.stopTime && it.decayTo < 0) { + removeAttribute(ITEM_ATTRIBUTE_DECAYSTATE); + removeAttribute(ITEM_ATTRIBUTE_DURATION); + } + + removeAttribute(ITEM_ATTRIBUTE_CORPSEOWNER); + + if (newDuration > 0 && (!prevIt.stopTime || !hasAttribute(ITEM_ATTRIBUTE_DURATION))) { + setDecaying(DECAYING_FALSE); + setDuration(newDuration); + } +} + +Cylinder* Item::getTopParent() +{ + Cylinder* aux = getParent(); + Cylinder* prevaux = dynamic_cast(this); + if (!aux) { + return prevaux; + } + + while (aux->getParent() != nullptr) { + prevaux = aux; + aux = aux->getParent(); + } + + if (prevaux) { + return prevaux; + } + return aux; +} + +const Cylinder* Item::getTopParent() const +{ + const Cylinder* aux = getParent(); + const Cylinder* prevaux = dynamic_cast(this); + if (!aux) { + return prevaux; + } + + while (aux->getParent() != nullptr) { + prevaux = aux; + aux = aux->getParent(); + } + + if (prevaux) { + return prevaux; + } + return aux; +} + +Tile* Item::getTile() +{ + Cylinder* cylinder = getTopParent(); + //get root cylinder + if (cylinder && cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + return dynamic_cast(cylinder); +} + +const Tile* Item::getTile() const +{ + const Cylinder* cylinder = getTopParent(); + //get root cylinder + if (cylinder && cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + return dynamic_cast(cylinder); +} + +uint16_t Item::getSubType() const +{ + const ItemType& it = items[id]; + if (it.isFluidContainer() || it.isSplash()) { + return getFluidType(); + } + else if (it.stackable) { + return count; + } + else if (it.charges != 0) { + return getCharges(); + } + return count; +} + +Player* Item::getHoldingPlayer() const +{ + Cylinder* p = getParent(); + while (p) { + if (p->getCreature()) { + return p->getCreature()->getPlayer(); + } + + p = p->getParent(); + } + return nullptr; +} + +void Item::setSubType(uint16_t n) +{ + const ItemType& it = items[id]; + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(n); + } + else if (it.stackable) { + setItemCount(n); + } + else if (it.charges != 0) { + setCharges(n); + } + else { + setItemCount(n); + } +} + +Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_COUNT: + case ATTR_RUNE_CHARGES: { + uint8_t count; + if (!propStream.read(count)) { + return ATTR_READ_ERROR; + } + + setSubType(count); + break; + } + + case ATTR_ACTION_ID: { + uint16_t actionId; + if (!propStream.read(actionId)) { + return ATTR_READ_ERROR; + } + + setActionId(actionId); + break; + } + + case ATTR_MOVEMENT_ID: { + uint16_t movementId; + if (!propStream.read(movementId)) { + return ATTR_READ_ERROR; + } + + setMovementID(movementId); + break; + } + + case ATTR_TEXT: { + std::string text; + if (!propStream.readString(text)) { + return ATTR_READ_ERROR; + } + + setText(text); + break; + } + + case ATTR_WRITTENDATE: { + uint32_t writtenDate; + if (!propStream.read(writtenDate)) { + return ATTR_READ_ERROR; + } + + setDate(writtenDate); + break; + } + + case ATTR_WRITTENBY: { + std::string writer; + if (!propStream.readString(writer)) { + return ATTR_READ_ERROR; + } + + setWriter(writer); + break; + } + + case ATTR_DESC: { + std::string text; + if (!propStream.readString(text)) { + return ATTR_READ_ERROR; + } + + setSpecialDescription(text); + break; + } + + case ATTR_CHARGES: { + uint16_t charges; + if (!propStream.read(charges)) { + return ATTR_READ_ERROR; + } + + setSubType(charges); + break; + } + + case ATTR_DURATION: { + int32_t duration; + if (!propStream.read(duration)) { + return ATTR_READ_ERROR; + } + + setDuration(std::max(0, duration)); + break; + } + + case ATTR_DECAYING_STATE: { + uint8_t state; + if (!propStream.read(state)) { + return ATTR_READ_ERROR; + } + + if (state != DECAYING_FALSE) { + setDecaying(DECAYING_PENDING); + } + break; + } + + case ATTR_NAME: { + std::string name; + if (!propStream.readString(name)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_NAME, name); + break; + } + + case ATTR_ARTICLE: { + std::string article; + if (!propStream.readString(article)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_ARTICLE, article); + break; + } + + case ATTR_PLURALNAME: { + std::string pluralName; + if (!propStream.readString(pluralName)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_PLURALNAME, pluralName); + break; + } + + case ATTR_WEIGHT: { + uint32_t weight; + if (!propStream.read(weight)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_WEIGHT, weight); + break; + } + + case ATTR_ATTACK: { + int32_t attack; + if (!propStream.read(attack)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_ATTACK, attack); + break; + } + + case ATTR_DEFENSE: { + int32_t defense; + if (!propStream.read(defense)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DEFENSE, defense); + break; + } + + case ATTR_ARMOR: { + int32_t armor; + if (!propStream.read(armor)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_ARMOR, armor); + break; + } + + case ATTR_SHOOTRANGE: { + uint8_t shootRange; + if (!propStream.read(shootRange)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE, shootRange); + break; + } + + case ATTR_KEYNUMBER: { + uint16_t keyNumber; + if (!propStream.read(keyNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber); + break; + } + + case ATTR_KEYHOLENUMBER: + { + uint16_t keyHoleNumber; + if (!propStream.read(keyHoleNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber); + break; + } + + case ATTR_DOORLEVEL: + { + uint16_t doorLevel; + if (!propStream.read(doorLevel)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel); + break; + } + + case ATTR_DOORQUESTNUMBER: + { + uint16_t doorQuestNumber; + if (!propStream.read(doorQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber); + break; + } + + case ATTR_DOORQUESTVALUE: + { + uint16_t doorQuestValue; + if (!propStream.read(doorQuestValue)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue); + break; + } + + case ATTR_CHESTQUESTNUMBER: + { + uint16_t chestQuestNumber; + if (!propStream.read(chestQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, chestQuestNumber); + break; + } + + //these should be handled through derived classes + //If these are called then something has changed in the items.xml since the map was saved + //just read the values + + //Depot class + case ATTR_DEPOT_ID: { + if (!propStream.skip(2)) { + return ATTR_READ_ERROR; + } + break; + } + + //Door class + case ATTR_HOUSEDOORID: { + if (!propStream.skip(1)) { + return ATTR_READ_ERROR; + } + break; + } + + //Bed class + case ATTR_SLEEPERGUID: { + if (!propStream.skip(4)) { + return ATTR_READ_ERROR; + } + break; + } + + case ATTR_SLEEPSTART: { + if (!propStream.skip(4)) { + return ATTR_READ_ERROR; + } + break; + } + + //Teleport class + case ATTR_TELE_DEST: { + if (!propStream.skip(5)) { + return ATTR_READ_ERROR; + } + break; + } + + //Container class + case ATTR_CONTAINER_ITEMS: { + return ATTR_READ_ERROR; + } + + default: + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; +} + +bool Item::unserializeAttr(PropStream& propStream) +{ + uint8_t attr_type; + while (propStream.read(attr_type) && attr_type != 0) { + Attr_ReadValue ret = readAttr(static_cast(attr_type), propStream); + if (ret == ATTR_READ_ERROR) { + return false; + } + else if (ret == ATTR_READ_END) { + return true; + } + } + return true; +} + +bool Item::unserializeItemNode(FileLoader&, NODE, PropStream& propStream) +{ + return unserializeAttr(propStream); +} + +void Item::serializeAttr(PropWriteStream& propWriteStream) const +{ + const ItemType& it = items[id]; + if (it.stackable || it.isFluidContainer() || it.isSplash()) { + propWriteStream.write(ATTR_COUNT); + propWriteStream.write(getSubType()); + } + + uint16_t charges = getCharges(); + if (charges != 0) { + propWriteStream.write(ATTR_CHARGES); + propWriteStream.write(charges); + } + + if (it.moveable) { + uint16_t actionId = getActionId(); + if (actionId != 0) { + propWriteStream.write(ATTR_ACTION_ID); + propWriteStream.write(actionId); + } + } + + const std::string& text = getText(); + if (!text.empty()) { + propWriteStream.write(ATTR_TEXT); + propWriteStream.writeString(text); + } + + const time_t writtenDate = getDate(); + if (writtenDate != 0) { + propWriteStream.write(ATTR_WRITTENDATE); + propWriteStream.write(writtenDate); + } + + const std::string& writer = getWriter(); + if (!writer.empty()) { + propWriteStream.write(ATTR_WRITTENBY); + propWriteStream.writeString(writer); + } + + const std::string& specialDesc = getSpecialDescription(); + if (!specialDesc.empty()) { + propWriteStream.write(ATTR_DESC); + propWriteStream.writeString(specialDesc); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) { + propWriteStream.write(ATTR_DURATION); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DURATION)); + } + + ItemDecayState_t decayState = getDecaying(); + if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) { + propWriteStream.write(ATTR_DECAYING_STATE); + propWriteStream.write(decayState); + } + + if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { + propWriteStream.write(ATTR_NAME); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_NAME)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { + propWriteStream.write(ATTR_ARTICLE); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_ARTICLE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { + propWriteStream.write(ATTR_PLURALNAME); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_PLURALNAME)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { + propWriteStream.write(ATTR_WEIGHT); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_WEIGHT)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { + propWriteStream.write(ATTR_ATTACK); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { + propWriteStream.write(ATTR_DEFENSE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { + propWriteStream.write(ATTR_ARMOR); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ARMOR)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { + propWriteStream.write(ATTR_SHOOTRANGE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) { + propWriteStream.write(ATTR_KEYNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) { + propWriteStream.write(ATTR_KEYHOLENUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { + propWriteStream.write(ATTR_DOORLEVEL); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) { + propWriteStream.write(ATTR_DOORQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) { + propWriteStream.write(ATTR_DOORQUESTVALUE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) { + propWriteStream.write(ATTR_CHESTQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + } +} + +bool Item::hasProperty(ITEMPROPERTY prop) const +{ + const ItemType& it = items[id]; + switch (prop) { + case CONST_PROP_BLOCKSOLID: return it.blockSolid; + case CONST_PROP_MOVEABLE: return it.moveable; + case CONST_PROP_HASHEIGHT: return it.hasHeight; + case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; + case CONST_PROP_BLOCKPATH: return it.blockPathFind; + case CONST_PROP_ISVERTICAL: return it.isVertical; + case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; + case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && !it.moveable; + case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && !it.moveable; + case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && !it.moveable; + case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; + case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; + case CONST_PROP_UNLAY: return !it.allowPickupable; + default: return false; + } +} + +uint32_t Item::getWeight() const +{ + uint32_t weight = getBaseWeight(); + if (isStackable()) { + return weight * std::max(1, getItemCount()); + } + return weight; +} + +std::string Item::getDescription(const ItemType& it, int32_t lookDistance, + const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + std::ostringstream s; + s << getNameDescription(it, item, subType, addArticle); + + if (item) { + subType = item->getSubType(); + } + + if (it.isRune()) { + uint32_t charges = std::max(static_cast(1), static_cast(item == nullptr ? it.charges : item->getCharges())); + + if (it.runeLevel > 0) { + s << " for level " << it.runeLevel; + } + + if (it.runeLevel > 0) { + s << " and"; + } + + s << " for magic level " << it.runeMagLevel; + s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; + } + else if (it.isDoor() && item) { + if (item->hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { + s << " for level " << item->getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL); + } + s << "."; + } + else if (it.weaponType != WEAPON_NONE) { + if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { + if (item->getAttack() != 0) { + s << ", Atk" << std::showpos << item->getAttack() << std::noshowpos; + } + } + else if (it.weaponType != WEAPON_WAND && (item->getAttack() != 0 || item->getDefense() != 0)) { + s << " ("; + if (item->getAttack() != 0) { + s << "Atk:" << static_cast(item->getAttack()); + } + + if (item->getDefense() != 0) { + if (item->getAttack() != 0) + s << " "; + + s << "Def:" << static_cast(item->getDefense()); + } + + s << ")"; + } + s << "."; + } + else if (item->getArmor() != 0 || (it.abilities && it.abilities->speed != 0)) { + if (it.charges > 0) { + if (subType > 1) { + s << " that has " << static_cast(subType) << " charges left"; + } + else { + s << " that has " << it.charges << " charge left"; + } + } + + s << " ("; + if (item->getArmor() > 0) { + s << "Arm:" << item->getArmor(); + } + if (it.abilities && it.abilities->speed > 0) { + if (item->getArmor() > 0) { + s << ", "; + } + s << "Speed +" << it.abilities->speed; + } + s << ")."; + } + else if (it.isFluidContainer()) { + if (item && item->getFluidType() != 0) { + s << " of " << items[item->getFluidType()].name << "."; + } + else { + s << ". It is empty."; + } + } + else if (it.isSplash()) { + s << " of "; + if (item && item->getFluidType() != 0) { + s << items[item->getFluidType()].name; + } + else { + s << items[1].name; + } + s << "."; + } + else if (it.isContainer() && !it.isChest()) { + s << " (Vol:" << static_cast(it.maxItems) << ")."; + } + else if (it.isKey()) { + if (item) { + s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; + } + else { + s << " (Key:0)."; + } + } + else if (it.allowDistRead) { + s << "."; + s << std::endl; + + if (item && item->getText() != "") { + if (lookDistance <= 4) { + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + s << writer << " wrote"; + time_t date = item->getDate(); + if (date != 0) { + s << " on " << formatDateShort(date); + } + s << ": "; + } + else { + s << "You read: "; + } + s << item->getText(); + } + else { + s << "You are too far away to read it."; + } + } + else { + s << "Nothing is written on it."; + } + } + else if (it.charges > 0) { + uint32_t charges = (item == nullptr ? it.charges : item->getCharges()); + if (charges > 1) { + s << " that has " << static_cast(charges) << " charges left."; + } + else { + s << " that has 1 charge left."; + } + } + else if (it.showDuration) { + if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { + int32_t duration = item->getDuration() / 1000; + s << " that has energy for "; + + if (duration >= 120) { + s << duration / 60 << " minutes left."; + } + else if (duration > 60) { + s << "1 minute left."; + } + else { + s << "less than a minute left."; + } + } + else { + s << " that is brand-new."; + } + } + else { + s << "."; + } + + if (it.wieldInfo != 0) { + s << std::endl << "It can only be wielded properly by "; + + if (it.wieldInfo & WIELDINFO_PREMIUM) { + s << "premium "; + } + + if (it.wieldInfo & WIELDINFO_VOCREQ) { + s << it.vocationString; + } + else { + s << "players"; + } + + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " of level " << static_cast(it.minReqLevel) << " or higher"; + } + + if (it.wieldInfo & WIELDINFO_MAGLV) { + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " and"; + } + else { + s << " of"; + } + + s << " magic level " << static_cast(it.minReqMagicLevel) << " or higher"; + } + + s << "."; + } + + if (lookDistance <= 1 && !it.isChest() && it.pickupable) { + double weight = (item == nullptr ? it.weight : item->getWeight()); + if (weight > 0) { + s << std::endl << getWeightDescription(it, weight); + } + } + + if (item && item->getSpecialDescription() != "") { + s << std::endl << item->getSpecialDescription().c_str(); + } + else if (it.description.length() && lookDistance <= 1) { + s << std::endl << it.description << "."; + } + + return s.str(); +} + +std::string Item::getDescription(int32_t lookDistance) const +{ + const ItemType& it = items[id]; + return getDescription(it, lookDistance, this); +} + +std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + if (item) { + subType = item->getSubType(); + } + + std::ostringstream s; + + const std::string& name = (item ? item->getName() : it.name); + if (!name.empty()) { + if (it.stackable && subType > 1) { + if (it.showCount) { + s << subType << ' '; + } + + s << (item ? item->getPluralName() : it.getPluralName()); + } + else { + if (addArticle) { + const std::string& article = (item ? item->getArticle() : it.article); + if (!article.empty()) { + s << article << ' '; + } + } + + s << name; + } + } + else { + s << "an item of type " << it.id; + } + return s.str(); +} + +std::string Item::getNameDescription() const +{ + const ItemType& it = items[id]; + return getNameDescription(it, this); +} + +std::string Item::getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count /*= 1*/) +{ + std::ostringstream ss; + if (it.stackable && count > 1 && it.showCount != 0) { + ss << "They weigh "; + } + else { + ss << "It weighs "; + } + + if (weight < 10) { + ss << "0.0" << weight; + } + else if (weight < 100) { + ss << "0." << weight; + } + else { + std::string weightString = std::to_string(weight); + weightString.insert(weightString.end() - 2, '.'); + ss << weightString; + } + + ss << " oz."; + return ss.str(); +} + +std::string Item::getWeightDescription(uint32_t weight) const +{ + const ItemType& it = Item::items[id]; + return getWeightDescription(it, weight, getItemCount()); +} + +std::string Item::getWeightDescription() const +{ + uint32_t weight = getWeight(); + if (weight == 0) { + return std::string(); + } + return getWeightDescription(weight); +} + +bool Item::canDecay() const +{ + if (isRemoved()) { + return false; + } + + const ItemType& it = Item::items[id]; + if (it.decayTo < 0 || it.decayTime == 0) { + return false; + } + + return true; +} + +uint32_t Item::getWorth() const +{ + switch (id) { + case ITEM_GOLD_COIN: + return count; + + case ITEM_PLATINUM_COIN: + return count * 100; + + case ITEM_CRYSTAL_COIN: + return count * 10000; + + default: + return 0; + } +} + +void Item::getLight(LightInfo& lightInfo) const +{ + const ItemType& it = items[id]; + lightInfo.color = it.lightColor; + lightInfo.level = it.lightLevel; +} + +std::string ItemAttributes::emptyString; + +const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const +{ + if (!isStrAttrType(type)) { + return emptyString; + } + + const Attribute* attr = getExistingAttr(type); + if (!attr) { + return emptyString; + } + return *attr->value.string; +} + +void ItemAttributes::setStrAttr(itemAttrTypes type, const std::string& value) +{ + if (!isStrAttrType(type)) { + return; + } + + if (value.empty()) { + return; + } + + Attribute& attr = getAttr(type); + delete attr.value.string; + attr.value.string = new std::string(value); +} + +void ItemAttributes::removeAttribute(itemAttrTypes type) +{ + if (!hasAttribute(type)) { + return; + } + + auto prev_it = attributes.cbegin(); + if ((*prev_it).type == type) { + attributes.pop_front(); + } + else { + auto it = prev_it, end = attributes.cend(); + while (++it != end) { + if ((*it).type == type) { + attributes.erase_after(prev_it); + break; + } + prev_it = it; + } + } + attributeBits &= ~type; +} + +int64_t ItemAttributes::getIntAttr(itemAttrTypes type) const +{ + if (!isIntAttrType(type)) { + return 0; + } + + const Attribute* attr = getExistingAttr(type); + if (!attr) { + return 0; + } + return attr->value.integer; +} + +void ItemAttributes::setIntAttr(itemAttrTypes type, int64_t value) +{ + if (!isIntAttrType(type)) { + return; + } + + getAttr(type).value.integer = value; +} + +void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value) +{ + if (!isIntAttrType(type)) { + return; + } + + getAttr(type).value.integer += value; +} + +const ItemAttributes::Attribute* ItemAttributes::getExistingAttr(itemAttrTypes type) const +{ + if (hasAttribute(type)) { + for (const Attribute& attribute : attributes) { + if (attribute.type == type) { + return &attribute; + } + } + } + return nullptr; +} + +ItemAttributes::Attribute& ItemAttributes::getAttr(itemAttrTypes type) +{ + if (hasAttribute(type)) { + for (Attribute& attribute : attributes) { + if (attribute.type == type) { + return attribute; + } + } + } + + attributeBits |= type; + attributes.emplace_front(type); + return attributes.front(); +} + +void Item::startDecaying() +{ + g_game.startDecay(this); +} diff --git a/app/SabrehavenServer/src/item.h b/app/SabrehavenServer/src/item.h new file mode 100644 index 0000000..87b2a04 --- /dev/null +++ b/app/SabrehavenServer/src/item.h @@ -0,0 +1,844 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 +#define FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 + +#include "cylinder.h" +#include "thing.h" +#include "items.h" + +#include + +class Creature; +class Player; +class Container; +class Depot; +class Teleport; +class Mailbox; +class DepotLocker; +class Door; +class MagicField; +class BedItem; + +enum ITEMPROPERTY { + CONST_PROP_BLOCKSOLID = 0, + CONST_PROP_HASHEIGHT, + CONST_PROP_BLOCKPROJECTILE, + CONST_PROP_BLOCKPATH, + CONST_PROP_ISVERTICAL, + CONST_PROP_ISHORIZONTAL, + CONST_PROP_MOVEABLE, + CONST_PROP_IMMOVABLEBLOCKSOLID, + CONST_PROP_IMMOVABLEBLOCKPATH, + CONST_PROP_IMMOVABLENOFIELDBLOCKPATH, + CONST_PROP_NOFIELDBLOCKPATH, + CONST_PROP_SUPPORTHANGABLE, + CONST_PROP_UNLAY, +}; + +enum TradeEvents_t { + ON_TRADE_TRANSFER, + ON_TRADE_CANCEL, +}; + +enum ItemDecayState_t : uint8_t { + DECAYING_FALSE = 0, + DECAYING_TRUE, + DECAYING_PENDING, +}; + +enum AttrTypes_t { + //ATTR_DESCRIPTION = 1, + //ATTR_EXT_FILE = 2, + ATTR_TILE_FLAGS = 3, + ATTR_ACTION_ID = 4, + ATTR_MOVEMENT_ID = 5, + ATTR_TEXT = 6, + ATTR_DESC = 7, + ATTR_TELE_DEST = 8, + ATTR_ITEM = 9, + ATTR_DEPOT_ID = 10, + //ATTR_EXT_SPAWN_FILE = 11, + ATTR_RUNE_CHARGES = 12, + //ATTR_EXT_HOUSE_FILE = 13, + ATTR_HOUSEDOORID = 14, + ATTR_COUNT = 15, + ATTR_DURATION = 16, + ATTR_DECAYING_STATE = 17, + ATTR_WRITTENDATE = 18, + ATTR_WRITTENBY = 19, + ATTR_SLEEPERGUID = 20, + ATTR_SLEEPSTART = 21, + ATTR_CHARGES = 22, + ATTR_KEYNUMBER = 23, + ATTR_KEYHOLENUMBER = 24, + ATTR_DOORQUESTNUMBER = 25, + ATTR_DOORQUESTVALUE = 26, + ATTR_DOORLEVEL = 27, + ATTR_CHESTQUESTNUMBER = 28, + // add non-OTBM attributes after here + ATTR_CONTAINER_ITEMS = 29, + ATTR_NAME = 30, + ATTR_ARTICLE = 31, + ATTR_PLURALNAME = 32, + ATTR_WEIGHT = 33, + ATTR_ATTACK = 34, + ATTR_DEFENSE = 35, + ATTR_ARMOR = 36, + ATTR_SHOOTRANGE = 37, +}; + +enum Attr_ReadValue { + ATTR_READ_CONTINUE, + ATTR_READ_ERROR, + ATTR_READ_END, +}; + +class ItemAttributes +{ + public: + ItemAttributes() = default; + + void setSpecialDescription(const std::string& desc) { + setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); + } + const std::string& getSpecialDescription() const { + return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); + } + + void setText(const std::string& text) { + setStrAttr(ITEM_ATTRIBUTE_TEXT, text); + } + void resetText() { + removeAttribute(ITEM_ATTRIBUTE_TEXT); + } + const std::string& getText() const { + return getStrAttr(ITEM_ATTRIBUTE_TEXT); + } + + void setDate(int32_t n) { + setIntAttr(ITEM_ATTRIBUTE_DATE, n); + } + void resetDate() { + removeAttribute(ITEM_ATTRIBUTE_DATE); + } + time_t getDate() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); + } + + void setWriter(const std::string& writer) { + setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); + } + void resetWriter() { + removeAttribute(ITEM_ATTRIBUTE_WRITER); + } + const std::string& getWriter() const { + return getStrAttr(ITEM_ATTRIBUTE_WRITER); + } + + void setActionId(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); + } + uint16_t getActionId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); + } + + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + } + uint16_t getMovementId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + } + + void setKeyNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, n); + } + uint16_t getKeyNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + } + + void setKeyHoleNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, n); + } + uint16_t getKeyHoleNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } + + void setDoorQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, n); + } + uint16_t getDoorQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + void setDoorQuestValue(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, n); + } + uint16_t getDoorQuestValue() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + void setDoorLevel(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, n); + } + uint16_t getDoorLevel() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + void setChestQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, n); + } + uint16_t getChestQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + } + + void setCharges(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); + } + uint16_t getCharges() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); + } + + void setFluidType(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); + } + uint16_t getFluidType() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } + + void setOwner(uint32_t owner) { + setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); + } + uint32_t getOwner() const { + return getIntAttr(ITEM_ATTRIBUTE_OWNER); + } + + void setDuration(int32_t time) { + setIntAttr(ITEM_ATTRIBUTE_DURATION, time); + } + void decreaseDuration(int32_t time) { + increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); + } + uint32_t getDuration() const { + return getIntAttr(ITEM_ATTRIBUTE_DURATION); + } + + void setDecaying(ItemDecayState_t decayState) { + setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); + } + ItemDecayState_t getDecaying() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } + + protected: + inline bool hasAttribute(itemAttrTypes type) const { + return (type & attributeBits) != 0; + } + void removeAttribute(itemAttrTypes type); + + static std::string emptyString; + + struct Attribute + { + union { + int64_t integer; + std::string* string; + } value; + itemAttrTypes type; + + explicit Attribute(itemAttrTypes type) : type(type) { + memset(&value, 0, sizeof(value)); + } + Attribute(const Attribute& i) { + type = i.type; + if (ItemAttributes::isIntAttrType(type)) { + value.integer = i.value.integer; + } else if (ItemAttributes::isStrAttrType(type)) { + value.string = new std::string(*i.value.string); + } else { + memset(&value, 0, sizeof(value)); + } + } + Attribute(Attribute&& attribute) : value(attribute.value), type(attribute.type) { + memset(&attribute.value, 0, sizeof(value)); + attribute.type = ITEM_ATTRIBUTE_NONE; + } + ~Attribute() { + if (ItemAttributes::isStrAttrType(type)) { + delete value.string; + } + } + Attribute& operator=(Attribute other) { + Attribute::swap(*this, other); + return *this; + } + Attribute& operator=(Attribute&& other) { + if (this != &other) { + if (ItemAttributes::isStrAttrType(type)) { + delete value.string; + } + + value = other.value; + type = other.type; + + memset(&other.value, 0, sizeof(value)); + other.type = ITEM_ATTRIBUTE_NONE; + } + return *this; + } + + static void swap(Attribute& first, Attribute& second) { + std::swap(first.value, second.value); + std::swap(first.type, second.type); + } + }; + + std::forward_list attributes; + uint32_t attributeBits = 0; + + const std::string& getStrAttr(itemAttrTypes type) const; + void setStrAttr(itemAttrTypes type, const std::string& value); + + int64_t getIntAttr(itemAttrTypes type) const; + void setIntAttr(itemAttrTypes type, int64_t value); + void increaseIntAttr(itemAttrTypes type, int64_t value); + + const Attribute* getExistingAttr(itemAttrTypes type) const; + Attribute& getAttr(itemAttrTypes type); + + public: + inline static bool isIntAttrType(itemAttrTypes type) { + return (type & 0xFFFFE13) != 0; + } + inline static bool isStrAttrType(itemAttrTypes type) { + return (type & 0x1EC) != 0; + } + + const std::forward_list& getList() const { + return attributes; + } + + friend class Item; +}; + +class Item : virtual public Thing +{ + public: + //Factory member to create item of right type based on type + static Item* CreateItem(const uint16_t type, uint16_t count = 0); + static Container* CreateItemAsContainer(const uint16_t type, uint16_t size); + static Item* CreateItem(PropStream& propStream); + static Items items; + + // Constructor for items + Item(const uint16_t type, uint16_t count = 0); + Item(const Item& i); + virtual Item* clone() const; + + virtual ~Item() = default; + + // non-assignable + Item& operator=(const Item&) = delete; + + bool equals(const Item* otherItem) const; + + Item* getItem() final { + return this; + } + const Item* getItem() const final { + return this; + } + virtual Teleport* getTeleport() { + return nullptr; + } + virtual const Teleport* getTeleport() const { + return nullptr; + } + virtual Mailbox* getMailbox() { + return nullptr; + } + virtual const Mailbox* getMailbox() const { + return nullptr; + } + virtual DepotLocker* getDepotLocker() { + return nullptr; + } + virtual const DepotLocker* getDepotLocker() const { + return nullptr; + } + virtual Door* getDoor() { + return nullptr; + } + virtual const Door* getDoor() const { + return nullptr; + } + virtual MagicField* getMagicField() { + return nullptr; + } + virtual const MagicField* getMagicField() const { + return nullptr; + } + virtual BedItem* getBed() { + return nullptr; + } + virtual const BedItem* getBed() const { + return nullptr; + } + + const std::string& getStrAttr(itemAttrTypes type) const { + if (!attributes) { + return ItemAttributes::emptyString; + } + return attributes->getStrAttr(type); + } + void setStrAttr(itemAttrTypes type, const std::string& value) { + getAttributes()->setStrAttr(type, value); + } + + int32_t getIntAttr(itemAttrTypes type) const { + if (!attributes) { + return 0; + } + return attributes->getIntAttr(type); + } + void setIntAttr(itemAttrTypes type, int32_t value) { + getAttributes()->setIntAttr(type, value); + } + void increaseIntAttr(itemAttrTypes type, int32_t value) { + getAttributes()->increaseIntAttr(type, value); + } + + void removeAttribute(itemAttrTypes type) { + if (attributes) { + attributes->removeAttribute(type); + } + } + bool hasAttribute(itemAttrTypes type) const { + if (!attributes) { + return false; + } + return attributes->hasAttribute(type); + } + + void setSpecialDescription(const std::string& desc) { + setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); + } + const std::string& getSpecialDescription() const { + return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); + } + + void setText(const std::string& text) { + setStrAttr(ITEM_ATTRIBUTE_TEXT, text); + } + void resetText() { + removeAttribute(ITEM_ATTRIBUTE_TEXT); + } + const std::string& getText() const { + return getStrAttr(ITEM_ATTRIBUTE_TEXT); + } + + void setDate(int32_t n) { + setIntAttr(ITEM_ATTRIBUTE_DATE, n); + } + void resetDate() { + removeAttribute(ITEM_ATTRIBUTE_DATE); + } + time_t getDate() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); + } + + void setWriter(const std::string& writer) { + setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); + } + void resetWriter() { + removeAttribute(ITEM_ATTRIBUTE_WRITER); + } + const std::string& getWriter() const { + return getStrAttr(ITEM_ATTRIBUTE_WRITER); + } + + void setActionId(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); + } + uint16_t getActionId() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); + } + + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + } + uint16_t getMovementId() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + } + + void setCharges(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); + } + uint16_t getCharges() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); + } + + void setFluidType(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); + } + uint16_t getFluidType() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } + + void setOwner(uint32_t owner) { + setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); + } + uint32_t getOwner() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_OWNER); + } + + void setCorpseOwner(uint32_t corpseOwner) { + setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); + } + uint32_t getCorpseOwner() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); + } + + void setDuration(int32_t time) { + setIntAttr(ITEM_ATTRIBUTE_DURATION, time); + } + void decreaseDuration(int32_t time) { + increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); + } + uint32_t getDuration() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_DURATION); + } + + void setDecaying(ItemDecayState_t decayState) { + setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); + } + ItemDecayState_t getDecaying() const { + if (!attributes) { + return DECAYING_FALSE; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } + + static std::string getDescription(const ItemType& it, int32_t lookDistance, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); + static std::string getNameDescription(const ItemType& it, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); + static std::string getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count = 1); + + std::string getDescription(int32_t lookDistance) const final; + std::string getNameDescription() const; + std::string getWeightDescription() const; + + //serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + bool unserializeAttr(PropStream& propStream); + virtual bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream); + + virtual void serializeAttr(PropWriteStream& propWriteStream) const; + + bool isPushable() const final { + return isMoveable(); + } + int32_t getThrowRange() const final { + return (isPickupable() ? 15 : 2); + } + + uint16_t getID() const { + return id; + } + void setID(uint16_t newid); + + // Returns the player that is holding this item in his inventory + Player* getHoldingPlayer() const; + + CombatType_t getDamageType() const { + return items[id].damageType; + } + CombatType_t getCombatType() const { + return items[id].combatType; + } + WeaponType_t getWeaponType() const { + return items[id].weaponType; + } + Ammo_t getAmmoType() const { + return items[id].ammoType; + } + uint8_t getShootRange() const { + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { + return getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE); + } + return items[id].shootRange; + } + uint8_t getMissileType() const { + return items[id].shootType; + } + uint8_t getFragility() const { + return items[id].fragility; + } + + int32_t getAttackStrength() const { + return items[id].attackStrength; + } + int32_t getAttackVariation() const { + return items[id].attackVariation; + } + int32_t getManaConsumption() const { + return items[id].manaConsumption; + } + uint32_t getMinimumLevel() const { + return items[id].minReqLevel; + } + int32_t getWeaponSpecialEffect() const { + return items[id].weaponSpecialEffect; + } + virtual uint32_t getWeight() const; + uint32_t getBaseWeight() const { + if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { + return getIntAttr(ITEM_ATTRIBUTE_WEIGHT); + } + return items[id].weight; + } + int32_t getAttack() const { + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { + return getIntAttr(ITEM_ATTRIBUTE_ATTACK); + } + return items[id].attack; + } + int32_t getArmor() const { + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { + return getIntAttr(ITEM_ATTRIBUTE_ARMOR); + } + return items[id].armor; + } + int32_t getDefense() const { + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { + return getIntAttr(ITEM_ATTRIBUTE_DEFENSE); + } + return items[id].defense; + } + int32_t getSlotPosition() const { + return items[id].slotPosition; + } + uint16_t getDisguiseId() const { + return items[id].disguiseId; + } + + uint32_t getWorth() const; + void getLight(LightInfo& lightInfo) const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool isBlocking() const { + return items[id].blockSolid; + } + bool isStackable() const { + return items[id].stackable; + } + bool isAlwaysOnTop() const { + return items[id].alwaysOnTop; + } + bool isGroundTile() const { + return items[id].isGroundTile(); + } + bool isMagicField() const { + return items[id].isMagicField(); + } + bool isSplash() const { + return items[id].isSplash(); + } + bool isMoveable() const { + return items[id].moveable; + } + bool isPickupable() const { + return items[id].pickupable; + } + bool isHangable() const { + return items[id].isHangable; + } + bool isRotatable() const { + const ItemType& it = items[id]; + return it.rotatable && it.rotateTo; + } + bool isDisguised() const { + return items[id].disguise; + } + bool isChangeUse() const { + return items[id].changeUse; + } + bool isChestQuest() const { + return items[id].isChest(); + } + bool hasCollisionEvent() const { + return items[id].collisionEvent; + } + bool hasSeparationEvent() const { + return items[id].separationEvent; + } + bool hasUseEvent() const { + return items[id].useEvent; + } + bool hasMultiUseEvent() const { + return items[id].multiUseEvent; + } + bool canDistUse() const { + return items[id].distUse; + } + bool isRune() const { + return items[id].isRune(); + } + const std::string& getName() const { + if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { + return getStrAttr(ITEM_ATTRIBUTE_NAME); + } + return items[id].name; + } + const std::string getPluralName() const { + if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { + return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME); + } + return items[id].getPluralName(); + } + const std::string& getArticle() const { + if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { + return getStrAttr(ITEM_ATTRIBUTE_ARTICLE); + } + return items[id].article; + } + + // get the number of items + uint16_t getItemCount() const { + return count; + } + void setItemCount(uint8_t n) { + count = n; + } + + static uint32_t countByType(const Item* i, int32_t subType); + + void setDefaultSubtype(); + uint16_t getSubType() const; + void setSubType(uint16_t n); + + void setDefaultDuration() { + uint32_t duration = getDefaultDuration(); + if (duration != 0) { + setDuration(duration); + } + } + uint32_t getDefaultDuration() const { + return items[id].decayTime * 1000; + } + bool canDecay() const; + + virtual bool canRemove() const { + return true; + } + virtual bool canTransform() const { + return true; + } + virtual void onRemoved(); + virtual void onTradeEvent(TradeEvents_t, Player*) {} + + virtual void startDecaying(); + + void setLoadedFromMap(bool value) { + loadedFromMap = value; + } + bool isCleanable() const { + return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + } + + std::unique_ptr& getAttributes() { + if (!attributes) { + attributes.reset(new ItemAttributes()); + } + return attributes; + } + + void incrementReferenceCounter() { + ++referenceCounter; + } + void decrementReferenceCounter() { + if (--referenceCounter == 0) { + delete this; + } + } + + Cylinder* getParent() const { + return parent; + } + void setParent(Cylinder* cylinder) { + parent = cylinder; + } + Cylinder* getTopParent(); + const Cylinder* getTopParent() const; + Tile* getTile(); + const Tile* getTile() const; + bool isRemoved() const { + return !parent || parent->isRemoved(); + } + + protected: + std::string getWeightDescription(uint32_t weight) const; + + Cylinder* parent = nullptr; + std::unique_ptr attributes; + + uint32_t referenceCounter = 0; + + uint16_t id; // the same id as in ItemType + uint8_t count = 1; // number of stacked items + + bool loadedFromMap = false; + + //Don't add variables here, use the ItemAttribute class. +}; + +typedef std::list ItemList; +typedef std::deque ItemDeque; + +inline uint32_t Item::countByType(const Item* i, int32_t subType) +{ + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; +} + +#endif diff --git a/app/SabrehavenServer/src/items.cpp b/app/SabrehavenServer/src/items.cpp new file mode 100644 index 0000000..0adad2b --- /dev/null +++ b/app/SabrehavenServer/src/items.cpp @@ -0,0 +1,608 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "items.h" +#include "game.h" +#include "spells.h" +#include "movement.h" +#include "script.h" + +#include "pugicast.h" + +extern MoveEvents* g_moveEvents; + +Items::Items() +{ + items.reserve(6000); + nameToItems.reserve(6000); +} + +void Items::clear() +{ + items.clear(); + nameToItems.clear(); +} + +bool Items::reload() +{ + clear(); + if (!loadItems()) { + return false; + } + + g_moveEvents->reload(); + return true; +} + +bool Items::loadItems() +{ + ScriptReader script; + if (!script.open("data/items" + std::to_string(g_game.getClientVersion()) + "/items.srv")) { + return false; + } + + std::string identifier; + uint16_t id = 0; + while (true) { + script.nextToken(); + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token != IDENTIFIER) { + script.error("Identifier expected"); + return false; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "typeid") { + id = script.readNumber(); + if (id >= items.size()) { + items.resize(id + 1); + } + + if (items[id].id) { + script.error("item type already defined"); + return false; + } + + items[id].id = id; + } else if (identifier == "name") { + items[id].name = script.readString(); + } else if (identifier == "description") { + items[id].description = script.readString(); + } else if (identifier == "flags") { + script.readSymbol('{'); + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + + if (identifier == "bank") { + items[id].group = ITEM_GROUP_GROUND; + } else if (identifier == "clip") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 1; + } else if (identifier == "bottom") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 2; + } else if (identifier == "top") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 3; + } else if (identifier == "container") { + items[id].type = ITEM_TYPE_CONTAINER; + } else if (identifier == "chest") { + items[id].type = ITEM_TYPE_CHEST; + } else if (identifier == "cumulative") { + items[id].stackable = true; + } else if (identifier == "changeuse") { + items[id].changeUse = true; + } else if (identifier == "forceuse") { + items[id].forceUse = true; + } else if (identifier == "key") { + items[id].type = ITEM_TYPE_KEY; + items[id].group = ITEM_GROUP_KEY; + } else if (identifier == "door") { + items[id].type = ITEM_TYPE_DOOR; + } else if (identifier == "bed") { + items[id].type = ITEM_TYPE_BED; + } else if (identifier == "rune") { + items[id].type = ITEM_TYPE_RUNE; + } else if (identifier == "depot") { + items[id].type = ITEM_TYPE_DEPOT; + } else if (identifier == "mailbox") { + items[id].type = ITEM_TYPE_MAILBOX; + } else if (identifier == "allowdistread") { + items[id].allowDistRead = true; + } else if (identifier == "text") { + items[id].canReadText = true; + } else if (identifier == "write") { + items[id].canWriteText = true; + } else if (identifier == "writeonce") { + items[id].canWriteText = true; + items[id].writeOnceItemId = id; + } else if (identifier == "fluidcontainer") { + items[id].group = ITEM_GROUP_FLUID; + } else if (identifier == "splash") { + items[id].group = ITEM_GROUP_SPLASH; + } else if (identifier == "unpass") { + items[id].blockSolid = true; + } else if (identifier == "unmove") { + items[id].moveable = false; + } else if (identifier == "unthrow") { + items[id].blockProjectile = true; + } else if (identifier == "unlay") { + items[id].allowPickupable = false; + } else if (identifier == "avoid") { + items[id].blockPathFind = true; + } else if (identifier == "magicfield") { + items[id].type = ITEM_TYPE_MAGICFIELD; + items[id].group = ITEM_GROUP_MAGICFIELD; + } else if (identifier == "take") { + items[id].pickupable = true; + } else if (identifier == "hang") { + items[id].isHangable = true; + } else if (identifier == "hooksouth") { + items[id].isHorizontal = true; + } else if (identifier == "hookeast") { + items[id].isVertical = true; + } else if (identifier == "rotate") { + items[id].rotatable = true; + } else if (identifier == "destroy") { + items[id].destroy = true; + } else if (identifier == "corpse") { + items[id].corpse = true; + } else if (identifier == "expire") { + items[id].stopTime = false; + } else if (identifier == "expirestop") { + items[id].stopTime = true; + } else if (identifier == "weapon") { + items[id].group = ITEM_GROUP_WEAPON; + } else if (identifier == "shield") { + items[id].weaponType = WEAPON_SHIELD; + } else if (identifier == "distance") { + items[id].weaponType = WEAPON_DISTANCE; + } else if (identifier == "wand") { + items[id].weaponType = WEAPON_WAND; + } else if (identifier == "ammo") { + items[id].weaponType = WEAPON_AMMO; + } else if (identifier == "armor") { + items[id].group = ITEM_GROUP_ARMOR; + } else if (identifier == "height") { + items[id].hasHeight = true; + } else if (identifier == "disguise") { + items[id].disguise = true; + } else if (identifier == "showdetail") { + items[id].showDuration = true; + } else if (identifier == "noreplace") { + items[id].replaceable = false; + } else if (identifier == "collisionevent") { + items[id].collisionEvent = true; + } else if (identifier == "separationevent") { + items[id].separationEvent = true; + } else if (identifier == "useevent") { + items[id].useEvent = true; + } else if (identifier == "distuse") { + items[id].distUse = true; + } else if (identifier == "multiuse") { + items[id].multiUseEvent = true; + } else { + script.error("Unknown flag"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + } else if (identifier == "attributes") { + script.readSymbol('{'); + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "waypoints") { + items[id].speed = script.readNumber(); + } else if (identifier == "capacity") { + items[id].maxItems = script.readNumber(); + } else if (identifier == "changetarget") { + items[id].transformToOnUse = script.readNumber(); + } else if (identifier == "nutrition") { + items[id].nutrition = script.readNumber(); + } else if (identifier == "maxlength") { + items[id].maxTextLen = script.readNumber(); + } else if (identifier == "fluidsource") { + items[id].fluidSource = getFluidType(script.readIdentifier()); + } else if (identifier == "avoiddamagetypes") { + items[id].combatType = getCombatType(script.readIdentifier()); + } else if (identifier == "damagetype") { + items[id].damageType = getCombatType(script.readIdentifier()); + } else if (identifier == "attackstrength") { + items[id].attackStrength = script.readNumber(); + } else if (identifier == "attackvariation") { + items[id].attackVariation = script.readNumber(); + } else if (identifier == "manaconsumption") { + items[id].manaConsumption = script.readNumber(); + } else if (identifier == "minimumlevel") { + items[id].minReqLevel = script.readNumber(); + items[id].wieldInfo |= WIELDINFO_LEVEL; + } else if (identifier == "vocations") { + int32_t vocations = script.readNumber(); + items[id].vocations = vocations; + + std::list vocationStringList; + + if (hasBitSet(VOCATION_SORCERER, vocations)) { + vocationStringList.push_back("sorcerer"); + } + + if (hasBitSet(VOCATION_DRUID, vocations)) { + vocationStringList.push_back("druid"); + } + + if (hasBitSet(VOCATION_PALADIN, vocations)) { + vocationStringList.push_back("paladin"); + } + + if (hasBitSet(VOCATION_KNIGHT, vocations)) { + vocationStringList.push_back("knight"); + } + + std::string vocationString; + for (const std::string& str : vocationStringList) { + if (!vocationString.empty()) { + if (str != vocationStringList.back()) { + vocationString.push_back(','); + vocationString.push_back(' '); + } else { + vocationString += " and "; + } + } + + vocationString += str; + vocationString.push_back('s'); + } + + items[id].wieldInfo |= WIELDINFO_VOCREQ; + items[id].vocationString = vocationString; + } else if (identifier == "weaponspecialeffect") { + items[id].weaponSpecialEffect = script.readNumber(); + } else if (identifier == "beddirection") { + items[id].bedPartnerDir = getDirection(script.readIdentifier()); + } else if (identifier == "bedtarget") { + items[id].transformToOnUse = script.readNumber(); + } else if (identifier == "bedfree") { + items[id].transformToFree = script.readNumber(); + } else if (identifier == "weight") { + items[id].weight = script.readNumber(); + } else if (identifier == "rotatetarget") { + items[id].rotateTo = script.readNumber(); + } else if (identifier == "destroytarget") { + items[id].destroyTarget = script.readNumber(); + } else if (identifier == "slottype") { + identifier = asLowerCaseString(script.readIdentifier()); + if (identifier == "head") { + items[id].slotPosition |= SLOTP_HEAD; + } else if (identifier == "body") { + items[id].slotPosition |= SLOTP_ARMOR; + } else if (identifier == "legs") { + items[id].slotPosition |= SLOTP_LEGS; + } else if (identifier == "feet") { + items[id].slotPosition |= SLOTP_FEET; + } else if (identifier == "backpack") { + items[id].slotPosition |= SLOTP_BACKPACK; + } else if (identifier == "twohanded") { + items[id].slotPosition |= SLOTP_TWO_HAND; + } else if (identifier == "righthand") { + items[id].slotPosition &= ~SLOTP_LEFT; + } else if (identifier == "lefthand") { + items[id].slotPosition &= ~SLOTP_RIGHT; + } else if (identifier == "necklace") { + items[id].slotPosition |= SLOTP_NECKLACE; + } else if (identifier == "ring") { + items[id].slotPosition |= SLOTP_RING; + } else if (identifier == "ammo") { + items[id].slotPosition |= SLOTP_AMMO; + } else if (identifier == "hand") { + items[id].slotPosition |= SLOTP_HAND; + } else { + script.error("Unknown slot position"); + return false; + } + } else if (identifier == "speedboost") { + items[id].getAbilities().speed = script.readNumber(); + } else if (identifier == "fistboost") { + items[id].getAbilities().skills[SKILL_FIST] = script.readNumber(); + } else if (identifier == "swordboost") { + items[id].getAbilities().skills[SKILL_SWORD] = script.readNumber(); + } else if (identifier == "clubboost") { + items[id].getAbilities().skills[SKILL_CLUB] = script.readNumber(); + } else if (identifier == "axeboost") { + items[id].getAbilities().skills[SKILL_AXE] = script.readNumber(); + } else if (identifier == "shieldboost") { + items[id].getAbilities().skills[SKILL_SHIELD] = script.readNumber(); + } else if (identifier == "distanceboost") { + items[id].getAbilities().skills[SKILL_DISTANCE] = script.readNumber(); + } else if (identifier == "magicboost") { + items[id].getAbilities().stats[STAT_MAGICPOINTS] = script.readNumber(); + } else if (identifier == "percenthp") { + items[id].getAbilities().statsPercent[STAT_MAXHITPOINTS] = script.readNumber(); + } else if (identifier == "percentmp") { + items[id].getAbilities().statsPercent[STAT_MAXMANAPOINTS] = script.readNumber(); + } else if (identifier == "suppressdrunk") { + if (script.readNumber()) { + items[id].getAbilities().conditionSuppressions |= CONDITION_DRUNK; + } + } else if (identifier == "invisible") { + if (script.readNumber()) { + items[id].getAbilities().invisible = true; + } + } else if (identifier == "manashield") { + if (script.readNumber()) { + items[id].getAbilities().manaShield = true; + } + } else if (identifier == "healthticks") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.healthTicks = script.readNumber(); + } else if (identifier == "healthgain") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.healthGain = script.readNumber(); + } else if (identifier == "manaticks") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.manaTicks = script.readNumber(); + } else if (identifier == "managain") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.manaGain = script.readNumber(); + } else if (identifier == "absorbmagic") { + int32_t percent = script.readNumber(); + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += percent; + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += percent; + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += percent; + } else if (identifier == "absorbenergy") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbfire") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbpoison") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbdrown") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += script.readNumber(); + } else if (identifier == "absorblifedrain") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += script.readNumber(); + } else if (identifier == "absorbmanadrain") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += script.readNumber(); + } else if (identifier == "absorbphysical") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbhealing") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += script.readNumber(); + } else if (identifier == "absorbundefined") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbfirefield") { + items[id].getAbilities().fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += static_cast(script.readNumber()); + } else if (identifier == "brightness") { + items[id].lightLevel = script.readNumber(); + } else if (identifier == "lightcolor") { + items[id].lightColor = script.readNumber(); + } else if (identifier == "totalexpiretime") { + items[id].decayTime = script.readNumber(); + } else if (identifier == "expiretarget") { + items[id].decayTo = script.readNumber(); + } else if (identifier == "totaluses") { + items[id].charges = script.readNumber(); + } else if (identifier == "weapontype") { + identifier = script.readIdentifier(); + if (identifier == "sword") { + items[id].weaponType = WEAPON_SWORD; + } else if (identifier == "club") { + items[id].weaponType = WEAPON_CLUB; + } else if (identifier == "axe") { + items[id].weaponType = WEAPON_AXE; + } else if (identifier == "shield") { + items[id].weaponType = WEAPON_SHIELD; + } else if (identifier == "distance") { + items[id].weaponType = WEAPON_DISTANCE; + } else if (identifier == "wand") { + items[id].weaponType = WEAPON_WAND; + } else if (identifier == "ammunition") { + items[id].weaponType = WEAPON_AMMO; + } else { + script.error("Unknown weapon type"); + return false; + } + } else if (identifier == "attack") { + items[id].attack = script.readNumber(); + } else if (identifier == "defense") { + items[id].defense = script.readNumber(); + } else if (identifier == "range") { + items[id].shootRange = static_cast(script.readNumber()); + } else if (identifier == "ammotype") { + items[id].ammoType = getAmmoType(script.readIdentifier()); + if (items[id].ammoType == AMMO_NONE) { + script.error("Unknown ammo type"); + return false; + } + } else if (identifier == "missileeffect") { + items[id].shootType = static_cast(script.readNumber()); + } else if (identifier == "fragility") { + items[id].fragility = script.readNumber(); + } else if (identifier == "armorvalue") { + items[id].armor = script.readNumber(); + } else if (identifier == "disguisetarget") { + items[id].disguiseId = script.readNumber(); + } else if (identifier == "equiptarget") { + items[id].transformEquipTo = script.readNumber(); + } else if (identifier == "deequiptarget") { + items[id].transformDeEquipTo = script.readNumber(); + } else { + script.error("Unknown attribute"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + } else if (identifier == "magicfield") { + script.readSymbol('{'); + + CombatType_t combatType = COMBAT_NONE; + ConditionDamage* conditionDamage = nullptr; + + int32_t cycles = 0; + int32_t hit_damage = 0; + + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "type") { + identifier = script.readIdentifier(); + if (identifier == "fire") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + combatType = COMBAT_FIREDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else if (identifier == "energy") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY); + combatType = COMBAT_ENERGYDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else if (identifier == "poison") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + combatType = COMBAT_EARTHDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else { + script.error("unknown magicfield type"); + return false; + } + } else if (identifier == "count") { + cycles = script.readNumber(); + } else if (identifier == "damage") { + hit_damage = script.readNumber(); + } else { + script.error("unknown identifier"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + + int32_t count = 3; + + if (combatType == COMBAT_FIREDAMAGE) { + cycles /= 10; + count = 8; + } else if (combatType == COMBAT_ENERGYDAMAGE) { + cycles /= 20; + count = 10; + } + + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_HIT_DAMAGE, hit_damage); + + conditionDamage->setParam(CONDITION_PARAM_FIELD, 1); + } + } + + script.close(); + items.shrink_to_fit(); + + for (ItemType& type : items) { + std::string& name = type.name; + extractArticleAndName(name, type.article, type.name); + nameToItems.insert({ asLowerCaseString(type.name), type.id }); + if (!name.empty()) { + if (type.stackable) { + type.showCount = true; + type.pluralName = pluralizeString(name); + } + } + } + + return true; +} + +ItemType& Items::getItemType(size_t id) +{ + if (id < items.size()) { + return items[id]; + } + return items.front(); +} + +const ItemType& Items::getItemType(size_t id) const +{ + if (id < items.size()) { + return items[id]; + } + return items.front(); +} + +uint16_t Items::getItemIdByName(const std::string& name) +{ + auto result = nameToItems.find(asLowerCaseString(name)); + + if (result == nameToItems.end()) + return 0; + + return result->second; +} diff --git a/app/SabrehavenServer/src/items.h b/app/SabrehavenServer/src/items.h new file mode 100644 index 0000000..6411e99 --- /dev/null +++ b/app/SabrehavenServer/src/items.h @@ -0,0 +1,326 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C +#define FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C + +#include "const.h" +#include "enums.h" +#include "position.h" +#include "fileloader.h" + +enum SlotPositionBits : uint32_t { + SLOTP_WHEREEVER = 0xFFFFFFFF, + SLOTP_HEAD = 1 << 0, + SLOTP_NECKLACE = 1 << 1, + SLOTP_BACKPACK = 1 << 2, + SLOTP_ARMOR = 1 << 3, + SLOTP_RIGHT = 1 << 4, + SLOTP_LEFT = 1 << 5, + SLOTP_LEGS = 1 << 6, + SLOTP_FEET = 1 << 7, + SLOTP_RING = 1 << 8, + SLOTP_AMMO = 1 << 9, + SLOTP_DEPOT = 1 << 10, + SLOTP_TWO_HAND = 1 << 11, + SLOTP_HAND = (SLOTP_LEFT | SLOTP_RIGHT) +}; + +enum ItemTypes_t { + ITEM_TYPE_NONE, + ITEM_TYPE_DEPOT, + ITEM_TYPE_MAILBOX, + ITEM_TYPE_CONTAINER, + ITEM_TYPE_DOOR, + ITEM_TYPE_MAGICFIELD, + ITEM_TYPE_TELEPORT, + ITEM_TYPE_BED, + ITEM_TYPE_KEY, + ITEM_TYPE_RUNE, + ITEM_TYPE_CHEST, + ITEM_TYPE_LAST +}; + +enum itemgroup_t { + ITEM_GROUP_NONE, + + ITEM_GROUP_GROUND, + ITEM_GROUP_WEAPON, + ITEM_GROUP_AMMUNITION, + ITEM_GROUP_ARMOR, + ITEM_GROUP_CHARGES, + ITEM_GROUP_TELEPORT, + ITEM_GROUP_MAGICFIELD, + ITEM_GROUP_WRITEABLE, + ITEM_GROUP_KEY, + ITEM_GROUP_SPLASH, + ITEM_GROUP_FLUID, + ITEM_GROUP_DOOR, + ITEM_GROUP_DEPRECATED, + + ITEM_GROUP_LAST +}; + +struct Abilities { + uint32_t healthGain = 0; + uint32_t healthTicks = 0; + uint32_t manaGain = 0; + uint32_t manaTicks = 0; + + uint32_t conditionImmunities = 0; + uint32_t conditionSuppressions = 0; + + //stats modifiers + int32_t stats[STAT_LAST + 1] = { 0 }; + int32_t statsPercent[STAT_LAST + 1] = { 0 }; + + //extra skill modifiers + int32_t skills[SKILL_LAST + 1] = { 0 }; + + int32_t speed = 0; + + // field damage abilities modifiers + int16_t fieldAbsorbPercent[COMBAT_COUNT] = { 0 }; + + //damage abilities modifiers + int16_t absorbPercent[COMBAT_COUNT] = { 0 }; + + bool manaShield = false; + bool invisible = false; + bool regeneration = false; +}; + +class ConditionDamage; + +class ItemType +{ + public: + ItemType() = default; + + //non-copyable + ItemType(const ItemType& other) = delete; + ItemType& operator=(const ItemType& other) = delete; + + ItemType(ItemType&& other) = default; + ItemType& operator=(ItemType&& other) = default; + + bool isGroundTile() const { + return group == ITEM_GROUP_GROUND; + } + bool isContainer() const { + return type == ITEM_TYPE_CONTAINER; + } + bool isChest() const { + return type == ITEM_TYPE_CHEST; + } + bool isSplash() const { + return group == ITEM_GROUP_SPLASH; + } + bool isFluidContainer() const { + return group == ITEM_GROUP_FLUID; + } + + bool isDoor() const { + return (type == ITEM_TYPE_DOOR); + } + bool isMagicField() const { + return (type == ITEM_TYPE_MAGICFIELD); + } + bool isTeleport() const { + return (type == ITEM_TYPE_TELEPORT); + } + bool isKey() const { + return (type == ITEM_TYPE_KEY); + } + bool isDepot() const { + return (type == ITEM_TYPE_DEPOT); + } + bool isMailbox() const { + return (type == ITEM_TYPE_MAILBOX); + } + bool isBed() const { + return (type == ITEM_TYPE_BED); + } + bool isRune() const { + return type == ITEM_TYPE_RUNE; + } + bool hasSubType() const { + return (isFluidContainer() || isSplash() || stackable || charges != 0); + } + + Abilities& getAbilities() { + if (!abilities) { + abilities.reset(new Abilities()); + } + return *abilities; + } + + std::string getPluralName() const { + if (!pluralName.empty()) { + return pluralName; + } + + if (showCount == 0) { + return name; + } + + std::string str; + str.reserve(name.length() + 1); + str.assign(name); + str += 's'; + return str; + } + + itemgroup_t group = ITEM_GROUP_NONE; + ItemTypes_t type = ITEM_TYPE_NONE; + uint16_t id = 0; + bool stackable = false; + + std::string name; + std::string article; + std::string pluralName; + std::string description; + std::string runeSpellName; + std::string vocationString; + + std::unique_ptr abilities; + std::unique_ptr conditionDamage; + + uint32_t weight = 0; + uint32_t decayTime = 0; + uint32_t wieldInfo = 0; + uint32_t minReqLevel = 0; + uint32_t minReqMagicLevel = 0; + uint32_t charges = 0; + int32_t attackStrength = 0; + int32_t attackVariation = 0; + int32_t manaConsumption = 0; + int32_t vocations = 0; + int32_t decayTo = -1; + int32_t attack = 0; + int32_t defense = 0; + int32_t extraDefense = 0; + int32_t armor = 0; + int32_t rotateTo = 0; + int32_t runeMagLevel = 0; + int32_t runeLevel = 0; + int32_t nutrition = 0; + int32_t destroyTarget = 0; + + CombatType_t combatType = COMBAT_NONE; + CombatType_t damageType = COMBAT_NONE; + + uint16_t transformToOnUse = 0; + uint16_t transformToFree = 0; + uint16_t disguiseId = 0; + uint16_t destroyTo = 0; + uint16_t maxTextLen = 0; + uint16_t writeOnceItemId = 0; + uint16_t transformEquipTo = 0; + uint16_t transformDeEquipTo = 0; + uint16_t maxItems = 8; + uint16_t slotPosition = SLOTP_RIGHT | SLOTP_LEFT | SLOTP_AMMO; + uint16_t speed = 0; + + MagicEffectClasses magicEffect = CONST_ME_NONE; + Direction bedPartnerDir = DIRECTION_NONE; + WeaponType_t weaponType = WEAPON_NONE; + Ammo_t ammoType = AMMO_NONE; + ShootType_t shootType = CONST_ANI_NONE; + RaceType_t corpseType = RACE_NONE; + FluidTypes_t fluidSource = FLUID_NONE; + + uint8_t fragility = 0; + uint8_t alwaysOnTopOrder = 0; + uint8_t lightLevel = 0; + uint8_t lightColor = 0; + uint8_t shootRange = 1; + uint8_t weaponSpecialEffect = 0; + + bool collisionEvent = false; + bool separationEvent = false; + bool useEvent = false; + bool multiUseEvent = false; + bool distUse = false; + bool disguise = false; + bool forceUse = false; + bool changeUse = false; + bool destroy = false; + bool corpse = false; + bool hasHeight = false; + bool walkStack = true; + bool blockSolid = false; + bool blockPickupable = false; + bool blockProjectile = false; + bool blockPathFind = false; + bool allowPickupable = true; + bool showDuration = false; + bool showCharges = false; + bool showAttributes = false; + bool replaceable = true; + bool pickupable = false; + bool rotatable = false; + bool useable = false; + bool moveable = true; + bool alwaysOnTop = false; + bool canReadText = false; + bool canWriteText = false; + bool isVertical = false; + bool isHorizontal = false; + bool isHangable = false; + bool allowDistRead = false; + bool lookThrough = false; + bool stopTime = false; + bool showCount = true; +}; + +class Items +{ + public: + using nameMap = std::unordered_multimap; + + Items(); + + // non-copyable + Items(const Items&) = delete; + Items& operator=(const Items&) = delete; + + bool reload(); + void clear(); + + const ItemType& operator[](size_t id) const { + return getItemType(id); + } + const ItemType& getItemType(size_t id) const; + ItemType& getItemType(size_t id); + + uint16_t getItemIdByName(const std::string& name); + + bool loadItems(); + + inline size_t size() const { + return items.size(); + } + + nameMap nameToItems; + + protected: + std::vector items; +}; +#endif diff --git a/app/SabrehavenServer/src/lockfree.h b/app/SabrehavenServer/src/lockfree.h new file mode 100644 index 0000000..7c8cc7b --- /dev/null +++ b/app/SabrehavenServer/src/lockfree.h @@ -0,0 +1,62 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 +#define FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 + +#if _MSC_FULL_VER >= 190023918 // Workaround for VS2015 Update 2. Boost.Lockfree is a header-only library, so this should be safe to do. +#define _ENABLE_ATOMIC_ALIGNMENT_FIX +#endif + +#include + +template +class LockfreePoolingAllocator : public std::allocator +{ + public: + template + explicit constexpr LockfreePoolingAllocator(const U&) {} + typedef T value_type; + + T* allocate(size_t) const { + T* p; // NOTE: p doesn't have to be initialized + if (!getFreeList().pop(p)) { + //Acquire memory without calling the constructor of T + p = static_cast(operator new (sizeof(T))); + } + return p; + } + + void deallocate(T* p, size_t) const { + if (!getFreeList().bounded_push(p)) { + //Release memory without calling the destructor of T + //(it has already been called at this point) + operator delete(p); + } + } + + private: + typedef boost::lockfree::stack> FreeList; + static FreeList& getFreeList() { + static FreeList freeList; + return freeList; + } +}; + +#endif diff --git a/app/SabrehavenServer/src/luascript.cpp b/app/SabrehavenServer/src/luascript.cpp new file mode 100644 index 0000000..c82f47d --- /dev/null +++ b/app/SabrehavenServer/src/luascript.cpp @@ -0,0 +1,12016 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "luascript.h" +#include "chat.h" +#include "player.h" +#include "game.h" +#include "protocolstatus.h" +#include "spells.h" +#include "iologindata.h" +#include "configmanager.h" +#include "teleport.h" +#include "databasemanager.h" +#include "bed.h" +#include "monster.h" +#include "scheduler.h" +#include "databasetasks.h" + +extern Chat* g_chat; +extern Game g_game; +extern Monsters g_monsters; +extern ConfigManager g_config; +extern Vocations g_vocations; +extern Spells* g_spells; + +ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; +uint32_t ScriptEnvironment::lastResultId = 0; + +std::multimap ScriptEnvironment::tempItems; + +LuaEnvironment g_luaEnvironment; + +ScriptEnvironment::ScriptEnvironment() +{ + resetEnv(); +} + +ScriptEnvironment::~ScriptEnvironment() +{ + resetEnv(); +} + +void ScriptEnvironment::resetEnv() +{ + scriptId = 0; + callbackId = 0; + timerEvent = false; + interface = nullptr; + localMap.clear(); + tempResults.clear(); + + auto pair = tempItems.equal_range(this); + auto it = pair.first; + while (it != pair.second) { + Item* item = it->second; + if (item->getParent() == VirtualCylinder::virtualCylinder) { + g_game.ReleaseItem(item); + } + it = tempItems.erase(it); + } +} + +bool ScriptEnvironment::setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface) +{ + if (this->callbackId != 0) { + //nested callbacks are not allowed + if (interface) { + interface->reportErrorFunc("Nested callbacks!"); + } + return false; + } + + this->callbackId = callbackId; + interface = scriptInterface; + return true; +} + +void ScriptEnvironment::getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const +{ + scriptId = this->scriptId; + scriptInterface = interface; + callbackId = this->callbackId; + timerEvent = this->timerEvent; +} + +uint32_t ScriptEnvironment::addThing(Thing* thing) +{ + if (!thing || thing->isRemoved()) { + return 0; + } + + Creature* creature = thing->getCreature(); + if (creature) { + return creature->getID(); + } + + Item* item = thing->getItem(); + for (const auto& it : localMap) { + if (it.second == item) { + return it.first; + } + } + + localMap[++lastUID] = item; + return lastUID; +} + +void ScriptEnvironment::insertItem(uint32_t uid, Item* item) +{ + auto result = localMap.emplace(uid, item); + if (!result.second) { + std::cout << std::endl << "Lua Script Error: Thing uid already taken."; + } +} + +Thing* ScriptEnvironment::getThingByUID(uint32_t uid) +{ + if (uid >= 0x10000000) { + return g_game.getCreatureByID(uid); + } + + auto it = localMap.find(uid); + if (it != localMap.end()) { + Item* item = it->second; + if (!item->isRemoved()) { + return item; + } + } + return nullptr; +} + +Item* ScriptEnvironment::getItemByUID(uint32_t uid) +{ + Thing* thing = getThingByUID(uid); + if (!thing) { + return nullptr; + } + return thing->getItem(); +} + +Container* ScriptEnvironment::getContainerByUID(uint32_t uid) +{ + Item* item = getItemByUID(uid); + if (!item) { + return nullptr; + } + return item->getContainer(); +} + +void ScriptEnvironment::removeItemByUID(uint32_t uid) +{ + auto it = localMap.find(uid); + if (it != localMap.end()) { + localMap.erase(it); + } +} + +void ScriptEnvironment::addTempItem(Item* item) +{ + tempItems.emplace(this, item); +} + +void ScriptEnvironment::removeTempItem(Item* item) +{ + for (auto it = tempItems.begin(), end = tempItems.end(); it != end; ++it) { + if (it->second == item) { + tempItems.erase(it); + break; + } + } +} + +uint32_t ScriptEnvironment::addResult(DBResult_ptr res) +{ + tempResults[++lastResultId] = res; + return lastResultId; +} + +bool ScriptEnvironment::removeResult(uint32_t id) +{ + auto it = tempResults.find(id); + if (it == tempResults.end()) { + return false; + } + + tempResults.erase(it); + return true; +} + +DBResult_ptr ScriptEnvironment::getResultByID(uint32_t id) +{ + auto it = tempResults.find(id); + if (it == tempResults.end()) { + return nullptr; + } + return it->second; +} + +std::string LuaScriptInterface::getErrorDesc(ErrorCode_t code) +{ + switch (code) { + case LUA_ERROR_PLAYER_NOT_FOUND: return "Player not found"; + case LUA_ERROR_CREATURE_NOT_FOUND: return "Creature not found"; + case LUA_ERROR_ITEM_NOT_FOUND: return "Item not found"; + case LUA_ERROR_THING_NOT_FOUND: return "Thing not found"; + case LUA_ERROR_TILE_NOT_FOUND: return "Tile not found"; + case LUA_ERROR_HOUSE_NOT_FOUND: return "House not found"; + case LUA_ERROR_COMBAT_NOT_FOUND: return "Combat not found"; + case LUA_ERROR_CONDITION_NOT_FOUND: return "Condition not found"; + case LUA_ERROR_AREA_NOT_FOUND: return "Area not found"; + case LUA_ERROR_CONTAINER_NOT_FOUND: return "Container not found"; + case LUA_ERROR_VARIANT_NOT_FOUND: return "Variant not found"; + case LUA_ERROR_VARIANT_UNKNOWN: return "Unknown variant type"; + case LUA_ERROR_SPELL_NOT_FOUND: return "Spell not found"; + default: return "Bad error code"; + } +} + +ScriptEnvironment LuaScriptInterface::scriptEnv[16]; +int32_t LuaScriptInterface::scriptEnvIndex = -1; + +LuaScriptInterface::LuaScriptInterface(std::string interfaceName) : interfaceName(std::move(interfaceName)) +{ + if (!g_luaEnvironment.getLuaState()) { + g_luaEnvironment.initState(); + } +} + +LuaScriptInterface::~LuaScriptInterface() +{ + closeState(); +} + +bool LuaScriptInterface::reInitState() +{ + g_luaEnvironment.clearCombatObjects(this); + g_luaEnvironment.clearAreaObjects(this); + + closeState(); + return initState(); +} + +/// Same as lua_pcall, but adds stack trace to error strings in called function. +int LuaScriptInterface::protectedCall(lua_State* L, int nargs, int nresults) +{ + int error_index = lua_gettop(L) - nargs; + lua_pushcfunction(L, luaErrorHandler); + lua_insert(L, error_index); + + int ret = lua_pcall(L, nargs, nresults, error_index); + lua_remove(L, error_index); + return ret; +} + +int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = nullptr*/) +{ + //loads file as a chunk at stack top + int ret = luaL_loadfile(luaState, file.c_str()); + if (ret != 0) { + lastLuaError = popString(luaState); + return -1; + } + + //check that it is loaded as a function + if (!isFunction(luaState, -1)) { + return -1; + } + + loadingFile = file; + + if (!reserveScriptEnv()) { + return -1; + } + + ScriptEnvironment* env = getScriptEnv(); + env->setScriptId(EVENT_ID_LOADING, this); + env->setNpc(npc); + + //execute it + ret = protectedCall(luaState, 0, 0); + if (ret != 0) { + reportError(nullptr, popString(luaState)); + resetScriptEnv(); + return -1; + } + + resetScriptEnv(); + return 0; +} + +int32_t LuaScriptInterface::getEvent(const std::string& eventName) +{ + //get our events table + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return -1; + } + + //get current event function pointer + lua_getglobal(luaState, eventName.c_str()); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 2); + return -1; + } + + //save in our events table + lua_pushvalue(luaState, -1); + lua_rawseti(luaState, -3, runningEventId); + lua_pop(luaState, 2); + + //reset global value of this event + lua_pushnil(luaState); + lua_setglobal(luaState, eventName.c_str()); + + cacheFiles[runningEventId] = loadingFile + ":" + eventName; + return runningEventId++; +} + +int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const std::string& eventName) +{ + //get our events table + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return -1; + } + + //get current event function pointer + lua_getglobal(luaState, globalName.c_str()); + lua_getfield(luaState, -1, eventName.c_str()); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 3); + return -1; + } + + //save in our events table + lua_pushvalue(luaState, -1); + lua_rawseti(luaState, -4, runningEventId); + lua_pop(luaState, 1); + + //reset global value of this event + lua_pushnil(luaState); + lua_setfield(luaState, -2, eventName.c_str()); + lua_pop(luaState, 2); + + cacheFiles[runningEventId] = loadingFile + ":" + globalName + "@" + eventName; + return runningEventId++; +} + +const std::string& LuaScriptInterface::getFileById(int32_t scriptId) +{ + if (scriptId == EVENT_ID_LOADING) { + return loadingFile; + } + + auto it = cacheFiles.find(scriptId); + if (it == cacheFiles.end()) { + static const std::string& unk = "(Unknown scriptfile)"; + return unk; + } + return it->second; +} + +std::string LuaScriptInterface::getStackTrace(const std::string& error_desc) +{ + lua_getglobal(luaState, "debug"); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return error_desc; + } + + lua_getfield(luaState, -1, "traceback"); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 2); + return error_desc; + } + + lua_replace(luaState, -2); + pushString(luaState, error_desc); + lua_call(luaState, 1, 1); + return popString(luaState); +} + +void LuaScriptInterface::reportError(const char* function, const std::string& error_desc, bool stack_trace/* = false*/) +{ + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + LuaScriptInterface* scriptInterface; + getScriptEnv()->getEventInfo(scriptId, scriptInterface, callbackId, timerEvent); + + std::cout << std::endl << "Lua Script Error: "; + + if (scriptInterface) { + std::cout << '[' << scriptInterface->getInterfaceName() << "] " << std::endl; + + if (timerEvent) { + std::cout << "in a timer event called from: " << std::endl; + } + + if (callbackId) { + std::cout << "in callback: " << scriptInterface->getFileById(callbackId) << std::endl; + } + + std::cout << scriptInterface->getFileById(scriptId) << std::endl; + } + + if (function) { + std::cout << function << "(). "; + } + + if (stack_trace && scriptInterface) { + std::cout << scriptInterface->getStackTrace(error_desc) << std::endl; + } else { + std::cout << error_desc << std::endl; + } +} + +bool LuaScriptInterface::pushFunction(int32_t functionId) +{ + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + return false; + } + + lua_rawgeti(luaState, -1, functionId); + lua_replace(luaState, -2); + return isFunction(luaState, -1); +} + +bool LuaScriptInterface::initState() +{ + luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return false; + } + + lua_newtable(luaState); + eventTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + runningEventId = EVENT_ID_USER; + return true; +} + +bool LuaScriptInterface::closeState() +{ + if (!g_luaEnvironment.getLuaState() || !luaState) { + return false; + } + + cacheFiles.clear(); + if (eventTableRef != -1) { + luaL_unref(luaState, LUA_REGISTRYINDEX, eventTableRef); + eventTableRef = -1; + } + + luaState = nullptr; + return true; +} + +int LuaScriptInterface::luaErrorHandler(lua_State* L) +{ + const std::string& errorMessage = popString(L); + auto interface = getScriptEnv()->getScriptInterface(); + assert(interface); //This fires if the ScriptEnvironment hasn't been setup + pushString(L, interface->getStackTrace(errorMessage)); + return 1; +} + +bool LuaScriptInterface::callFunction(int params) +{ + bool result = false; + int size = lua_gettop(luaState); + if (protectedCall(luaState, params, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::getString(luaState, -1)); + } else { + result = LuaScriptInterface::getBoolean(luaState, -1); + } + + lua_pop(luaState, 1); + if ((lua_gettop(luaState) + params + 1) != size) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + resetScriptEnv(); + return result; +} + +void LuaScriptInterface::callVoidFunction(int params) +{ + int size = lua_gettop(luaState); + if (protectedCall(luaState, params, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(luaState)); + } + + if ((lua_gettop(luaState) + params + 1) != size) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + resetScriptEnv(); +} + +void LuaScriptInterface::pushVariant(lua_State* L, const LuaVariant& var) +{ + lua_createtable(L, 0, 2); + setField(L, "type", var.type); + switch (var.type) { + case VARIANT_NUMBER: + setField(L, "number", var.number); + break; + case VARIANT_STRING: + setField(L, "string", var.text); + break; + case VARIANT_TARGETPOSITION: + case VARIANT_POSITION: { + pushPosition(L, var.pos); + lua_setfield(L, -2, "pos"); + break; + } + default: + break; + } + setMetatable(L, -1, "Variant"); +} + +void LuaScriptInterface::pushThing(lua_State* L, Thing* thing) +{ + if (!thing) { + lua_createtable(L, 0, 4); + setField(L, "uid", 0); + setField(L, "itemid", 0); + setField(L, "actionid", 0); + setField(L, "type", 0); + return; + } + + if (Item* item = thing->getItem()) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else if (Creature* creature = thing->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } +} + +void LuaScriptInterface::pushCylinder(lua_State* L, Cylinder* cylinder) +{ + if (Creature* creature = cylinder->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else if (Item* parentItem = cylinder->getItem()) { + pushUserdata(L, parentItem); + setItemMetatable(L, -1, parentItem); + } else if (Tile* tile = cylinder->getTile()) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else if (cylinder == VirtualCylinder::virtualCylinder) { + pushBoolean(L, true); + } else { + lua_pushnil(L); + } +} + +void LuaScriptInterface::pushString(lua_State* L, const std::string& value) +{ + lua_pushlstring(L, value.c_str(), value.length()); +} + +void LuaScriptInterface::pushCallback(lua_State* L, int32_t callback) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); +} + +std::string LuaScriptInterface::popString(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return std::string(); + } + + std::string str(getString(L, -1)); + lua_pop(L, 1); + return str; +} + +int32_t LuaScriptInterface::popCallback(lua_State* L) +{ + return luaL_ref(L, LUA_REGISTRYINDEX); +} + +// Metatables +void LuaScriptInterface::setMetatable(lua_State* L, int32_t index, const std::string& name) +{ + luaL_getmetatable(L, name.c_str()); + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setWeakMetatable(lua_State* L, int32_t index, const std::string& name) +{ + static std::set weakObjectTypes; + const std::string& weakName = name + "_weak"; + + auto result = weakObjectTypes.emplace(name); + if (result.second) { + luaL_getmetatable(L, name.c_str()); + int childMetatable = lua_gettop(L); + + luaL_newmetatable(L, weakName.c_str()); + int metatable = lua_gettop(L); + + static const std::vector methodKeys = {"__index", "__metatable", "__eq"}; + for (const std::string& metaKey : methodKeys) { + lua_getfield(L, childMetatable, metaKey.c_str()); + lua_setfield(L, metatable, metaKey.c_str()); + } + + static const std::vector methodIndexes = {'h', 'p', 't'}; + for (int metaIndex : methodIndexes) { + lua_rawgeti(L, childMetatable, metaIndex); + lua_rawseti(L, metatable, metaIndex); + } + + lua_pushnil(L); + lua_setfield(L, metatable, "__gc"); + + lua_remove(L, childMetatable); + } else { + luaL_getmetatable(L, weakName.c_str()); + } + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setItemMetatable(lua_State* L, int32_t index, const Item* item) +{ + if (item->getContainer()) { + luaL_getmetatable(L, "Container"); + } else if (item->getTeleport()) { + luaL_getmetatable(L, "Teleport"); + } else { + luaL_getmetatable(L, "Item"); + } + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature) +{ + if (creature->getPlayer()) { + luaL_getmetatable(L, "Player"); + } else if (creature->getMonster()) { + luaL_getmetatable(L, "Monster"); + } else { + luaL_getmetatable(L, "Npc"); + } + lua_setmetatable(L, index - 1); +} + +// Get +CombatDamage LuaScriptInterface::getCombatDamage(lua_State* L) +{ + CombatDamage damage; + damage.value = getNumber(L, -4); + damage.type = getNumber(L, -3); + damage.min = getNumber(L, -2); + damage.max = getNumber(L, -1); + + lua_pop(L, 4); + return damage; +} + +std::string LuaScriptInterface::getString(lua_State* L, int32_t arg) +{ + size_t len; + const char* c_str = lua_tolstring(L, arg, &len); + if (!c_str || len == 0) { + return std::string(); + } + return std::string(c_str, len); +} + +Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg, int32_t& stackpos) +{ + Position position; + position.x = getField(L, arg, "x"); + position.y = getField(L, arg, "y"); + position.z = getField(L, arg, "z"); + + lua_getfield(L, arg, "stackpos"); + if (lua_isnil(L, -1) == 1) { + stackpos = 0; + } else { + stackpos = getNumber(L, -1); + } + + lua_pop(L, 4); + return position; +} + +Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) +{ + Position position; + position.x = getField(L, arg, "x"); + position.y = getField(L, arg, "y"); + position.z = getField(L, arg, "z"); + + lua_pop(L, 3); + return position; +} + +Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) +{ + Outfit_t outfit; + outfit.lookAddons = getField(L, arg, "lookAddons"); + + outfit.lookFeet = getField(L, arg, "lookFeet"); + outfit.lookLegs = getField(L, arg, "lookLegs"); + outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookHead = getField(L, arg, "lookHead"); + + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookType = getField(L, arg, "lookType"); + + lua_pop(L, 6); + return outfit; +} + +LuaVariant LuaScriptInterface::getVariant(lua_State* L, int32_t arg) +{ + LuaVariant var; + switch (var.type = getField(L, arg, "type")) { + case VARIANT_NUMBER: { + var.number = getField(L, arg, "number"); + lua_pop(L, 2); + break; + } + + case VARIANT_STRING: { + var.text = getFieldString(L, arg, "string"); + lua_pop(L, 2); + break; + } + + case VARIANT_POSITION: + case VARIANT_TARGETPOSITION: { + lua_getfield(L, arg, "pos"); + var.pos = getPosition(L, lua_gettop(L)); + lua_pop(L, 2); + break; + } + + default: { + var.type = VARIANT_NONE; + lua_pop(L, 1); + break; + } + } + return var; +} + +Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) +{ + Thing* thing; + if (lua_getmetatable(L, arg) != 0) { + lua_rawgeti(L, -1, 't'); + switch(getNumber(L, -1)) { + case LuaData_Item: + thing = getUserdata(L, arg); + break; + case LuaData_Container: + thing = getUserdata(L, arg); + break; + case LuaData_Teleport: + thing = getUserdata(L, arg); + break; + case LuaData_Player: + thing = getUserdata(L, arg); + break; + case LuaData_Monster: + thing = getUserdata(L, arg); + break; + case LuaData_Npc: + thing = getUserdata(L, arg); + break; + default: + thing = nullptr; + break; + } + lua_pop(L, 2); + } else { + thing = getScriptEnv()->getThingByUID(getNumber(L, arg)); + } + return thing; +} + +Creature* LuaScriptInterface::getCreature(lua_State* L, int32_t arg) +{ + if (isUserdata(L, arg)) { + return getUserdata(L, arg); + } + return g_game.getCreatureByID(getNumber(L, arg)); +} + +Player* LuaScriptInterface::getPlayer(lua_State* L, int32_t arg) +{ + if (isUserdata(L, arg)) { + return getUserdata(L, arg); + } + return g_game.getPlayerByID(getNumber(L, arg)); +} + +std::string LuaScriptInterface::getFieldString(lua_State* L, int32_t arg, const std::string& key) +{ + lua_getfield(L, arg, key.c_str()); + return getString(L, -1); +} + +LuaDataType LuaScriptInterface::getUserdataType(lua_State* L, int32_t arg) +{ + if (lua_getmetatable(L, arg) == 0) { + return LuaData_Unknown; + } + lua_rawgeti(L, -1, 't'); + + LuaDataType type = getNumber(L, -1); + lua_pop(L, 2); + + return type; +} + +// Push +void LuaScriptInterface::pushBoolean(lua_State* L, bool value) +{ + lua_pushboolean(L, value ? 1 : 0); +} + +void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& damage) +{ + lua_pushnumber(L, damage.value); + lua_pushnumber(L, damage.type); + lua_pushnumber(L, damage.min); + lua_pushnumber(L, damage.max); + lua_pushnumber(L, damage.origin); +} + + +void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos/* = 0*/) +{ + lua_createtable(L, 0, 4); + + setField(L, "x", position.x); + setField(L, "y", position.y); + setField(L, "z", position.z); + setField(L, "stackpos", stackpos); + + setMetatable(L, -1, "Position"); +} + +void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) +{ + lua_createtable(L, 0, 6); + setField(L, "lookType", outfit.lookType); + setField(L, "lookTypeEx", outfit.lookTypeEx); + setField(L, "lookHead", outfit.lookHead); + setField(L, "lookBody", outfit.lookBody); + setField(L, "lookLegs", outfit.lookLegs); + setField(L, "lookFeet", outfit.lookFeet); + setField(L, "lookAddons", outfit.lookAddons); +} + +#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } +#define registerEnumIn(tableName, value) { std::string enumName = #value; registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); } + +void LuaScriptInterface::registerFunctions() +{ + //getPlayerFlagValue(cid, flag) + lua_register(luaState, "getPlayerFlagValue", LuaScriptInterface::luaGetPlayerFlagValue); + + //getPlayerInstantSpellCount(cid) + lua_register(luaState, "getPlayerInstantSpellCount", LuaScriptInterface::luaGetPlayerInstantSpellCount); + + //getPlayerInstantSpellInfo(cid, index) + lua_register(luaState, "getPlayerInstantSpellInfo", LuaScriptInterface::luaGetPlayerInstantSpellInfo); + + //doPlayerAddItem(uid, itemid, count/subtype) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + //Returns uid of the created item + lua_register(luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); + + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + lua_register(luaState, "doCreateItem", LuaScriptInterface::luaDoCreateItem); + + //doCreateItemEx(itemid, count/subtype) + lua_register(luaState, "doCreateItemEx", LuaScriptInterface::luaDoCreateItemEx); + + //doTileAddItemEx(pos, uid) + lua_register(luaState, "doTileAddItemEx", LuaScriptInterface::luaDoTileAddItemEx); + + //doMoveCreature(cid, direction) + lua_register(luaState, "doMoveCreature", LuaScriptInterface::luaDoMoveCreature); + + //doSetCreatureLight(cid, lightLevel, lightColor, time) + lua_register(luaState, "doSetCreatureLight", LuaScriptInterface::luaDoSetCreatureLight); + + //getCreatureCondition(cid, condition[, subId]) + lua_register(luaState, "getCreatureCondition", LuaScriptInterface::luaGetCreatureCondition); + + //isValidUID(uid) + lua_register(luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); + + //isDepot(uid) + lua_register(luaState, "isDepot", LuaScriptInterface::luaIsDepot); + + //isMovable(uid) + lua_register(luaState, "isMovable", LuaScriptInterface::luaIsMoveable); + + //doAddContainerItem(uid, itemid, count/subtype) + lua_register(luaState, "doAddContainerItem", LuaScriptInterface::luaDoAddContainerItem); + + //getDepotId(uid) + lua_register(luaState, "getDepotId", LuaScriptInterface::luaGetDepotId); + + //getWorldTime() + lua_register(luaState, "getWorldTime", LuaScriptInterface::luaGetWorldTime); + + //getWorldLight() + lua_register(luaState, "getWorldLight", LuaScriptInterface::luaGetWorldLight); + + //getWorldUpTime() + lua_register(luaState, "getWorldUpTime", LuaScriptInterface::luaGetWorldUpTime); + + //createCombatArea( {area}, {extArea} ) + lua_register(luaState, "createCombatArea", LuaScriptInterface::luaCreateCombatArea); + + //doAreaCombatHealth(cid, type, pos, area, min, max, effect) + lua_register(luaState, "doAreaCombatHealth", LuaScriptInterface::luaDoAreaCombatHealth); + + //doTargetCombatHealth(cid, target, type, min, max, effect) + lua_register(luaState, "doTargetCombatHealth", LuaScriptInterface::luaDoTargetCombatHealth); + + //doAreaCombatMana(cid, pos, area, min, max, effect) + lua_register(luaState, "doAreaCombatMana", LuaScriptInterface::luaDoAreaCombatMana); + + //doTargetCombatMana(cid, target, min, max, effect) + lua_register(luaState, "doTargetCombatMana", LuaScriptInterface::luaDoTargetCombatMana); + + //doAreaCombatCondition(cid, pos, area, condition, effect) + lua_register(luaState, "doAreaCombatCondition", LuaScriptInterface::luaDoAreaCombatCondition); + + //doTargetCombatCondition(cid, target, condition, effect) + lua_register(luaState, "doTargetCombatCondition", LuaScriptInterface::luaDoTargetCombatCondition); + + //doAreaCombatDispel(cid, pos, area, type, effect) + lua_register(luaState, "doAreaCombatDispel", LuaScriptInterface::luaDoAreaCombatDispel); + + //doTargetCombatDispel(cid, target, type, effect) + lua_register(luaState, "doTargetCombatDispel", LuaScriptInterface::luaDoTargetCombatDispel); + + //doChallengeCreature(cid, target) + lua_register(luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); + + //doSetMonsterOutfit(cid, name, time) + lua_register(luaState, "doSetMonsterOutfit", LuaScriptInterface::luaSetMonsterOutfit); + + //doSetItemOutfit(cid, item, time) + lua_register(luaState, "doSetItemOutfit", LuaScriptInterface::luaSetItemOutfit); + + //doSetCreatureOutfit(cid, outfit, time) + lua_register(luaState, "doSetCreatureOutfit", LuaScriptInterface::luaSetCreatureOutfit); + + //isInArray(array, value) + lua_register(luaState, "isInArray", LuaScriptInterface::luaIsInArray); + + //addEvent(callback, delay, ...) + lua_register(luaState, "addEvent", LuaScriptInterface::luaAddEvent); + + //stopEvent(eventid) + lua_register(luaState, "stopEvent", LuaScriptInterface::luaStopEvent); + + //saveServer() + lua_register(luaState, "saveServer", LuaScriptInterface::luaSaveServer); + + //cleanMap() + lua_register(luaState, "cleanMap", LuaScriptInterface::luaCleanMap); + + //debugPrint(text) + lua_register(luaState, "debugPrint", LuaScriptInterface::luaDebugPrint); + + //isInWar(cid, target) + lua_register(luaState, "isInWar", LuaScriptInterface::luaIsInWar); + + //getWaypointPosition(name) + lua_register(luaState, "getWaypointPositionByName", LuaScriptInterface::luaGetWaypointPositionByName); + + //sendChannelMessage(channelId, type, message) + lua_register(luaState, "sendChannelMessage", LuaScriptInterface::luaSendChannelMessage); + + //sendGuildChannelMessage(guildId, type, message) + lua_register(luaState, "sendGuildChannelMessage", LuaScriptInterface::luaSendGuildChannelMessage); + +#ifndef LUAJIT_VERSION + //bit operations for Lua, based on bitlib project release 24 + //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + luaL_register(luaState, "bit", LuaScriptInterface::luaBitReg); +#endif + + //configManager table + luaL_register(luaState, "configManager", LuaScriptInterface::luaConfigManagerTable); + + //db table + luaL_register(luaState, "db", LuaScriptInterface::luaDatabaseTable); + + //result table + luaL_register(luaState, "result", LuaScriptInterface::luaResultTable); + + /* New functions */ + //registerClass(className, baseClass, newFunction) + //registerTable(tableName) + //registerMethod(className, functionName, function) + //registerMetaMethod(className, functionName, function) + //registerGlobalMethod(functionName, function) + //registerVariable(tableName, name, value) + //registerGlobalVariable(name, value) + //registerEnum(value) + //registerEnumIn(tableName, value) + + // Enums + registerEnum(ACCOUNT_TYPE_NORMAL) + registerEnum(ACCOUNT_TYPE_TUTOR) + registerEnum(ACCOUNT_TYPE_SENIORTUTOR) + registerEnum(ACCOUNT_TYPE_GAMEMASTER) + registerEnum(ACCOUNT_TYPE_GOD) + + registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE) + registerEnum(CALLBACK_PARAM_SKILLVALUE) + registerEnum(CALLBACK_PARAM_TARGETTILE) + registerEnum(CALLBACK_PARAM_TARGETCREATURE) + + registerEnum(COMBAT_FORMULA_UNDEFINED) + registerEnum(COMBAT_FORMULA_LEVELMAGIC) + registerEnum(COMBAT_FORMULA_SKILL) + registerEnum(COMBAT_FORMULA_DAMAGE) + + registerEnum(DIRECTION_NORTH) + registerEnum(DIRECTION_EAST) + registerEnum(DIRECTION_SOUTH) + registerEnum(DIRECTION_WEST) + registerEnum(DIRECTION_SOUTHWEST) + registerEnum(DIRECTION_SOUTHEAST) + registerEnum(DIRECTION_NORTHWEST) + registerEnum(DIRECTION_NORTHEAST) + + registerEnum(COMBAT_NONE) + registerEnum(COMBAT_PHYSICALDAMAGE) + registerEnum(COMBAT_ENERGYDAMAGE) + registerEnum(COMBAT_EARTHDAMAGE) + registerEnum(COMBAT_FIREDAMAGE) + registerEnum(COMBAT_DROWNDAMAGE) + registerEnum(COMBAT_UNDEFINEDDAMAGE) + registerEnum(COMBAT_LIFEDRAIN) + registerEnum(COMBAT_MANADRAIN) + registerEnum(COMBAT_HEALING) + + registerEnum(COMBAT_PARAM_TYPE) + registerEnum(COMBAT_PARAM_EFFECT) + registerEnum(COMBAT_PARAM_DISTANCEEFFECT) + registerEnum(COMBAT_PARAM_BLOCKSHIELD) + registerEnum(COMBAT_PARAM_BLOCKARMOR) + registerEnum(COMBAT_PARAM_TARGETCASTERORTOPMOST) + registerEnum(COMBAT_PARAM_CREATEITEM) + registerEnum(COMBAT_PARAM_AGGRESSIVE) + registerEnum(COMBAT_PARAM_DISPEL) + registerEnum(COMBAT_PARAM_USECHARGES) + registerEnum(COMBAT_PARAM_DECREASEDAMAGE) + registerEnum(COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE) + + registerEnum(CONDITION_NONE) + registerEnum(CONDITION_POISON) + registerEnum(CONDITION_FIRE) + registerEnum(CONDITION_ENERGY) + registerEnum(CONDITION_DROWN) + registerEnum(CONDITION_HASTE) + registerEnum(CONDITION_PARALYZE) + registerEnum(CONDITION_OUTFIT) + registerEnum(CONDITION_INVISIBLE) + registerEnum(CONDITION_LIGHT) + registerEnum(CONDITION_MANASHIELD) + registerEnum(CONDITION_INFIGHT) + registerEnum(CONDITION_DRUNK) + registerEnum(CONDITION_REGENERATION) + registerEnum(CONDITION_SOUL) + registerEnum(CONDITION_MUTED) + registerEnum(CONDITION_CHANNELMUTEDTICKS) + registerEnum(CONDITION_YELLTICKS) + registerEnum(CONDITION_ATTRIBUTES) + registerEnum(CONDITION_EXHAUST) + registerEnum(CONDITION_PACIFIED) + + registerEnum(CONDITIONID_DEFAULT) + registerEnum(CONDITIONID_COMBAT) + registerEnum(CONDITIONID_HEAD) + registerEnum(CONDITIONID_NECKLACE) + registerEnum(CONDITIONID_BACKPACK) + registerEnum(CONDITIONID_ARMOR) + registerEnum(CONDITIONID_RIGHT) + registerEnum(CONDITIONID_LEFT) + registerEnum(CONDITIONID_LEGS) + registerEnum(CONDITIONID_FEET) + registerEnum(CONDITIONID_RING) + registerEnum(CONDITIONID_AMMO) + + registerEnum(CONDITION_PARAM_OWNER) + registerEnum(CONDITION_PARAM_TICKS) + registerEnum(CONDITION_PARAM_HEALTHGAIN) + registerEnum(CONDITION_PARAM_HEALTHTICKS) + registerEnum(CONDITION_PARAM_MANAGAIN) + registerEnum(CONDITION_PARAM_MANATICKS) + registerEnum(CONDITION_PARAM_DELAYED) + registerEnum(CONDITION_PARAM_SPEED) + registerEnum(CONDITION_PARAM_LIGHT_LEVEL) + registerEnum(CONDITION_PARAM_LIGHT_COLOR) + registerEnum(CONDITION_PARAM_SOULGAIN) + registerEnum(CONDITION_PARAM_SOULTICKS) + registerEnum(CONDITION_PARAM_MINVALUE) + registerEnum(CONDITION_PARAM_MAXVALUE) + registerEnum(CONDITION_PARAM_STARTVALUE) + registerEnum(CONDITION_PARAM_TICKINTERVAL) + registerEnum(CONDITION_PARAM_SKILL_MELEE) + registerEnum(CONDITION_PARAM_SKILL_FIST) + registerEnum(CONDITION_PARAM_SKILL_CLUB) + registerEnum(CONDITION_PARAM_SKILL_SWORD) + registerEnum(CONDITION_PARAM_SKILL_AXE) + registerEnum(CONDITION_PARAM_SKILL_DISTANCE) + registerEnum(CONDITION_PARAM_SKILL_SHIELD) + registerEnum(CONDITION_PARAM_SKILL_FISHING) + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT) + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT) + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT) + registerEnum(CONDITION_PARAM_PERIODICDAMAGE) + registerEnum(CONDITION_PARAM_HIT_DAMAGE) + registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT) + registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT) + registerEnum(CONDITION_PARAM_SKILL_SWORDPERCENT) + registerEnum(CONDITION_PARAM_SKILL_AXEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT) + registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT) + registerEnum(CONDITION_PARAM_SUBID) + registerEnum(CONDITION_PARAM_FIELD) + + registerEnum(CONST_ME_NONE) + registerEnum(CONST_ME_DRAWBLOOD) + registerEnum(CONST_ME_LOSEENERGY) + registerEnum(CONST_ME_POFF) + registerEnum(CONST_ME_BLOCKHIT) + registerEnum(CONST_ME_EXPLOSIONAREA) + registerEnum(CONST_ME_EXPLOSIONHIT) + registerEnum(CONST_ME_FIREAREA) + registerEnum(CONST_ME_YELLOW_RINGS) + registerEnum(CONST_ME_GREEN_RINGS) + registerEnum(CONST_ME_HITAREA) + registerEnum(CONST_ME_TELEPORT) + registerEnum(CONST_ME_ENERGYHIT) + registerEnum(CONST_ME_MAGIC_BLUE) + registerEnum(CONST_ME_MAGIC_RED) + registerEnum(CONST_ME_MAGIC_GREEN) + registerEnum(CONST_ME_HITBYFIRE) + registerEnum(CONST_ME_HITBYPOISON) + registerEnum(CONST_ME_MORTAREA) + registerEnum(CONST_ME_SOUND_GREEN) + registerEnum(CONST_ME_SOUND_RED) + registerEnum(CONST_ME_POISONAREA) + registerEnum(CONST_ME_SOUND_YELLOW) + registerEnum(CONST_ME_SOUND_PURPLE) + registerEnum(CONST_ME_SOUND_BLUE) + registerEnum(CONST_ME_SOUND_WHITE) + registerEnum(CONST_ME_BUBBLES) + registerEnum(CONST_ME_CRAPS) + registerEnum(CONST_ME_GIFT_WRAPS) + registerEnum(CONST_ME_FIREWORK_YELLOW) + registerEnum(CONST_ME_FIREWORK_RED) + registerEnum(CONST_ME_FIREWORK_BLUE) + registerEnum(CONST_ANI_NONE) + registerEnum(CONST_ANI_SPEAR) + registerEnum(CONST_ANI_BOLT) + registerEnum(CONST_ANI_ARROW) + registerEnum(CONST_ANI_FIRE) + registerEnum(CONST_ANI_ENERGY) + registerEnum(CONST_ANI_POISONARROW) + registerEnum(CONST_ANI_BURSTARROW) + registerEnum(CONST_ANI_THROWINGSTAR) + registerEnum(CONST_ANI_THROWINGKNIFE) + registerEnum(CONST_ANI_SMALLSTONE) + registerEnum(CONST_ANI_DEATH) + registerEnum(CONST_ANI_LARGEROCK) + registerEnum(CONST_ANI_SNOWBALL) + registerEnum(CONST_ANI_POWERBOLT) + registerEnum(CONST_ANI_POISON) + registerEnum(CONST_ANI_INFERNALBOLT) + + registerEnum(CONST_PROP_BLOCKSOLID) + registerEnum(CONST_PROP_HASHEIGHT) + registerEnum(CONST_PROP_BLOCKPROJECTILE) + registerEnum(CONST_PROP_BLOCKPATH) + registerEnum(CONST_PROP_ISVERTICAL) + registerEnum(CONST_PROP_ISHORIZONTAL) + registerEnum(CONST_PROP_MOVEABLE) + registerEnum(CONST_PROP_IMMOVABLEBLOCKSOLID) + registerEnum(CONST_PROP_IMMOVABLEBLOCKPATH) + registerEnum(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) + registerEnum(CONST_PROP_NOFIELDBLOCKPATH) + registerEnum(CONST_PROP_SUPPORTHANGABLE) + + registerEnum(CONST_SLOT_HEAD) + registerEnum(CONST_SLOT_NECKLACE) + registerEnum(CONST_SLOT_BACKPACK) + registerEnum(CONST_SLOT_ARMOR) + registerEnum(CONST_SLOT_RIGHT) + registerEnum(CONST_SLOT_LEFT) + registerEnum(CONST_SLOT_LEGS) + registerEnum(CONST_SLOT_FEET) + registerEnum(CONST_SLOT_RING) + registerEnum(CONST_SLOT_AMMO) + + registerEnum(CREATURE_EVENT_NONE) + registerEnum(CREATURE_EVENT_LOGIN) + registerEnum(CREATURE_EVENT_LOGOUT) + registerEnum(CREATURE_EVENT_THINK) + registerEnum(CREATURE_EVENT_PREPAREDEATH) + registerEnum(CREATURE_EVENT_DEATH) + registerEnum(CREATURE_EVENT_KILL) + registerEnum(CREATURE_EVENT_ADVANCE) + registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) + + registerEnum(GAME_STATE_STARTUP) + registerEnum(GAME_STATE_INIT) + registerEnum(GAME_STATE_NORMAL) + registerEnum(GAME_STATE_CLOSED) + registerEnum(GAME_STATE_SHUTDOWN) + registerEnum(GAME_STATE_CLOSING) + registerEnum(GAME_STATE_MAINTAIN) + + registerEnum(MESSAGE_STATUS_CONSOLE_BLUE) + registerEnum(MESSAGE_STATUS_CONSOLE_RED) + registerEnum(MESSAGE_STATUS_DEFAULT) + registerEnum(MESSAGE_STATUS_WARNING) + registerEnum(MESSAGE_EVENT_ADVANCE) + registerEnum(MESSAGE_STATUS_SMALL) + registerEnum(MESSAGE_INFO_DESCR) + registerEnum(MESSAGE_EVENT_DEFAULT) + registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) + + registerEnum(CLIENTOS_LINUX) + registerEnum(CLIENTOS_WINDOWS) + registerEnum(CLIENTOS_FLASH) + registerEnum(CLIENTOS_OTCLIENT_LINUX) + registerEnum(CLIENTOS_OTCLIENT_WINDOWS) + registerEnum(CLIENTOS_OTCLIENT_MAC) + + registerEnum(ITEM_ATTRIBUTE_NONE) + registerEnum(ITEM_ATTRIBUTE_ACTIONID) + registerEnum(ITEM_ATTRIBUTE_MOVEMENTID) + registerEnum(ITEM_ATTRIBUTE_DESCRIPTION) + registerEnum(ITEM_ATTRIBUTE_TEXT) + registerEnum(ITEM_ATTRIBUTE_DATE) + registerEnum(ITEM_ATTRIBUTE_WRITER) + registerEnum(ITEM_ATTRIBUTE_NAME) + registerEnum(ITEM_ATTRIBUTE_ARTICLE) + registerEnum(ITEM_ATTRIBUTE_PLURALNAME) + registerEnum(ITEM_ATTRIBUTE_WEIGHT) + registerEnum(ITEM_ATTRIBUTE_ATTACK) + registerEnum(ITEM_ATTRIBUTE_DEFENSE) + registerEnum(ITEM_ATTRIBUTE_ARMOR) + registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE) + registerEnum(ITEM_ATTRIBUTE_OWNER) + registerEnum(ITEM_ATTRIBUTE_DURATION) + registerEnum(ITEM_ATTRIBUTE_DECAYSTATE) + registerEnum(ITEM_ATTRIBUTE_CORPSEOWNER) + registerEnum(ITEM_ATTRIBUTE_CHARGES) + registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE) + registerEnum(ITEM_ATTRIBUTE_DOORID) + registerEnum(ITEM_ATTRIBUTE_KEYNUMBER) + registerEnum(ITEM_ATTRIBUTE_KEYHOLENUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTNUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTVALUE) + registerEnum(ITEM_ATTRIBUTE_DOORLEVEL) + registerEnum(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) + + registerEnum(ITEM_TYPE_DEPOT) + registerEnum(ITEM_TYPE_MAILBOX) + registerEnum(ITEM_TYPE_CONTAINER) + registerEnum(ITEM_TYPE_DOOR) + registerEnum(ITEM_TYPE_MAGICFIELD) + registerEnum(ITEM_TYPE_TELEPORT) + registerEnum(ITEM_TYPE_BED) + registerEnum(ITEM_TYPE_KEY) + registerEnum(ITEM_TYPE_RUNE) + registerEnum(ITEM_TYPE_CHEST) + + registerEnum(ITEM_GOLD_COIN) + registerEnum(ITEM_PLATINUM_COIN) + registerEnum(ITEM_CRYSTAL_COIN) + registerEnum(ITEM_AMULETOFLOSS) + registerEnum(ITEM_PARCEL) + registerEnum(ITEM_LABEL) + registerEnum(ITEM_FIREFIELD_PVP_FULL) + registerEnum(ITEM_FIREFIELD_PVP_MEDIUM) + registerEnum(ITEM_FIREFIELD_PVP_SMALL) + registerEnum(ITEM_FIREFIELD_PERSISTENT_FULL) + registerEnum(ITEM_FIREFIELD_PERSISTENT_MEDIUM) + registerEnum(ITEM_FIREFIELD_PERSISTENT_SMALL) + registerEnum(ITEM_FIREFIELD_NOPVP) + registerEnum(ITEM_POISONFIELD_PVP) + registerEnum(ITEM_POISONFIELD_PERSISTENT) + registerEnum(ITEM_POISONFIELD_NOPVP) + registerEnum(ITEM_ENERGYFIELD_PVP) + registerEnum(ITEM_ENERGYFIELD_PERSISTENT) + registerEnum(ITEM_ENERGYFIELD_NOPVP) + registerEnum(ITEM_MAGICWALL) + registerEnum(ITEM_MAGICWALL_PERSISTENT) + registerEnum(ITEM_WILDGROWTH) + registerEnum(ITEM_WILDGROWTH_PERSISTENT) + + registerEnum(PlayerFlag_CannotUseCombat) + registerEnum(PlayerFlag_CannotAttackPlayer) + registerEnum(PlayerFlag_CannotAttackMonster) + registerEnum(PlayerFlag_CannotBeAttacked) + registerEnum(PlayerFlag_CanConvinceAll) + registerEnum(PlayerFlag_CanSummonAll) + registerEnum(PlayerFlag_CanIllusionAll) + registerEnum(PlayerFlag_CanSenseInvisibility) + registerEnum(PlayerFlag_IgnoredByMonsters) + registerEnum(PlayerFlag_NotGainInFight) + registerEnum(PlayerFlag_HasInfiniteMana) + registerEnum(PlayerFlag_HasInfiniteSoul) + registerEnum(PlayerFlag_HasNoExhaustion) + registerEnum(PlayerFlag_CannotUseSpells) + registerEnum(PlayerFlag_CannotPickupItem) + registerEnum(PlayerFlag_CanAlwaysLogin) + registerEnum(PlayerFlag_CanBroadcast) + registerEnum(PlayerFlag_CanEditHouses) + registerEnum(PlayerFlag_CannotBeBanned) + registerEnum(PlayerFlag_CannotBePushed) + registerEnum(PlayerFlag_HasInfiniteCapacity) + registerEnum(PlayerFlag_CanPushAllCreatures) + registerEnum(PlayerFlag_CanTalkRedPrivate) + registerEnum(PlayerFlag_CanTalkRedChannel) + registerEnum(PlayerFlag_TalkOrangeHelpChannel) + registerEnum(PlayerFlag_NotGainExperience) + registerEnum(PlayerFlag_NotGainMana) + registerEnum(PlayerFlag_NotGainHealth) + registerEnum(PlayerFlag_NotGainSkill) + registerEnum(PlayerFlag_SetMaxSpeed) + registerEnum(PlayerFlag_SpecialVIP) + registerEnum(PlayerFlag_NotGenerateLoot) + registerEnum(PlayerFlag_CanTalkRedChannelAnonymous) + registerEnum(PlayerFlag_IgnoreProtectionZone) + registerEnum(PlayerFlag_IgnoreSpellCheck) + registerEnum(PlayerFlag_IgnoreWeaponCheck) + registerEnum(PlayerFlag_CannotBeMuted) + registerEnum(PlayerFlag_IsAlwaysPremium) + registerEnum(PlayerFlag_SpecialMoveUse) + + registerEnum(PLAYERSEX_FEMALE) + registerEnum(PLAYERSEX_MALE) + + registerEnum(VOCATION_NONE) + + registerEnum(FIGHTMODE_ATTACK) + registerEnum(FIGHTMODE_BALANCED) + registerEnum(FIGHTMODE_DEFENSE) + + registerEnum(SKILL_FIST) + registerEnum(SKILL_CLUB) + registerEnum(SKILL_SWORD) + registerEnum(SKILL_AXE) + registerEnum(SKILL_DISTANCE) + registerEnum(SKILL_SHIELD) + registerEnum(SKILL_FISHING) + registerEnum(SKILL_MAGLEVEL) + registerEnum(SKILL_LEVEL) + + registerEnum(SKULL_NONE) + registerEnum(SKULL_YELLOW) + registerEnum(SKULL_GREEN) + registerEnum(SKULL_WHITE) + registerEnum(SKULL_RED) + + registerEnum(FLUID_NONE) + registerEnum(FLUID_WATER) + registerEnum(FLUID_WINE) + registerEnum(FLUID_BEER) + registerEnum(FLUID_MUD) + registerEnum(FLUID_BLOOD) + registerEnum(FLUID_SLIME) + registerEnum(FLUID_OIL) + registerEnum(FLUID_URINE) + registerEnum(FLUID_MILK) + registerEnum(FLUID_MANAFLUID) + registerEnum(FLUID_LIFEFLUID) + registerEnum(FLUID_LEMONADE) + registerEnum(FLUID_RUM) + registerEnum(FLUID_COCONUTMILK) + registerEnum(FLUID_FRUITJUICE) + + registerEnum(TALKTYPE_SAY) + registerEnum(TALKTYPE_WHISPER) + registerEnum(TALKTYPE_YELL) + registerEnum(TALKTYPE_CHANNEL_Y) + registerEnum(TALKTYPE_CHANNEL_O) + registerEnum(TALKTYPE_BROADCAST) + registerEnum(TALKTYPE_CHANNEL_R1) + registerEnum(TALKTYPE_MONSTER_SAY) + registerEnum(TALKTYPE_MONSTER_YELL) + registerEnum(TALKTYPE_CHANNEL_R2) + + registerEnum(TEXTCOLOR_BLUE) + registerEnum(TEXTCOLOR_LIGHTGREEN) + registerEnum(TEXTCOLOR_LIGHTBLUE) + registerEnum(TEXTCOLOR_MAYABLUE) + registerEnum(TEXTCOLOR_DARKRED) + registerEnum(TEXTCOLOR_LIGHTGREY) + registerEnum(TEXTCOLOR_SKYBLUE) + registerEnum(TEXTCOLOR_PURPLE) + registerEnum(TEXTCOLOR_RED) + registerEnum(TEXTCOLOR_ORANGE) + registerEnum(TEXTCOLOR_YELLOW) + registerEnum(TEXTCOLOR_WHITE_EXP) + registerEnum(TEXTCOLOR_NONE) + + registerEnum(TILESTATE_NONE) + registerEnum(TILESTATE_PROTECTIONZONE) + registerEnum(TILESTATE_NOPVPZONE) + registerEnum(TILESTATE_NOLOGOUT) + registerEnum(TILESTATE_PVPZONE) + registerEnum(TILESTATE_REFRESH) + registerEnum(TILESTATE_TELEPORT) + registerEnum(TILESTATE_MAGICFIELD) + registerEnum(TILESTATE_MAILBOX) + registerEnum(TILESTATE_BED) + registerEnum(TILESTATE_DEPOT) + registerEnum(TILESTATE_BLOCKSOLID) + registerEnum(TILESTATE_BLOCKPATH) + registerEnum(TILESTATE_IMMOVABLEBLOCKSOLID) + registerEnum(TILESTATE_IMMOVABLEBLOCKPATH) + registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH) + registerEnum(TILESTATE_NOFIELDBLOCKPATH) + registerEnum(TILESTATE_SUPPORTS_HANGABLE) + + registerEnum(WEAPON_NONE) + registerEnum(WEAPON_SWORD) + registerEnum(WEAPON_CLUB) + registerEnum(WEAPON_AXE) + registerEnum(WEAPON_SHIELD) + registerEnum(WEAPON_DISTANCE) + registerEnum(WEAPON_WAND) + registerEnum(WEAPON_AMMO) + + registerEnum(WORLD_TYPE_NO_PVP) + registerEnum(WORLD_TYPE_PVP) + registerEnum(WORLD_TYPE_PVP_ENFORCED) + + // Use with container:addItem, container:addItemEx and possibly other functions. + registerEnum(FLAG_NOLIMIT) + registerEnum(FLAG_IGNOREBLOCKITEM) + registerEnum(FLAG_IGNOREBLOCKCREATURE) + registerEnum(FLAG_CHILDISOWNER) + registerEnum(FLAG_PATHFINDING) + registerEnum(FLAG_IGNOREFIELDDAMAGE) + registerEnum(FLAG_IGNORENOTMOVEABLE) + registerEnum(FLAG_IGNOREAUTOSTACK) + + // Use with itemType:getSlotPosition + registerEnum(SLOTP_WHEREEVER) + registerEnum(SLOTP_HEAD) + registerEnum(SLOTP_NECKLACE) + registerEnum(SLOTP_BACKPACK) + registerEnum(SLOTP_ARMOR) + registerEnum(SLOTP_RIGHT) + registerEnum(SLOTP_LEFT) + registerEnum(SLOTP_LEGS) + registerEnum(SLOTP_FEET) + registerEnum(SLOTP_RING) + registerEnum(SLOTP_AMMO) + registerEnum(SLOTP_DEPOT) + registerEnum(SLOTP_TWO_HAND) + + // Use with combat functions + registerEnum(ORIGIN_NONE) + registerEnum(ORIGIN_CONDITION) + registerEnum(ORIGIN_SPELL) + registerEnum(ORIGIN_MELEE) + registerEnum(ORIGIN_RANGED) + + // Use with house:getAccessList, house:setAccessList + registerEnum(GUEST_LIST) + registerEnum(SUBOWNER_LIST) + + // Use with Game.getReturnMessage + registerEnum(RETURNVALUE_NOERROR) + registerEnum(RETURNVALUE_NOTPOSSIBLE) + registerEnum(RETURNVALUE_NOTENOUGHROOM) + registerEnum(RETURNVALUE_PLAYERISPZLOCKED) + registerEnum(RETURNVALUE_PLAYERISNOTINVITED) + registerEnum(RETURNVALUE_CANNOTTHROW) + registerEnum(RETURNVALUE_THEREISNOWAY) + registerEnum(RETURNVALUE_DESTINATIONOUTOFREACH) + registerEnum(RETURNVALUE_CREATUREBLOCK) + registerEnum(RETURNVALUE_NOTMOVEABLE) + registerEnum(RETURNVALUE_DROPTWOHANDEDITEM) + registerEnum(RETURNVALUE_BOTHHANDSNEEDTOBEFREE) + registerEnum(RETURNVALUE_CANONLYUSEONEWEAPON) + registerEnum(RETURNVALUE_NEEDEXCHANGE) + registerEnum(RETURNVALUE_CANNOTBEDRESSED) + registerEnum(RETURNVALUE_PUTTHISOBJECTINYOURHAND) + registerEnum(RETURNVALUE_PUTTHISOBJECTINBOTHHANDS) + registerEnum(RETURNVALUE_TOOFARAWAY) + registerEnum(RETURNVALUE_FIRSTGODOWNSTAIRS) + registerEnum(RETURNVALUE_FIRSTGOUPSTAIRS) + registerEnum(RETURNVALUE_CONTAINERNOTENOUGHROOM) + registerEnum(RETURNVALUE_NOTENOUGHCAPACITY) + registerEnum(RETURNVALUE_CANNOTPICKUP) + registerEnum(RETURNVALUE_THISISIMPOSSIBLE) + registerEnum(RETURNVALUE_DEPOTISFULL) + registerEnum(RETURNVALUE_CREATUREDOESNOTEXIST) + registerEnum(RETURNVALUE_CANNOTUSETHISOBJECT) + registerEnum(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + registerEnum(RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE) + registerEnum(RETURNVALUE_YOUAREALREADYTRADING) + registerEnum(RETURNVALUE_THISPLAYERISALREADYTRADING) + registerEnum(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT) + registerEnum(RETURNVALUE_DIRECTPLAYERSHOOT) + registerEnum(RETURNVALUE_NOTENOUGHLEVEL) + registerEnum(RETURNVALUE_NOTENOUGHMAGICLEVEL) + registerEnum(RETURNVALUE_NOTENOUGHMANA) + registerEnum(RETURNVALUE_NOTENOUGHSOUL) + registerEnum(RETURNVALUE_YOUAREEXHAUSTED) + registerEnum(RETURNVALUE_PLAYERISNOTREACHABLE) + registerEnum(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES) + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE) + registerEnum(RETURNVALUE_YOUCANONLYUSEITONCREATURES) + registerEnum(RETURNVALUE_CREATUREISNOTREACHABLE) + registerEnum(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS) + registerEnum(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) + registerEnum(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL) + registerEnum(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL) + registerEnum(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL) + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE) + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE) + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE) + registerEnum(RETURNVALUE_YOUCANNOTLOGOUTHERE) + registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL) + registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE) + registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS) + registerEnum(RETURNVALUE_NAMEISTOOAMBIGIOUS) + registerEnum(RETURNVALUE_CANONLYUSEONESHIELD) + registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE) + registerEnum(RETURNVALUE_YOUARENOTTHEOWNER) + registerEnum(RETURNVALUE_TRADEPLAYERFARAWAY) + registerEnum(RETURNVALUE_YOUDONTOWNTHISHOUSE) + registerEnum(RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE) + registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER) + registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE) + + registerEnum(RELOAD_TYPE_ALL) + registerEnum(RELOAD_TYPE_ACTIONS) + registerEnum(RELOAD_TYPE_CHAT) + registerEnum(RELOAD_TYPE_COMMANDS) + registerEnum(RELOAD_TYPE_CONFIG) + registerEnum(RELOAD_TYPE_CREATURESCRIPTS) + registerEnum(RELOAD_TYPE_EVENTS) + registerEnum(RELOAD_TYPE_GLOBAL) + registerEnum(RELOAD_TYPE_GLOBALEVENTS) + registerEnum(RELOAD_TYPE_ITEMS) + registerEnum(RELOAD_TYPE_MONSTERS) + registerEnum(RELOAD_TYPE_MOUNTS) + registerEnum(RELOAD_TYPE_MOVEMENTS) + registerEnum(RELOAD_TYPE_NPCS) + registerEnum(RELOAD_TYPE_QUESTS) + registerEnum(RELOAD_TYPE_RAIDS) + registerEnum(RELOAD_TYPE_SPELLS) + registerEnum(RELOAD_TYPE_TALKACTIONS) + registerEnum(RELOAD_TYPE_WEAPONS) + + // _G + registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); + registerGlobalBoolean("VIRTUAL_PARENT", true); + + registerGlobalMethod("isType", LuaScriptInterface::luaIsType); + registerGlobalMethod("rawgetmetatable", LuaScriptInterface::luaRawGetMetatable); + + // configKeys + registerTable("configKeys"); + + registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT) + registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT) + registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES) + registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS) + registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM) + registerEnumIn("configKeys", ConfigManager::REPLACE_KICK_ON_LOGIN) + registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES) + registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS) + registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE) + registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM) + registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS) + registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS) + registerEnumIn("configKeys", ConfigManager::TELEPORT_NEWBIES) + + registerEnumIn("configKeys", ConfigManager::MAP_NAME) + registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD) + registerEnumIn("configKeys", ConfigManager::SERVER_NAME) + registerEnumIn("configKeys", ConfigManager::OWNER_NAME) + registerEnumIn("configKeys", ConfigManager::OWNER_EMAIL) + registerEnumIn("configKeys", ConfigManager::URL) + registerEnumIn("configKeys", ConfigManager::LOCATION) + registerEnumIn("configKeys", ConfigManager::IP) + registerEnumIn("configKeys", ConfigManager::MOTD) + registerEnumIn("configKeys", ConfigManager::WORLD_TYPE) + registerEnumIn("configKeys", ConfigManager::MYSQL_HOST) + registerEnumIn("configKeys", ConfigManager::MYSQL_USER) + registerEnumIn("configKeys", ConfigManager::MYSQL_PASS) + registerEnumIn("configKeys", ConfigManager::MYSQL_DB) + registerEnumIn("configKeys", ConfigManager::MYSQL_SOCK) + registerEnumIn("configKeys", ConfigManager::DEFAULT_PRIORITY) + registerEnumIn("configKeys", ConfigManager::MAP_AUTHOR) + + registerEnumIn("configKeys", ConfigManager::SQL_PORT) + registerEnumIn("configKeys", ConfigManager::MAX_PLAYERS) + registerEnumIn("configKeys", ConfigManager::PZ_LOCKED) + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRANGE) + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRADIUS) + registerEnumIn("configKeys", ConfigManager::RATE_EXPERIENCE) + registerEnumIn("configKeys", ConfigManager::RATE_SKILL) + registerEnumIn("configKeys", ConfigManager::RATE_LOOT) + registerEnumIn("configKeys", ConfigManager::RATE_MAGIC) + registerEnumIn("configKeys", ConfigManager::RATE_SPAWN) + registerEnumIn("configKeys", ConfigManager::MIN_RATE_SPAWN) + registerEnumIn("configKeys", ConfigManager::MAX_RATE_SPAWN) + registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER) + registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL) + registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL) + registerEnumIn("configKeys", ConfigManager::KICK_AFTER_MINUTES) + registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL) + registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT) + registerEnumIn("configKeys", ConfigManager::KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT) + registerEnumIn("configKeys", ConfigManager::PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT) + registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT) + registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::RED_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::GAME_PORT) + registerEnumIn("configKeys", ConfigManager::LOGIN_PORT) + registerEnumIn("configKeys", ConfigManager::STATUS_PORT) + registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY) + registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE) + registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND) + registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN) + registerEnumIn("configKeys", ConfigManager::NEWBIE_LEVEL_THRESHOLD) + registerEnumIn("configKeys", ConfigManager::BLOCK_HEIGHT) + registerEnumIn("configKeys", ConfigManager::UH_TRAP) + registerEnumIn("configKeys", ConfigManager::ROPE_SPOT_BLOCK) + registerEnumIn("configKeys", ConfigManager::DROP_ITEMS) + registerEnumIn("configKeys", ConfigManager::CLIENT_VERSION) + + // random + registerMethod("os", "rand", LuaScriptInterface::luaRandomRand); + + // os + registerMethod("os", "mtime", LuaScriptInterface::luaSystemTime); + + // table + registerMethod("table", "create", LuaScriptInterface::luaTableCreate); + + // Game + registerTable("Game"); + + registerMethod("Game", "getSpectators", LuaScriptInterface::luaGameGetSpectators); + registerMethod("Game", "getPlayers", LuaScriptInterface::luaGameGetPlayers); + registerMethod("Game", "loadMap", LuaScriptInterface::luaGameLoadMap); + + registerMethod("Game", "getExperienceStage", LuaScriptInterface::luaGameGetExperienceStage); + registerMethod("Game", "getMonsterCount", LuaScriptInterface::luaGameGetMonsterCount); + registerMethod("Game", "getPlayerCount", LuaScriptInterface::luaGameGetPlayerCount); + registerMethod("Game", "getNpcCount", LuaScriptInterface::luaGameGetNpcCount); + + registerMethod("Game", "getTowns", LuaScriptInterface::luaGameGetTowns); + registerMethod("Game", "getHouses", LuaScriptInterface::luaGameGetHouses); + + registerMethod("Game", "getGameState", LuaScriptInterface::luaGameGetGameState); + registerMethod("Game", "setGameState", LuaScriptInterface::luaGameSetGameState); + + registerMethod("Game", "getWorldType", LuaScriptInterface::luaGameGetWorldType); + registerMethod("Game", "setWorldType", LuaScriptInterface::luaGameSetWorldType); + + registerMethod("Game", "getReturnMessage", LuaScriptInterface::luaGameGetReturnMessage); + + registerMethod("Game", "createItem", LuaScriptInterface::luaGameCreateItem); + registerMethod("Game", "createContainer", LuaScriptInterface::luaGameCreateContainer); + registerMethod("Game", "createMonster", LuaScriptInterface::luaGameCreateMonster); + registerMethod("Game", "createNpc", LuaScriptInterface::luaGameCreateNpc); + registerMethod("Game", "createTile", LuaScriptInterface::luaGameCreateTile); + + registerMethod("Game", "startRaid", LuaScriptInterface::luaGameStartRaid); + + registerMethod("Game", "reload", LuaScriptInterface::luaGameReload); + + // Variant + registerClass("Variant", "", LuaScriptInterface::luaVariantCreate); + + registerMethod("Variant", "getNumber", LuaScriptInterface::luaVariantGetNumber); + registerMethod("Variant", "getString", LuaScriptInterface::luaVariantGetString); + registerMethod("Variant", "getPosition", LuaScriptInterface::luaVariantGetPosition); + + // Position + registerClass("Position", "", LuaScriptInterface::luaPositionCreate); + registerMetaMethod("Position", "__add", LuaScriptInterface::luaPositionAdd); + registerMetaMethod("Position", "__sub", LuaScriptInterface::luaPositionSub); + registerMetaMethod("Position", "__eq", LuaScriptInterface::luaPositionCompare); + + registerMethod("Position", "getDistance", LuaScriptInterface::luaPositionGetDistance); + registerMethod("Position", "isSightClear", LuaScriptInterface::luaPositionIsSightClear); + + registerMethod("Position", "sendMagicEffect", LuaScriptInterface::luaPositionSendMagicEffect); + registerMethod("Position", "sendDistanceEffect", LuaScriptInterface::luaPositionSendDistanceEffect); + registerMethod("Position", "sendMonsterSay", LuaScriptInterface::luaPositionSendMonsterSay); + + // Tile + registerClass("Tile", "", LuaScriptInterface::luaTileCreate); + registerMetaMethod("Tile", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Tile", "getPosition", LuaScriptInterface::luaTileGetPosition); + registerMethod("Tile", "getGround", LuaScriptInterface::luaTileGetGround); + registerMethod("Tile", "getThing", LuaScriptInterface::luaTileGetThing); + registerMethod("Tile", "getThingCount", LuaScriptInterface::luaTileGetThingCount); + registerMethod("Tile", "getTopVisibleThing", LuaScriptInterface::luaTileGetTopVisibleThing); + + registerMethod("Tile", "getTopTopItem", LuaScriptInterface::luaTileGetTopTopItem); + registerMethod("Tile", "getTopDownItem", LuaScriptInterface::luaTileGetTopDownItem); + registerMethod("Tile", "getFieldItem", LuaScriptInterface::luaTileGetFieldItem); + + registerMethod("Tile", "getItemById", LuaScriptInterface::luaTileGetItemById); + registerMethod("Tile", "getItemByType", LuaScriptInterface::luaTileGetItemByType); + registerMethod("Tile", "getItemByTopOrder", LuaScriptInterface::luaTileGetItemByTopOrder); + registerMethod("Tile", "getItemCountById", LuaScriptInterface::luaTileGetItemCountById); + + registerMethod("Tile", "getBottomCreature", LuaScriptInterface::luaTileGetBottomCreature); + registerMethod("Tile", "getTopCreature", LuaScriptInterface::luaTileGetTopCreature); + registerMethod("Tile", "getBottomVisibleCreature", LuaScriptInterface::luaTileGetBottomVisibleCreature); + registerMethod("Tile", "getTopVisibleCreature", LuaScriptInterface::luaTileGetTopVisibleCreature); + + registerMethod("Tile", "getItems", LuaScriptInterface::luaTileGetItems); + registerMethod("Tile", "getItemCount", LuaScriptInterface::luaTileGetItemCount); + registerMethod("Tile", "getDownItemCount", LuaScriptInterface::luaTileGetDownItemCount); + registerMethod("Tile", "getTopItemCount", LuaScriptInterface::luaTileGetTopItemCount); + + registerMethod("Tile", "getCreatures", LuaScriptInterface::luaTileGetCreatures); + registerMethod("Tile", "getCreatureCount", LuaScriptInterface::luaTileGetCreatureCount); + + registerMethod("Tile", "getThingIndex", LuaScriptInterface::luaTileGetThingIndex); + + registerMethod("Tile", "hasProperty", LuaScriptInterface::luaTileHasProperty); + registerMethod("Tile", "hasFlag", LuaScriptInterface::luaTileHasFlag); + + registerMethod("Tile", "queryAdd", LuaScriptInterface::luaTileQueryAdd); + + registerMethod("Tile", "getHouse", LuaScriptInterface::luaTileGetHouse); + + // NetworkMessage + registerClass("NetworkMessage", "", LuaScriptInterface::luaNetworkMessageCreate); + registerMetaMethod("NetworkMessage", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("NetworkMessage", "__gc", LuaScriptInterface::luaNetworkMessageDelete); + registerMethod("NetworkMessage", "delete", LuaScriptInterface::luaNetworkMessageDelete); + + registerMethod("NetworkMessage", "getByte", LuaScriptInterface::luaNetworkMessageGetByte); + registerMethod("NetworkMessage", "getU16", LuaScriptInterface::luaNetworkMessageGetU16); + registerMethod("NetworkMessage", "getU32", LuaScriptInterface::luaNetworkMessageGetU32); + registerMethod("NetworkMessage", "getU64", LuaScriptInterface::luaNetworkMessageGetU64); + registerMethod("NetworkMessage", "getString", LuaScriptInterface::luaNetworkMessageGetString); + registerMethod("NetworkMessage", "getPosition", LuaScriptInterface::luaNetworkMessageGetPosition); + + registerMethod("NetworkMessage", "addByte", LuaScriptInterface::luaNetworkMessageAddByte); + registerMethod("NetworkMessage", "addU16", LuaScriptInterface::luaNetworkMessageAddU16); + registerMethod("NetworkMessage", "addU32", LuaScriptInterface::luaNetworkMessageAddU32); + registerMethod("NetworkMessage", "addU64", LuaScriptInterface::luaNetworkMessageAddU64); + registerMethod("NetworkMessage", "addString", LuaScriptInterface::luaNetworkMessageAddString); + registerMethod("NetworkMessage", "addPosition", LuaScriptInterface::luaNetworkMessageAddPosition); + registerMethod("NetworkMessage", "addDouble", LuaScriptInterface::luaNetworkMessageAddDouble); + registerMethod("NetworkMessage", "addItem", LuaScriptInterface::luaNetworkMessageAddItem); + registerMethod("NetworkMessage", "addItemId", LuaScriptInterface::luaNetworkMessageAddItemId); + + registerMethod("NetworkMessage", "reset", LuaScriptInterface::luaNetworkMessageReset); + registerMethod("NetworkMessage", "skipBytes", LuaScriptInterface::luaNetworkMessageSkipBytes); + registerMethod("NetworkMessage", "sendToPlayer", LuaScriptInterface::luaNetworkMessageSendToPlayer); + + // Item + registerClass("Item", "", LuaScriptInterface::luaItemCreate); + registerMetaMethod("Item", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Item", "isItem", LuaScriptInterface::luaItemIsItem); + + registerMethod("Item", "getParent", LuaScriptInterface::luaItemGetParent); + registerMethod("Item", "getTopParent", LuaScriptInterface::luaItemGetTopParent); + + registerMethod("Item", "getId", LuaScriptInterface::luaItemGetId); + + registerMethod("Item", "clone", LuaScriptInterface::luaItemClone); + registerMethod("Item", "split", LuaScriptInterface::luaItemSplit); + registerMethod("Item", "remove", LuaScriptInterface::luaItemRemove); + + registerMethod("Item", "getMovementId", LuaScriptInterface::luaItemGetMovementId); + registerMethod("Item", "setMovementId", LuaScriptInterface::luaItemSetMovementId); + registerMethod("Item", "getActionId", LuaScriptInterface::luaItemGetActionId); + registerMethod("Item", "setActionId", LuaScriptInterface::luaItemSetActionId); + registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); + + registerMethod("Item", "getCount", LuaScriptInterface::luaItemGetCount); + registerMethod("Item", "getCharges", LuaScriptInterface::luaItemGetCharges); + registerMethod("Item", "getFluidType", LuaScriptInterface::luaItemGetFluidType); + registerMethod("Item", "getWeight", LuaScriptInterface::luaItemGetWeight); + + registerMethod("Item", "getSubType", LuaScriptInterface::luaItemGetSubType); + + registerMethod("Item", "getName", LuaScriptInterface::luaItemGetName); + registerMethod("Item", "getPluralName", LuaScriptInterface::luaItemGetPluralName); + registerMethod("Item", "getArticle", LuaScriptInterface::luaItemGetArticle); + + registerMethod("Item", "getPosition", LuaScriptInterface::luaItemGetPosition); + registerMethod("Item", "getTile", LuaScriptInterface::luaItemGetTile); + + registerMethod("Item", "hasAttribute", LuaScriptInterface::luaItemHasAttribute); + registerMethod("Item", "getAttribute", LuaScriptInterface::luaItemGetAttribute); + registerMethod("Item", "setAttribute", LuaScriptInterface::luaItemSetAttribute); + registerMethod("Item", "removeAttribute", LuaScriptInterface::luaItemRemoveAttribute); + + registerMethod("Item", "moveTo", LuaScriptInterface::luaItemMoveTo); + registerMethod("Item", "transform", LuaScriptInterface::luaItemTransform); + registerMethod("Item", "decay", LuaScriptInterface::luaItemDecay); + + registerMethod("Item", "getDescription", LuaScriptInterface::luaItemGetDescription); + + registerMethod("Item", "hasProperty", LuaScriptInterface::luaItemHasProperty); + + // Container + registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); + registerMetaMethod("Container", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Container", "getSize", LuaScriptInterface::luaContainerGetSize); + registerMethod("Container", "getCapacity", LuaScriptInterface::luaContainerGetCapacity); + registerMethod("Container", "getEmptySlots", LuaScriptInterface::luaContainerGetEmptySlots); + + registerMethod("Container", "getItemHoldingCount", LuaScriptInterface::luaContainerGetItemHoldingCount); + registerMethod("Container", "getItemCountById", LuaScriptInterface::luaContainerGetItemCountById); + + registerMethod("Container", "getItem", LuaScriptInterface::luaContainerGetItem); + registerMethod("Container", "hasItem", LuaScriptInterface::luaContainerHasItem); + registerMethod("Container", "addItem", LuaScriptInterface::luaContainerAddItem); + registerMethod("Container", "addItemEx", LuaScriptInterface::luaContainerAddItemEx); + + // Teleport + registerClass("Teleport", "Item", LuaScriptInterface::luaTeleportCreate); + registerMetaMethod("Teleport", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Teleport", "getDestination", LuaScriptInterface::luaTeleportGetDestination); + registerMethod("Teleport", "setDestination", LuaScriptInterface::luaTeleportSetDestination); + + // Creature + registerClass("Creature", "", LuaScriptInterface::luaCreatureCreate); + registerMetaMethod("Creature", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Creature", "getEvents", LuaScriptInterface::luaCreatureGetEvents); + registerMethod("Creature", "registerEvent", LuaScriptInterface::luaCreatureRegisterEvent); + registerMethod("Creature", "unregisterEvent", LuaScriptInterface::luaCreatureUnregisterEvent); + + registerMethod("Creature", "isRemoved", LuaScriptInterface::luaCreatureIsRemoved); + registerMethod("Creature", "isCreature", LuaScriptInterface::luaCreatureIsCreature); + registerMethod("Creature", "isInGhostMode", LuaScriptInterface::luaCreatureIsInGhostMode); + + registerMethod("Creature", "canSee", LuaScriptInterface::luaCreatureCanSee); + registerMethod("Creature", "canSeeCreature", LuaScriptInterface::luaCreatureCanSeeCreature); + + registerMethod("Creature", "getParent", LuaScriptInterface::luaCreatureGetParent); + + registerMethod("Creature", "getId", LuaScriptInterface::luaCreatureGetId); + registerMethod("Creature", "getName", LuaScriptInterface::luaCreatureGetName); + + registerMethod("Creature", "getTarget", LuaScriptInterface::luaCreatureGetTarget); + registerMethod("Creature", "setTarget", LuaScriptInterface::luaCreatureSetTarget); + + registerMethod("Creature", "getFollowCreature", LuaScriptInterface::luaCreatureGetFollowCreature); + registerMethod("Creature", "setFollowCreature", LuaScriptInterface::luaCreatureSetFollowCreature); + + registerMethod("Creature", "getMaster", LuaScriptInterface::luaCreatureGetMaster); + registerMethod("Creature", "setMaster", LuaScriptInterface::luaCreatureSetMaster); + + registerMethod("Creature", "getLight", LuaScriptInterface::luaCreatureGetLight); + registerMethod("Creature", "setLight", LuaScriptInterface::luaCreatureSetLight); + + registerMethod("Creature", "getSpeed", LuaScriptInterface::luaCreatureGetSpeed); + registerMethod("Creature", "getBaseSpeed", LuaScriptInterface::luaCreatureGetBaseSpeed); + registerMethod("Creature", "changeSpeed", LuaScriptInterface::luaCreatureChangeSpeed); + + registerMethod("Creature", "setDropLoot", LuaScriptInterface::luaCreatureSetDropLoot); + + registerMethod("Creature", "getPosition", LuaScriptInterface::luaCreatureGetPosition); + registerMethod("Creature", "getTile", LuaScriptInterface::luaCreatureGetTile); + registerMethod("Creature", "getDirection", LuaScriptInterface::luaCreatureGetDirection); + registerMethod("Creature", "setDirection", LuaScriptInterface::luaCreatureSetDirection); + + registerMethod("Creature", "getHealth", LuaScriptInterface::luaCreatureGetHealth); + registerMethod("Creature", "addHealth", LuaScriptInterface::luaCreatureAddHealth); + registerMethod("Creature", "getMaxHealth", LuaScriptInterface::luaCreatureGetMaxHealth); + registerMethod("Creature", "setMaxHealth", LuaScriptInterface::luaCreatureSetMaxHealth); + registerMethod("Creature", "setHiddenHealth", LuaScriptInterface::luaCreatureSetHiddenHealth); + + registerMethod("Creature", "getSkull", LuaScriptInterface::luaCreatureGetSkull); + registerMethod("Creature", "setSkull", LuaScriptInterface::luaCreatureSetSkull); + + registerMethod("Creature", "getOutfit", LuaScriptInterface::luaCreatureGetOutfit); + registerMethod("Creature", "setOutfit", LuaScriptInterface::luaCreatureSetOutfit); + + registerMethod("Creature", "getCondition", LuaScriptInterface::luaCreatureGetCondition); + registerMethod("Creature", "addCondition", LuaScriptInterface::luaCreatureAddCondition); + registerMethod("Creature", "removeCondition", LuaScriptInterface::luaCreatureRemoveCondition); + + registerMethod("Creature", "remove", LuaScriptInterface::luaCreatureRemove); + registerMethod("Creature", "teleportTo", LuaScriptInterface::luaCreatureTeleportTo); + registerMethod("Creature", "say", LuaScriptInterface::luaCreatureSay); + + registerMethod("Creature", "getDamageMap", LuaScriptInterface::luaCreatureGetDamageMap); + + registerMethod("Creature", "getSummons", LuaScriptInterface::luaCreatureGetSummons); + + registerMethod("Creature", "getDescription", LuaScriptInterface::luaCreatureGetDescription); + + registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo); + + // Player + registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate); + registerMetaMethod("Player", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Player", "isPlayer", LuaScriptInterface::luaPlayerIsPlayer); + + registerMethod("Player", "getGuid", LuaScriptInterface::luaPlayerGetGuid); + registerMethod("Player", "getIp", LuaScriptInterface::luaPlayerGetIp); + registerMethod("Player", "getAccountId", LuaScriptInterface::luaPlayerGetAccountId); + registerMethod("Player", "getLastLoginSaved", LuaScriptInterface::luaPlayerGetLastLoginSaved); + registerMethod("Player", "getLastLogout", LuaScriptInterface::luaPlayerGetLastLogout); + registerMethod("Player", "hasFlag", LuaScriptInterface::luaPlayerHasFlag); + + registerMethod("Player", "getAccountType", LuaScriptInterface::luaPlayerGetAccountType); + registerMethod("Player", "setAccountType", LuaScriptInterface::luaPlayerSetAccountType); + + registerMethod("Player", "getCapacity", LuaScriptInterface::luaPlayerGetCapacity); + registerMethod("Player", "setCapacity", LuaScriptInterface::luaPlayerSetCapacity); + + registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity); + + registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest); + + registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps); + registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd); + registerMethod("Player", "setPlayerKillerEnd", LuaScriptInterface::luaPlayerSetPlayerKillerEnd); + registerMethod("Player", "getDeathPenalty", LuaScriptInterface::luaPlayerGetDeathPenalty); + + registerMethod("Player", "getExperience", LuaScriptInterface::luaPlayerGetExperience); + registerMethod("Player", "addExperience", LuaScriptInterface::luaPlayerAddExperience); + registerMethod("Player", "removeExperience", LuaScriptInterface::luaPlayerRemoveExperience); + registerMethod("Player", "getLevel", LuaScriptInterface::luaPlayerGetLevel); + + registerMethod("Player", "getMagicLevel", LuaScriptInterface::luaPlayerGetMagicLevel); + registerMethod("Player", "getBaseMagicLevel", LuaScriptInterface::luaPlayerGetBaseMagicLevel); + registerMethod("Player", "getMana", LuaScriptInterface::luaPlayerGetMana); + registerMethod("Player", "addMana", LuaScriptInterface::luaPlayerAddMana); + registerMethod("Player", "getMaxMana", LuaScriptInterface::luaPlayerGetMaxMana); + registerMethod("Player", "setMaxMana", LuaScriptInterface::luaPlayerSetMaxMana); + registerMethod("Player", "getManaSpent", LuaScriptInterface::luaPlayerGetManaSpent); + registerMethod("Player", "addManaSpent", LuaScriptInterface::luaPlayerAddManaSpent); + + registerMethod("Player", "getBaseMaxHealth", LuaScriptInterface::luaPlayerGetBaseMaxHealth); + registerMethod("Player", "getBaseMaxMana", LuaScriptInterface::luaPlayerGetBaseMaxMana); + + registerMethod("Player", "getSkillLevel", LuaScriptInterface::luaPlayerGetSkillLevel); + registerMethod("Player", "getEffectiveSkillLevel", LuaScriptInterface::luaPlayerGetEffectiveSkillLevel); + registerMethod("Player", "getSkillPercent", LuaScriptInterface::luaPlayerGetSkillPercent); + registerMethod("Player", "getSkillTries", LuaScriptInterface::luaPlayerGetSkillTries); + registerMethod("Player", "addSkillTries", LuaScriptInterface::luaPlayerAddSkillTries); + + registerMethod("Player", "addOfflineTrainingTime", LuaScriptInterface::luaPlayerAddOfflineTrainingTime); + registerMethod("Player", "getOfflineTrainingTime", LuaScriptInterface::luaPlayerGetOfflineTrainingTime); + registerMethod("Player", "removeOfflineTrainingTime", LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime); + + registerMethod("Player", "addOfflineTrainingTries", LuaScriptInterface::luaPlayerAddOfflineTrainingTries); + + registerMethod("Player", "getOfflineTrainingSkill", LuaScriptInterface::luaPlayerGetOfflineTrainingSkill); + registerMethod("Player", "setOfflineTrainingSkill", LuaScriptInterface::luaPlayerSetOfflineTrainingSkill); + + registerMethod("Player", "getItemCount", LuaScriptInterface::luaPlayerGetItemCount); + registerMethod("Player", "getItemById", LuaScriptInterface::luaPlayerGetItemById); + + registerMethod("Player", "getVocation", LuaScriptInterface::luaPlayerGetVocation); + registerMethod("Player", "setVocation", LuaScriptInterface::luaPlayerSetVocation); + + registerMethod("Player", "getSex", LuaScriptInterface::luaPlayerGetSex); + registerMethod("Player", "setSex", LuaScriptInterface::luaPlayerSetSex); + + registerMethod("Player", "getTown", LuaScriptInterface::luaPlayerGetTown); + registerMethod("Player", "setTown", LuaScriptInterface::luaPlayerSetTown); + + registerMethod("Player", "getGuild", LuaScriptInterface::luaPlayerGetGuild); + registerMethod("Player", "setGuild", LuaScriptInterface::luaPlayerSetGuild); + + registerMethod("Player", "getGuildLevel", LuaScriptInterface::luaPlayerGetGuildLevel); + registerMethod("Player", "setGuildLevel", LuaScriptInterface::luaPlayerSetGuildLevel); + + registerMethod("Player", "getGuildNick", LuaScriptInterface::luaPlayerGetGuildNick); + registerMethod("Player", "setGuildNick", LuaScriptInterface::luaPlayerSetGuildNick); + + registerMethod("Player", "getGroup", LuaScriptInterface::luaPlayerGetGroup); + registerMethod("Player", "setGroup", LuaScriptInterface::luaPlayerSetGroup); + + registerMethod("Player", "getStamina", LuaScriptInterface::luaPlayerGetStamina); + registerMethod("Player", "setStamina", LuaScriptInterface::luaPlayerSetStamina); + + registerMethod("Player", "getSoul", LuaScriptInterface::luaPlayerGetSoul); + registerMethod("Player", "addSoul", LuaScriptInterface::luaPlayerAddSoul); + registerMethod("Player", "getMaxSoul", LuaScriptInterface::luaPlayerGetMaxSoul); + + registerMethod("Player", "getBankBalance", LuaScriptInterface::luaPlayerGetBankBalance); + registerMethod("Player", "setBankBalance", LuaScriptInterface::luaPlayerSetBankBalance); + + registerMethod("Player", "getStorageValue", LuaScriptInterface::luaPlayerGetStorageValue); + registerMethod("Player", "setStorageValue", LuaScriptInterface::luaPlayerSetStorageValue); + + registerMethod("Player", "addItem", LuaScriptInterface::luaPlayerAddItem); + registerMethod("Player", "addItemEx", LuaScriptInterface::luaPlayerAddItemEx); + registerMethod("Player", "removeItem", LuaScriptInterface::luaPlayerRemoveItem); + + registerMethod("Player", "getMoney", LuaScriptInterface::luaPlayerGetMoney); + registerMethod("Player", "addMoney", LuaScriptInterface::luaPlayerAddMoney); + registerMethod("Player", "removeMoney", LuaScriptInterface::luaPlayerRemoveMoney); + + registerMethod("Player", "showTextDialog", LuaScriptInterface::luaPlayerShowTextDialog); + + registerMethod("Player", "sendTextMessage", LuaScriptInterface::luaPlayerSendTextMessage); + registerMethod("Player", "sendPrivateMessage", LuaScriptInterface::luaPlayerSendPrivateMessage); + registerMethod("Player", "channelSay", LuaScriptInterface::luaPlayerChannelSay); + registerMethod("Player", "openChannel", LuaScriptInterface::luaPlayerOpenChannel); + + registerMethod("Player", "getSlotItem", LuaScriptInterface::luaPlayerGetSlotItem); + + registerMethod("Player", "getParty", LuaScriptInterface::luaPlayerGetParty); + + registerMethod("Player", "addOutfit", LuaScriptInterface::luaPlayerAddOutfit); + registerMethod("Player", "addOutfitAddon", LuaScriptInterface::luaPlayerAddOutfitAddon); + registerMethod("Player", "removeOutfit", LuaScriptInterface::luaPlayerRemoveOutfit); + registerMethod("Player", "removeOutfitAddon", LuaScriptInterface::luaPlayerRemoveOutfitAddon); + registerMethod("Player", "hasOutfit", LuaScriptInterface::luaPlayerHasOutfit); + registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow); + + registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays); + registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays); + registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays); + + registerMethod("Player", "hasBlessing", LuaScriptInterface::luaPlayerHasBlessing); + registerMethod("Player", "addBlessing", LuaScriptInterface::luaPlayerAddBlessing); + registerMethod("Player", "removeBlessing", LuaScriptInterface::luaPlayerRemoveBlessing); + + registerMethod("Player", "canLearnSpell", LuaScriptInterface::luaPlayerCanLearnSpell); + registerMethod("Player", "learnSpell", LuaScriptInterface::luaPlayerLearnSpell); + registerMethod("Player", "forgetSpell", LuaScriptInterface::luaPlayerForgetSpell); + registerMethod("Player", "hasLearnedSpell", LuaScriptInterface::luaPlayerHasLearnedSpell); + + registerMethod("Player", "save", LuaScriptInterface::luaPlayerSave); + + registerMethod("Player", "isPzLocked", LuaScriptInterface::luaPlayerIsPzLocked); + registerMethod("Player", "isFakePlayer", LuaScriptInterface::luaPlayerIsFakePlayer); + + registerMethod("Player", "getClient", LuaScriptInterface::luaPlayerGetClient); + registerMethod("Player", "getHouse", LuaScriptInterface::luaPlayerGetHouse); + + registerMethod("Player", "setGhostMode", LuaScriptInterface::luaPlayerSetGhostMode); + + registerMethod("Player", "getContainerId", LuaScriptInterface::luaPlayerGetContainerId); + registerMethod("Player", "getContainerById", LuaScriptInterface::luaPlayerGetContainerById); + registerMethod("Player", "getContainerIndex", LuaScriptInterface::luaPlayerGetContainerIndex); + + registerMethod("Player", "getTotalDamage", LuaScriptInterface::luaPlayerGetTotalDamage); + + // Monster + registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); + registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Monster", "isMonster", LuaScriptInterface::luaMonsterIsMonster); + + registerMethod("Monster", "getType", LuaScriptInterface::luaMonsterGetType); + + registerMethod("Monster", "getSpawnPosition", LuaScriptInterface::luaMonsterGetSpawnPosition); + registerMethod("Monster", "isInSpawnRange", LuaScriptInterface::luaMonsterIsInSpawnRange); + + registerMethod("Monster", "isIdle", LuaScriptInterface::luaMonsterIsIdle); + registerMethod("Monster", "setIdle", LuaScriptInterface::luaMonsterSetIdle); + + registerMethod("Monster", "isTarget", LuaScriptInterface::luaMonsterIsTarget); + registerMethod("Monster", "isOpponent", LuaScriptInterface::luaMonsterIsOpponent); + registerMethod("Monster", "isFriend", LuaScriptInterface::luaMonsterIsFriend); + + registerMethod("Monster", "addFriend", LuaScriptInterface::luaMonsterAddFriend); + registerMethod("Monster", "removeFriend", LuaScriptInterface::luaMonsterRemoveFriend); + registerMethod("Monster", "getFriendList", LuaScriptInterface::luaMonsterGetFriendList); + registerMethod("Monster", "getFriendCount", LuaScriptInterface::luaMonsterGetFriendCount); + + registerMethod("Monster", "addTarget", LuaScriptInterface::luaMonsterAddTarget); + registerMethod("Monster", "removeTarget", LuaScriptInterface::luaMonsterRemoveTarget); + registerMethod("Monster", "getTargetList", LuaScriptInterface::luaMonsterGetTargetList); + registerMethod("Monster", "getTargetCount", LuaScriptInterface::luaMonsterGetTargetCount); + + registerMethod("Monster", "selectTarget", LuaScriptInterface::luaMonsterSelectTarget); + registerMethod("Monster", "searchTarget", LuaScriptInterface::luaMonsterSearchTarget); + + // Npc + registerClass("Npc", "Creature", LuaScriptInterface::luaNpcCreate); + registerMetaMethod("Npc", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Npc", "isNpc", LuaScriptInterface::luaNpcIsNpc); + + registerMethod("Npc", "setMasterPos", LuaScriptInterface::luaNpcSetMasterPos); + + // Guild + registerClass("Guild", "", LuaScriptInterface::luaGuildCreate); + registerMetaMethod("Guild", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Guild", "getId", LuaScriptInterface::luaGuildGetId); + registerMethod("Guild", "getName", LuaScriptInterface::luaGuildGetName); + registerMethod("Guild", "getMembersOnline", LuaScriptInterface::luaGuildGetMembersOnline); + registerMethod("Guild", "setGuildWarEmblem", LuaScriptInterface::luaGuildSetGuildWarEmblem); + + registerMethod("Guild", "addRank", LuaScriptInterface::luaGuildAddRank); + registerMethod("Guild", "getRankById", LuaScriptInterface::luaGuildGetRankById); + registerMethod("Guild", "getRankByLevel", LuaScriptInterface::luaGuildGetRankByLevel); + + registerMethod("Guild", "getBankBalance", LuaScriptInterface::luaGuildGetBankBalance); + registerMethod("Guild", "increaseBankBalance", LuaScriptInterface::luaGuildIncreaseBankBalance); + registerMethod("Guild", "decreaseBankBalance", LuaScriptInterface::luaGuildDecreaseBankBalance); + + // Group + registerClass("Group", "", LuaScriptInterface::luaGroupCreate); + registerMetaMethod("Group", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Group", "getId", LuaScriptInterface::luaGroupGetId); + registerMethod("Group", "getName", LuaScriptInterface::luaGroupGetName); + registerMethod("Group", "getFlags", LuaScriptInterface::luaGroupGetFlags); + registerMethod("Group", "getAccess", LuaScriptInterface::luaGroupGetAccess); + registerMethod("Group", "getMaxDepotItems", LuaScriptInterface::luaGroupGetMaxDepotItems); + registerMethod("Group", "getMaxVipEntries", LuaScriptInterface::luaGroupGetMaxVipEntries); + + // Vocation + registerClass("Vocation", "", LuaScriptInterface::luaVocationCreate); + registerMetaMethod("Vocation", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Vocation", "getId", LuaScriptInterface::luaVocationGetId); + registerMethod("Vocation", "getName", LuaScriptInterface::luaVocationGetName); + registerMethod("Vocation", "getDescription", LuaScriptInterface::luaVocationGetDescription); + + registerMethod("Vocation", "getRequiredSkillTries", LuaScriptInterface::luaVocationGetRequiredSkillTries); + registerMethod("Vocation", "getRequiredManaSpent", LuaScriptInterface::luaVocationGetRequiredManaSpent); + + registerMethod("Vocation", "getCapacityGain", LuaScriptInterface::luaVocationGetCapacityGain); + + registerMethod("Vocation", "getHealthGain", LuaScriptInterface::luaVocationGetHealthGain); + registerMethod("Vocation", "getHealthGainTicks", LuaScriptInterface::luaVocationGetHealthGainTicks); + registerMethod("Vocation", "getHealthGainAmount", LuaScriptInterface::luaVocationGetHealthGainAmount); + + registerMethod("Vocation", "getManaGain", LuaScriptInterface::luaVocationGetManaGain); + registerMethod("Vocation", "getManaGainTicks", LuaScriptInterface::luaVocationGetManaGainTicks); + registerMethod("Vocation", "getManaGainAmount", LuaScriptInterface::luaVocationGetManaGainAmount); + + registerMethod("Vocation", "getMaxSoul", LuaScriptInterface::luaVocationGetMaxSoul); + registerMethod("Vocation", "getSoulGainTicks", LuaScriptInterface::luaVocationGetSoulGainTicks); + + registerMethod("Vocation", "getAttackSpeed", LuaScriptInterface::luaVocationGetAttackSpeed); + registerMethod("Vocation", "getBaseSpeed", LuaScriptInterface::luaVocationGetBaseSpeed); + + registerMethod("Vocation", "getDemotion", LuaScriptInterface::luaVocationGetDemotion); + registerMethod("Vocation", "getPromotion", LuaScriptInterface::luaVocationGetPromotion); + + // Town + registerClass("Town", "", LuaScriptInterface::luaTownCreate); + registerMetaMethod("Town", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Town", "getId", LuaScriptInterface::luaTownGetId); + registerMethod("Town", "getName", LuaScriptInterface::luaTownGetName); + registerMethod("Town", "getTemplePosition", LuaScriptInterface::luaTownGetTemplePosition); + + // House + registerClass("House", "", LuaScriptInterface::luaHouseCreate); + registerMetaMethod("House", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("House", "getId", LuaScriptInterface::luaHouseGetId); + registerMethod("House", "getName", LuaScriptInterface::luaHouseGetName); + registerMethod("House", "getTown", LuaScriptInterface::luaHouseGetTown); + registerMethod("House", "getExitPosition", LuaScriptInterface::luaHouseGetExitPosition); + registerMethod("House", "getRent", LuaScriptInterface::luaHouseGetRent); + + registerMethod("House", "getOwnerGuid", LuaScriptInterface::luaHouseGetOwnerGuid); + registerMethod("House", "setOwnerGuid", LuaScriptInterface::luaHouseSetOwnerGuid); + registerMethod("House", "startTrade", LuaScriptInterface::luaHouseStartTrade); + + registerMethod("House", "getBeds", LuaScriptInterface::luaHouseGetBeds); + registerMethod("House", "getBedCount", LuaScriptInterface::luaHouseGetBedCount); + + registerMethod("House", "getDoors", LuaScriptInterface::luaHouseGetDoors); + registerMethod("House", "getDoorCount", LuaScriptInterface::luaHouseGetDoorCount); + + registerMethod("House", "getTiles", LuaScriptInterface::luaHouseGetTiles); + registerMethod("House", "getTileCount", LuaScriptInterface::luaHouseGetTileCount); + + registerMethod("House", "getAccessList", LuaScriptInterface::luaHouseGetAccessList); + registerMethod("House", "setAccessList", LuaScriptInterface::luaHouseSetAccessList); + + // ItemType + registerClass("ItemType", "", LuaScriptInterface::luaItemTypeCreate); + registerMetaMethod("ItemType", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("ItemType", "isCorpse", LuaScriptInterface::luaItemTypeIsCorpse); + registerMethod("ItemType", "isDoor", LuaScriptInterface::luaItemTypeIsDoor); + registerMethod("ItemType", "isContainer", LuaScriptInterface::luaItemTypeIsContainer); + registerMethod("ItemType", "isChest", LuaScriptInterface::luaItemTypeIsChest); + registerMethod("ItemType", "isFluidContainer", LuaScriptInterface::luaItemTypeIsFluidContainer); + registerMethod("ItemType", "isMovable", LuaScriptInterface::luaItemTypeIsMovable); + registerMethod("ItemType", "isRune", LuaScriptInterface::luaItemTypeIsRune); + registerMethod("ItemType", "isStackable", LuaScriptInterface::luaItemTypeIsStackable); + registerMethod("ItemType", "isReadable", LuaScriptInterface::luaItemTypeIsReadable); + registerMethod("ItemType", "isWritable", LuaScriptInterface::luaItemTypeIsWritable); + registerMethod("ItemType", "isMagicField", LuaScriptInterface::luaItemTypeIsMagicField); + registerMethod("ItemType", "isSplash", LuaScriptInterface::luaItemTypeIsSplash); + registerMethod("ItemType", "isKey", LuaScriptInterface::luaItemTypeIsKey); + registerMethod("ItemType", "isDisguised", LuaScriptInterface::luaItemTypeIsDisguised); + registerMethod("ItemType", "isDestroyable", LuaScriptInterface::luaItemTypeIsDestroyable); + registerMethod("ItemType", "isGroundTile", LuaScriptInterface::luaItemTypeIsGroundTile); + + registerMethod("ItemType", "getType", LuaScriptInterface::luaItemTypeGetType); + registerMethod("ItemType", "getId", LuaScriptInterface::luaItemTypeGetId); + registerMethod("ItemType", "getDisguiseId", LuaScriptInterface::luaItemTypeGetDisguiseId); + registerMethod("ItemType", "getName", LuaScriptInterface::luaItemTypeGetName); + registerMethod("ItemType", "getPluralName", LuaScriptInterface::luaItemTypeGetPluralName); + registerMethod("ItemType", "getArticle", LuaScriptInterface::luaItemTypeGetArticle); + registerMethod("ItemType", "getDescription", LuaScriptInterface::luaItemTypeGetDescription); + registerMethod("ItemType", "getSlotPosition", LuaScriptInterface::luaItemTypeGetSlotPosition); + registerMethod("ItemType", "getDestroyTarget", LuaScriptInterface::luaItemTypeGetDestroyTarget); + + registerMethod("ItemType", "getCharges", LuaScriptInterface::luaItemTypeGetCharges); + registerMethod("ItemType", "getFluidSource", LuaScriptInterface::luaItemTypeGetFluidSource); + registerMethod("ItemType", "getCapacity", LuaScriptInterface::luaItemTypeGetCapacity); + registerMethod("ItemType", "getWeight", LuaScriptInterface::luaItemTypeGetWeight); + + registerMethod("ItemType", "getShootRange", LuaScriptInterface::luaItemTypeGetShootRange); + + registerMethod("ItemType", "getAttack", LuaScriptInterface::luaItemTypeGetAttack); + registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense); + registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); + registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType); + registerMethod("ItemType", "getAmmoType", LuaScriptInterface::luaItemTypeGetAmmoType); + + registerMethod("ItemType", "getTransformEquipId", LuaScriptInterface::luaItemTypeGetTransformEquipId); + registerMethod("ItemType", "getTransformDeEquipId", LuaScriptInterface::luaItemTypeGetTransformDeEquipId); + registerMethod("ItemType", "getDecayId", LuaScriptInterface::luaItemTypeGetDecayId); + registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition); + registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel); + + registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType); + + // Combat + registerClass("Combat", "", LuaScriptInterface::luaCombatCreate); + registerMetaMethod("Combat", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Combat", "setParameter", LuaScriptInterface::luaCombatSetParameter); + registerMethod("Combat", "setFormula", LuaScriptInterface::luaCombatSetFormula); + + registerMethod("Combat", "setArea", LuaScriptInterface::luaCombatSetArea); + registerMethod("Combat", "setCondition", LuaScriptInterface::luaCombatSetCondition); + registerMethod("Combat", "setCallback", LuaScriptInterface::luaCombatSetCallback); + registerMethod("Combat", "setOrigin", LuaScriptInterface::luaCombatSetOrigin); + + registerMethod("Combat", "execute", LuaScriptInterface::luaCombatExecute); + + // Condition + registerClass("Condition", "", LuaScriptInterface::luaConditionCreate); + registerMetaMethod("Condition", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("Condition", "__gc", LuaScriptInterface::luaConditionDelete); + registerMethod("Condition", "delete", LuaScriptInterface::luaConditionDelete); + + registerMethod("Condition", "getId", LuaScriptInterface::luaConditionGetId); + registerMethod("Condition", "getSubId", LuaScriptInterface::luaConditionGetSubId); + registerMethod("Condition", "getType", LuaScriptInterface::luaConditionGetType); + registerMethod("Condition", "getIcons", LuaScriptInterface::luaConditionGetIcons); + registerMethod("Condition", "getEndTime", LuaScriptInterface::luaConditionGetEndTime); + + registerMethod("Condition", "clone", LuaScriptInterface::luaConditionClone); + + registerMethod("Condition", "getTicks", LuaScriptInterface::luaConditionGetTicks); + registerMethod("Condition", "setTicks", LuaScriptInterface::luaConditionSetTicks); + + registerMethod("Condition", "setParameter", LuaScriptInterface::luaConditionSetParameter); + registerMethod("Condition", "setSpeedDelta", LuaScriptInterface::luaConditionSetSpeedDelta); + registerMethod("Condition", "setOutfit", LuaScriptInterface::luaConditionSetOutfit); + + registerMethod("Condition", "setTiming", LuaScriptInterface::luaConditionSetTiming); + + // MonsterType + registerClass("MonsterType", "", LuaScriptInterface::luaMonsterTypeCreate); + registerMetaMethod("MonsterType", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("MonsterType", "isAttackable", LuaScriptInterface::luaMonsterTypeIsAttackable); + registerMethod("MonsterType", "isConvinceable", LuaScriptInterface::luaMonsterTypeIsConvinceable); + registerMethod("MonsterType", "isSummonable", LuaScriptInterface::luaMonsterTypeIsSummonable); + registerMethod("MonsterType", "isIllusionable", LuaScriptInterface::luaMonsterTypeIsIllusionable); + registerMethod("MonsterType", "isHostile", LuaScriptInterface::luaMonsterTypeIsHostile); + registerMethod("MonsterType", "isPushable", LuaScriptInterface::luaMonsterTypeIsPushable); + registerMethod("MonsterType", "isHealthShown", LuaScriptInterface::luaMonsterTypeIsHealthShown); + + registerMethod("MonsterType", "canPushItems", LuaScriptInterface::luaMonsterTypeCanPushItems); + registerMethod("MonsterType", "canPushCreatures", LuaScriptInterface::luaMonsterTypeCanPushCreatures); + + registerMethod("MonsterType", "getName", LuaScriptInterface::luaMonsterTypeGetName); + registerMethod("MonsterType", "getNameDescription", LuaScriptInterface::luaMonsterTypeGetNameDescription); + + registerMethod("MonsterType", "getHealth", LuaScriptInterface::luaMonsterTypeGetHealth); + registerMethod("MonsterType", "getMaxHealth", LuaScriptInterface::luaMonsterTypeGetMaxHealth); + registerMethod("MonsterType", "getRunHealth", LuaScriptInterface::luaMonsterTypeGetRunHealth); + registerMethod("MonsterType", "getExperience", LuaScriptInterface::luaMonsterTypeGetExperience); + + registerMethod("MonsterType", "getCombatImmunities", LuaScriptInterface::luaMonsterTypeGetCombatImmunities); + registerMethod("MonsterType", "getConditionImmunities", LuaScriptInterface::luaMonsterTypeGetConditionImmunities); + + registerMethod("MonsterType", "getAttackList", LuaScriptInterface::luaMonsterTypeGetAttackList); + registerMethod("MonsterType", "getDefenseList", LuaScriptInterface::luaMonsterTypeGetDefenseList); + registerMethod("MonsterType", "getElementList", LuaScriptInterface::luaMonsterTypeGetElementList); + + registerMethod("MonsterType", "getVoices", LuaScriptInterface::luaMonsterTypeGetVoices); + registerMethod("MonsterType", "getLoot", LuaScriptInterface::luaMonsterTypeGetLoot); + registerMethod("MonsterType", "getCreatureEvents", LuaScriptInterface::luaMonsterTypeGetCreatureEvents); + + registerMethod("MonsterType", "getSummonList", LuaScriptInterface::luaMonsterTypeGetSummonList); + registerMethod("MonsterType", "getMaxSummons", LuaScriptInterface::luaMonsterTypeGetMaxSummons); + + registerMethod("MonsterType", "getArmor", LuaScriptInterface::luaMonsterTypeGetArmor); + registerMethod("MonsterType", "getSkill", LuaScriptInterface::luaMonsterTypeGetSkill); + registerMethod("MonsterType", "getDefense", LuaScriptInterface::luaMonsterTypeGetDefense); + registerMethod("MonsterType", "getOutfit", LuaScriptInterface::luaMonsterTypeGetOutfit); + registerMethod("MonsterType", "getRace", LuaScriptInterface::luaMonsterTypeGetRace); + registerMethod("MonsterType", "getCorpseId", LuaScriptInterface::luaMonsterTypeGetCorpseId); + registerMethod("MonsterType", "getManaCost", LuaScriptInterface::luaMonsterTypeGetManaCost); + registerMethod("MonsterType", "getBaseSpeed", LuaScriptInterface::luaMonsterTypeGetBaseSpeed); + registerMethod("MonsterType", "getLight", LuaScriptInterface::luaMonsterTypeGetLight); + + registerMethod("MonsterType", "getTargetDistance", LuaScriptInterface::luaMonsterTypeGetTargetDistance); + registerMethod("MonsterType", "getChangeTargetChance", LuaScriptInterface::luaMonsterTypeGetChangeTargetChance); + registerMethod("MonsterType", "getChangeTargetSpeed", LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed); + + // Party + registerClass("Party", "", nullptr); + registerMetaMethod("Party", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Party", "disband", LuaScriptInterface::luaPartyDisband); + + registerMethod("Party", "getLeader", LuaScriptInterface::luaPartyGetLeader); + registerMethod("Party", "setLeader", LuaScriptInterface::luaPartySetLeader); + + registerMethod("Party", "getMembers", LuaScriptInterface::luaPartyGetMembers); + registerMethod("Party", "getMemberCount", LuaScriptInterface::luaPartyGetMemberCount); + + registerMethod("Party", "getInvitees", LuaScriptInterface::luaPartyGetInvitees); + registerMethod("Party", "getInviteeCount", LuaScriptInterface::luaPartyGetInviteeCount); + + registerMethod("Party", "addInvite", LuaScriptInterface::luaPartyAddInvite); + registerMethod("Party", "removeInvite", LuaScriptInterface::luaPartyRemoveInvite); + + registerMethod("Party", "addMember", LuaScriptInterface::luaPartyAddMember); + registerMethod("Party", "removeMember", LuaScriptInterface::luaPartyRemoveMember); + + registerMethod("Party", "isSharedExperienceActive", LuaScriptInterface::luaPartyIsSharedExperienceActive); + registerMethod("Party", "isSharedExperienceEnabled", LuaScriptInterface::luaPartyIsSharedExperienceEnabled); + registerMethod("Party", "shareExperience", LuaScriptInterface::luaPartyShareExperience); + registerMethod("Party", "setSharedExperience", LuaScriptInterface::luaPartySetSharedExperience); +} + +#undef registerEnum +#undef registerEnumIn + +void LuaScriptInterface::registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction/* = nullptr*/) +{ + // className = {} + lua_newtable(luaState); + lua_pushvalue(luaState, -1); + lua_setglobal(luaState, className.c_str()); + int methods = lua_gettop(luaState); + + // methodsTable = {} + lua_newtable(luaState); + int methodsTable = lua_gettop(luaState); + + if (newFunction) { + // className.__call = newFunction + lua_pushcfunction(luaState, newFunction); + lua_setfield(luaState, methodsTable, "__call"); + } + + uint32_t parents = 0; + if (!baseClass.empty()) { + lua_getglobal(luaState, baseClass.c_str()); + lua_rawgeti(luaState, -1, 'p'); + parents = getNumber(luaState, -1) + 1; + lua_pop(luaState, 1); + lua_setfield(luaState, methodsTable, "__index"); + } + + // setmetatable(className, methodsTable) + lua_setmetatable(luaState, methods); + + // className.metatable = {} + luaL_newmetatable(luaState, className.c_str()); + int metatable = lua_gettop(luaState); + + // className.metatable.__metatable = className + lua_pushvalue(luaState, methods); + lua_setfield(luaState, metatable, "__metatable"); + + // className.metatable.__index = className + lua_pushvalue(luaState, methods); + lua_setfield(luaState, metatable, "__index"); + + // className.metatable['h'] = hash + lua_pushnumber(luaState, std::hash()(className)); + lua_rawseti(luaState, metatable, 'h'); + + // className.metatable['p'] = parents + lua_pushnumber(luaState, parents); + lua_rawseti(luaState, metatable, 'p'); + + // className.metatable['t'] = type + if (className == "Item") { + lua_pushnumber(luaState, LuaData_Item); + } else if (className == "Container") { + lua_pushnumber(luaState, LuaData_Container); + } else if (className == "Teleport") { + lua_pushnumber(luaState, LuaData_Teleport); + } else if (className == "Player") { + lua_pushnumber(luaState, LuaData_Player); + } else if (className == "Monster") { + lua_pushnumber(luaState, LuaData_Monster); + } else if (className == "Npc") { + lua_pushnumber(luaState, LuaData_Npc); + } else if (className == "Tile") { + lua_pushnumber(luaState, LuaData_Tile); + } else { + lua_pushnumber(luaState, LuaData_Unknown); + } + lua_rawseti(luaState, metatable, 't'); + + // pop className, className.metatable + lua_pop(luaState, 2); +} + +void LuaScriptInterface::registerTable(const std::string& tableName) +{ + // _G[tableName] = {} + lua_newtable(luaState); + lua_setglobal(luaState, tableName.c_str()); +} + +void LuaScriptInterface::registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func) +{ + // globalName.methodName = func + lua_getglobal(luaState, globalName.c_str()); + lua_pushcfunction(luaState, func); + lua_setfield(luaState, -2, methodName.c_str()); + + // pop globalName + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func) +{ + // className.metatable.methodName = func + luaL_getmetatable(luaState, className.c_str()); + lua_pushcfunction(luaState, func); + lua_setfield(luaState, -2, methodName.c_str()); + + // pop className.metatable + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerGlobalMethod(const std::string& functionName, lua_CFunction func) +{ + // _G[functionName] = func + lua_pushcfunction(luaState, func); + lua_setglobal(luaState, functionName.c_str()); +} + +void LuaScriptInterface::registerVariable(const std::string& tableName, const std::string& name, lua_Number value) +{ + // tableName.name = value + lua_getglobal(luaState, tableName.c_str()); + setField(luaState, name.c_str(), value); + + // pop tableName + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerGlobalVariable(const std::string& name, lua_Number value) +{ + // _G[name] = value + lua_pushnumber(luaState, value); + lua_setglobal(luaState, name.c_str()); +} + +void LuaScriptInterface::registerGlobalBoolean(const std::string& name, bool value) +{ + // _G[name] = value + pushBoolean(luaState, value); + lua_setglobal(luaState, name.c_str()); +} + +int LuaScriptInterface::luaGetPlayerFlagValue(lua_State* L) +{ + //getPlayerFlagValue(cid, flag) + Player* player = getPlayer(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellCount(lua_State* L) +{ + //getPlayerInstantSpellCount(cid) + Player* player = getPlayer(L, 1); + if (player) { + lua_pushnumber(L, g_spells->getInstantSpellCount(player)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellInfo(lua_State* L) +{ + //getPlayerInstantSpellInfo(cid, index) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t index = getNumber(L, 2); + InstantSpell* spell = g_spells->getInstantSpellByIndex(player, index); + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + lua_createtable(L, 0, 6); + setField(L, "name", spell->getName()); + setField(L, "words", spell->getWords()); + setField(L, "level", spell->getLevel()); + setField(L, "mlevel", spell->getMagicLevel()); + setField(L, "mana", spell->getManaCost(player)); + setField(L, "manapercent", spell->getManaPercent()); + return 1; +} + +int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) +{ + //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t itemId = getNumber(L, 2); + int32_t count = getNumber(L, 3, 1); + bool canDropOnMap = getBoolean(L, 4, true); + uint16_t subType = getNumber(L, 5, 1); + + const ItemType& it = Item::items[itemId]; + int32_t itemCount; + + auto parameters = lua_gettop(L); + if (parameters > 4) { + //subtype already supplied, count then is the amount + itemCount = std::max(1, count); + } else if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } else { + itemCount = 1; + } + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + uint16_t stackCount = subType; + if (it.stackable && stackCount > 100) { + stackCount = 100; + } + + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem, canDropOnMap); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = getScriptEnv()->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + return 1; + } + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoTileAddItemEx(lua_State* L) +{ + //doTileAddItemEx(pos, uid) + const Position& pos = getPosition(L, 1); + + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + uint32_t uid = getNumber(L, 2); + Item* item = getScriptEnv()->getItemByUID(uid); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, g_game.internalAddItem(tile, item)); + return 1; +} + +int LuaScriptInterface::luaDoCreateItem(lua_State* L) +{ + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + const Position& pos = getPosition(L, 3); + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + + int32_t itemCount = 1; + int32_t subType = 1; + + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(tile, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + return 1; + } + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoCreateItemEx(lua_State* L) +{ + //doCreateItemEx(itemid, count/subtype) + //Returns uid of the created item + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.stackable && count > 100) { + reportErrorFunc("Stack count cannot be higher than 100."); + count = 100; + } + + Item* newItem = Item::CreateItem(itemId, count); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + newItem->setParent(VirtualCylinder::virtualCylinder); + + ScriptEnvironment* env = getScriptEnv(); + env->addTempItem(newItem); + + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; +} + +int LuaScriptInterface::luaDebugPrint(lua_State* L) +{ + //debugPrint(text) + reportErrorFunc(getString(L, -1)); + return 0; +} + +int LuaScriptInterface::luaGetWorldTime(lua_State* L) +{ + //getWorldTime() + uint32_t time = g_game.getLightHour(); + lua_pushnumber(L, time); + return 1; +} + +int LuaScriptInterface::luaGetWorldLight(lua_State* L) +{ + //getWorldLight() + LightInfo lightInfo = g_game.getWorldLightInfo(); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); + return 2; +} + +int LuaScriptInterface::luaGetWorldUpTime(lua_State* L) +{ + //getWorldUpTime() + uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; + lua_pushnumber(L, uptime); + return 1; +} + +bool LuaScriptInterface::getArea(lua_State* L, std::list& list, uint32_t& rows) +{ + lua_pushnil(L); + for (rows = 0; lua_next(L, -2) != 0; ++rows) { + if (!isTable(L, -1)) { + return false; + } + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (!isNumber(L, -1)) { + return false; + } + list.push_back(getNumber(L, -1)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + return (rows != 0); +} + +int LuaScriptInterface::luaCreateCombatArea(lua_State* L) +{ + //createCombatArea( {area}, {extArea} ) + ScriptEnvironment* env = getScriptEnv(); + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = g_luaEnvironment.createAreaObject(env->getScriptInterface()); + AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + + int parameters = lua_gettop(L); + if (parameters >= 2) { + uint32_t rowsExtArea; + std::list listExtArea; + if (!isTable(L, 2) || !getArea(L, listExtArea, rowsExtArea)) { + reportErrorFunc("Invalid extended area table."); + pushBoolean(L, false); + return 1; + } + area->setupExtArea(listExtArea, rowsExtArea); + } + + uint32_t rowsArea = 0; + std::list listArea; + if (!isTable(L, 1) || !getArea(L, listArea, rowsArea)) { + reportErrorFunc("Invalid area table."); + pushBoolean(L, false); + return 1; + } + + area->setupArea(listArea, rowsArea); + lua_pushnumber(L, areaId); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatHealth(lua_State* L) +{ + //doAreaCombatHealth(cid, type, pos, area, min, max, effect[, origin = ORIGIN_SPELL]) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 4); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatType_t combatType = getNumber(L, 2); + + CombatParams params; + params.combatType = combatType; + params.impactEffect = getNumber(L, 7); + + CombatDamage damage; + damage.origin = getNumber(L, 8, ORIGIN_SPELL); + damage.type = combatType; + damage.value = normal_random(getNumber(L, 6), getNumber(L, 5)); + + Combat::doCombatHealth(creature, getPosition(L, 3), area, damage, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatHealth(lua_State* L) +{ + //doTargetCombatHealth(cid, target, type, min, max, effect[, origin = ORIGIN_SPELL]) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatType_t combatType = getNumber(L, 3); + + CombatParams params; + params.combatType = combatType; + params.impactEffect = getNumber(L, 6); + + CombatDamage damage; + damage.origin = getNumber(L, 7, ORIGIN_SPELL); + damage.type = combatType; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + + Combat::doCombatHealth(creature, target, damage, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatMana(lua_State* L) +{ + //doAreaCombatMana(cid, pos, area, min, max, effect[, origin = ORIGIN_SPELL]) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 6); + + CombatDamage damage; + damage.origin = getNumber(L, 7, ORIGIN_SPELL); + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + + Position pos = getPosition(L, 2); + Combat::doCombatMana(creature, pos, area, damage, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatMana(lua_State* L) +{ + //doTargetCombatMana(cid, target, min, max, effect[, origin = ORIGIN_SPELL) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.impactEffect = getNumber(L, 5); + + CombatDamage damage; + damage.origin = getNumber(L, 6, ORIGIN_SPELL); + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 3), getNumber(L, 4)); + + Combat::doCombatMana(creature, target, damage, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatCondition(lua_State* L) +{ + //doAreaCombatCondition(cid, pos, area, condition, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + const Condition* condition = getUserdata(L, 4); + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 5); + params.conditionList.emplace_front(condition); + Combat::doCombatCondition(creature, getPosition(L, 2), area, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatCondition(lua_State* L) +{ + //doTargetCombatCondition(cid, target, condition, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + const Condition* condition = getUserdata(L, 3); + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.impactEffect = getNumber(L, 4); + params.conditionList.emplace_front(condition); + Combat::doCombatCondition(creature, target, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatDispel(lua_State* L) +{ + //doAreaCombatDispel(cid, pos, area, type, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 5); + params.dispelType = getNumber(L, 4); + Combat::doCombatDispel(creature, getPosition(L, 2), area, params); + + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatDispel(lua_State* L) +{ + //doTargetCombatDispel(cid, target, type, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.dispelType = getNumber(L, 3); + params.impactEffect = getNumber(L, 4); + Combat::doCombatDispel(creature, target, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) +{ + //doChallengeCreature(cid, target) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + target->challengeCreature(creature); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaSetCreatureOutfit(lua_State* L) +{ + //doSetCreatureOutfit(cid, outfit, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Outfit_t outfit = getOutfit(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, outfit, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetMonsterOutfit(lua_State* L) +{ + //doSetMonsterOutfit(cid, name, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + std::string name = getString(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, name, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetItemOutfit(lua_State* L) +{ + //doSetItemOutfit(cid, item, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t item = getNumber(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, item, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaDoMoveCreature(lua_State* L) +{ + //doMoveCreature(cid, direction) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Direction direction = getNumber(L, 2); + if (direction > DIRECTION_LAST) { + reportErrorFunc("No valid direction"); + pushBoolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT); + lua_pushnumber(L, ret); + return 1; +} + +int LuaScriptInterface::luaIsValidUID(lua_State* L) +{ + //isValidUID(uid) + pushBoolean(L, getScriptEnv()->getThingByUID(getNumber(L, -1)) != nullptr); + return 1; +} + +int LuaScriptInterface::luaIsDepot(lua_State* L) +{ + //isDepot(uid) + Container* container = getScriptEnv()->getContainerByUID(getNumber(L, -1)); + pushBoolean(L, container && container->getDepotLocker()); + return 1; +} + +int LuaScriptInterface::luaIsMoveable(lua_State* L) +{ + //isMoveable(uid) + //isMovable(uid) + Thing* thing = getScriptEnv()->getThingByUID(getNumber(L, -1)); + pushBoolean(L, thing && thing->isPushable()); + return 1; +} + +int LuaScriptInterface::luaDoAddContainerItem(lua_State* L) +{ + //doAddContainerItem(uid, itemid, count/subtype) + uint32_t uid = getNumber(L, 1); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + if (!container) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t itemId = getNumber(L, 2); + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + int32_t subType = 1; + uint32_t count = getNumber(L, 3, 1); + + if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(container, newItem); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + lua_pushnumber(L, env->addThing(newItem)); + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + } + return 1; + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaGetDepotId(lua_State* L) +{ + //getDepotId(uid) + uint32_t uid = getNumber(L, -1); + + Container* container = getScriptEnv()->getContainerByUID(uid); + if (!container) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + DepotLocker* depotLocker = container->getDepotLocker(); + if (!depotLocker) { + reportErrorFunc("Depot not found"); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, depotLocker->getDepotId()); + return 1; +} + +int LuaScriptInterface::luaIsInArray(lua_State* L) +{ + //isInArray(array, value) + if (!isTable(L, 1)) { + pushBoolean(L, false); + return 1; + } + + lua_pushnil(L); + while (lua_next(L, 1)) { + if (lua_equal(L, 2, -1) != 0) { + pushBoolean(L, true); + return 1; + } + lua_pop(L, 1); + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoSetCreatureLight(lua_State* L) +{ + //doSetCreatureLight(cid, lightLevel, lightColor, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t level = getNumber(L, 2); + uint16_t color = getNumber(L, 3); + uint32_t time = getNumber(L, 4); + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_LIGHT, time, level | (color << 8)); + creature->addCondition(condition); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaAddEvent(lua_State* L) +{ + //addEvent(callback, delay, ...) + lua_State* globalState = g_luaEnvironment.getLuaState(); + if (!globalState) { + reportErrorFunc("No valid script interface!"); + pushBoolean(L, false); + return 1; + } else if (globalState != L) { + lua_xmove(L, globalState, lua_gettop(L)); + } + + int parameters = lua_gettop(globalState); + if (!isFunction(globalState, -parameters)) { //-parameters means the first parameter from left to right + reportErrorFunc("callback parameter should be a function."); + pushBoolean(L, false); + return 1; + } + + if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS) || g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { + std::vector> indexes; + for (int i = 3; i <= parameters; ++i) { + if (lua_getmetatable(globalState, i) == 0) { + continue; + } + lua_rawgeti(L, -1, 't'); + + LuaDataType type = getNumber(L, -1); + if (type != LuaData_Unknown && type != LuaData_Tile) { + indexes.push_back({i, type}); + } + lua_pop(globalState, 2); + } + + if (!indexes.empty()) { + if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS)) { + bool plural = indexes.size() > 1; + + std::string warningString = "Argument"; + if (plural) { + warningString += 's'; + } + + for (const auto& entry : indexes) { + if (entry == indexes.front()) { + warningString += ' '; + } else if (entry == indexes.back()) { + warningString += " and "; + } else { + warningString += ", "; + } + warningString += '#'; + warningString += std::to_string(entry.first); + } + + if (plural) { + warningString += " are unsafe"; + } else { + warningString += " is unsafe"; + } + + reportErrorFunc(warningString); + } + + if (g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { + for (const auto& entry : indexes) { + switch (entry.second) { + case LuaData_Item: + case LuaData_Container: + case LuaData_Teleport: { + lua_getglobal(globalState, "Item"); + lua_getfield(globalState, -1, "getMovementId"); + break; + } + case LuaData_Player: + case LuaData_Monster: + case LuaData_Npc: { + lua_getglobal(globalState, "Creature"); + lua_getfield(globalState, -1, "getId"); + break; + } + default: + break; + } + lua_replace(globalState, -2); + lua_pushvalue(globalState, entry.first); + lua_call(globalState, 1, 1); + lua_replace(globalState, entry.first); + } + } + } + } + + LuaTimerEventDesc eventDesc; + for (int i = 0; i < parameters - 2; ++i) { //-2 because addEvent needs at least two parameters + eventDesc.parameters.push_back(luaL_ref(globalState, LUA_REGISTRYINDEX)); + } + + uint32_t delay = std::max(100, getNumber(globalState, 2)); + lua_pop(globalState, 1); + + eventDesc.function = luaL_ref(globalState, LUA_REGISTRYINDEX); + eventDesc.scriptId = getScriptEnv()->getScriptId(); + + auto& lastTimerEventId = g_luaEnvironment.lastEventTimerId; + eventDesc.eventId = g_scheduler.addEvent(createSchedulerTask( + delay, std::bind(&LuaEnvironment::executeTimerEvent, &g_luaEnvironment, lastTimerEventId) + )); + + g_luaEnvironment.timerEvents.emplace(lastTimerEventId, std::move(eventDesc)); + lua_pushnumber(L, lastTimerEventId++); + return 1; +} + +int LuaScriptInterface::luaStopEvent(lua_State* L) +{ + //stopEvent(eventid) + lua_State* globalState = g_luaEnvironment.getLuaState(); + if (!globalState) { + reportErrorFunc("No valid script interface!"); + pushBoolean(L, false); + return 1; + } + + uint32_t eventId = getNumber(L, 1); + + auto& timerEvents = g_luaEnvironment.timerEvents; + auto it = timerEvents.find(eventId); + if (it == timerEvents.end()) { + pushBoolean(L, false); + return 1; + } + + LuaTimerEventDesc timerEventDesc = std::move(it->second); + timerEvents.erase(it); + + g_scheduler.stopEvent(timerEventDesc.eventId); + luaL_unref(globalState, LUA_REGISTRYINDEX, timerEventDesc.function); + + for (auto parameter : timerEventDesc.parameters) { + luaL_unref(globalState, LUA_REGISTRYINDEX, parameter); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGetCreatureCondition(lua_State* L) +{ + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + ConditionType_t condition = getNumber(L, 2); + uint32_t subId = getNumber(L, 3, 0); + pushBoolean(L, creature->hasCondition(condition, subId)); + return 1; +} + +int LuaScriptInterface::luaSaveServer(lua_State* L) +{ + g_game.saveGameState(); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCleanMap(lua_State* L) +{ + lua_pushnumber(L, g_game.map.clean()); + return 1; +} + +int LuaScriptInterface::luaIsInWar(lua_State* L) +{ + //isInWar(cid, target) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Player* targetPlayer = getPlayer(L, 2); + if (!targetPlayer) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, player->getWarId(targetPlayer)); + return 1; +} + +int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) +{ + //getWaypointPositionByName(name) + auto& waypoints = g_game.map.waypoints; + + auto it = waypoints.find(getString(L, -1)); + if (it != waypoints.end()) { + pushPosition(L, it->second); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaSendChannelMessage(lua_State* L) +{ + //sendChannelMessage(channelId, type, message) + uint32_t channelId = getNumber(L, 1); + ChatChannel* channel = g_chat->getChannelById(channelId); + if (!channel) { + pushBoolean(L, false); + return 1; + } + + SpeakClasses type = getNumber(L, 2); + std::string message = getString(L, 3); + channel->sendToAll(message, type); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) +{ + //sendGuildChannelMessage(guildId, type, message) + uint32_t guildId = getNumber(L, 1); + ChatChannel* channel = g_chat->getGuildChannelById(guildId); + if (!channel) { + pushBoolean(L, false); + return 1; + } + + SpeakClasses type = getNumber(L, 2); + std::string message = getString(L, 3); + channel->sendToAll(message, type); + pushBoolean(L, true); + return 1; +} + +std::string LuaScriptInterface::escapeString(const std::string& string) +{ + std::string s = string; + replaceString(s, "\\", "\\\\"); + replaceString(s, "\"", "\\\""); + replaceString(s, "'", "\\'"); + replaceString(s, "[[", "\\[["); + return s; +} + +#ifndef LUAJIT_VERSION +const luaL_Reg LuaScriptInterface::luaBitReg[] = { + //{"tobit", LuaScriptInterface::luaBitToBit}, + {"bnot", LuaScriptInterface::luaBitNot}, + {"band", LuaScriptInterface::luaBitAnd}, + {"bor", LuaScriptInterface::luaBitOr}, + {"bxor", LuaScriptInterface::luaBitXor}, + {"lshift", LuaScriptInterface::luaBitLeftShift}, + {"rshift", LuaScriptInterface::luaBitRightShift}, + //{"arshift", LuaScriptInterface::luaBitArithmeticalRightShift}, + //{"rol", LuaScriptInterface::luaBitRotateLeft}, + //{"ror", LuaScriptInterface::luaBitRotateRight}, + //{"bswap", LuaScriptInterface::luaBitSwapEndian}, + //{"tohex", LuaScriptInterface::luaBitToHex}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaBitNot(lua_State* L) +{ + lua_pushnumber(L, ~getNumber(L, -1)); + return 1; +} + +#define MULTIOP(name, op) \ +int LuaScriptInterface::luaBit##name(lua_State* L) \ +{ \ + int n = lua_gettop(L); \ + uint32_t w = getNumber(L, -1); \ + for (int i = 1; i < n; ++i) \ + w op getNumber(L, i); \ + lua_pushnumber(L, w); \ + return 1; \ +} + +MULTIOP(And, &= ) +MULTIOP(Or, |= ) +MULTIOP(Xor, ^= ) + +#define SHIFTOP(name, op) \ +int LuaScriptInterface::luaBit##name(lua_State* L) \ +{ \ + uint32_t n1 = getNumber(L, 1), n2 = getNumber(L, 2); \ + lua_pushnumber(L, (n1 op n2)); \ + return 1; \ +} + +SHIFTOP(LeftShift, << ) +SHIFTOP(RightShift, >> ) +#endif + +const luaL_Reg LuaScriptInterface::luaConfigManagerTable[] = { + {"getString", LuaScriptInterface::luaConfigManagerGetString}, + {"getNumber", LuaScriptInterface::luaConfigManagerGetNumber}, + {"getBoolean", LuaScriptInterface::luaConfigManagerGetBoolean}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaConfigManagerGetString(lua_State* L) +{ + pushString(L, g_config.getString(getNumber(L, -1))); + return 1; +} + +int LuaScriptInterface::luaConfigManagerGetNumber(lua_State* L) +{ + lua_pushnumber(L, g_config.getNumber(getNumber(L, -1))); + return 1; +} + +int LuaScriptInterface::luaConfigManagerGetBoolean(lua_State* L) +{ + pushBoolean(L, g_config.getBoolean(getNumber(L, -1))); + return 1; +} + +const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { + {"query", LuaScriptInterface::luaDatabaseExecute}, + {"asyncQuery", LuaScriptInterface::luaDatabaseAsyncExecute}, + {"storeQuery", LuaScriptInterface::luaDatabaseStoreQuery}, + {"asyncStoreQuery", LuaScriptInterface::luaDatabaseAsyncStoreQuery}, + {"escapeString", LuaScriptInterface::luaDatabaseEscapeString}, + {"escapeBlob", LuaScriptInterface::luaDatabaseEscapeBlob}, + {"lastInsertId", LuaScriptInterface::luaDatabaseLastInsertId}, + {"tableExists", LuaScriptInterface::luaDatabaseTableExists}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaDatabaseExecute(lua_State* L) +{ + pushBoolean(L, Database::getInstance()->executeQuery(getString(L, -1))); + return 1; +} + +int LuaScriptInterface::luaDatabaseAsyncExecute(lua_State* L) +{ + std::function callback; + if (lua_gettop(L) > 1) { + int32_t ref = luaL_ref(L, LUA_REGISTRYINDEX); + auto scriptId = getScriptEnv()->getScriptId(); + callback = [ref, scriptId](DBResult_ptr, bool success) { + lua_State* luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return; + } + + if (!LuaScriptInterface::reserveScriptEnv()) { + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + return; + } + + lua_rawgeti(luaState, LUA_REGISTRYINDEX, ref); + pushBoolean(luaState, success); + auto env = getScriptEnv(); + env->setScriptId(scriptId, &g_luaEnvironment); + g_luaEnvironment.callFunction(1); + + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + }; + } + g_databaseTasks.addTask(getString(L, -1), callback); + return 0; +} + +int LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) +{ + if (DBResult_ptr res = Database::getInstance()->storeQuery(getString(L, -1))) { + lua_pushnumber(L, ScriptEnvironment::addResult(res)); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDatabaseAsyncStoreQuery(lua_State* L) +{ + std::function callback; + if (lua_gettop(L) > 1) { + int32_t ref = luaL_ref(L, LUA_REGISTRYINDEX); + auto scriptId = getScriptEnv()->getScriptId(); + callback = [ref, scriptId](DBResult_ptr result, bool) { + lua_State* luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return; + } + + if (!LuaScriptInterface::reserveScriptEnv()) { + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + return; + } + + lua_rawgeti(luaState, LUA_REGISTRYINDEX, ref); + if (result) { + lua_pushnumber(luaState, ScriptEnvironment::addResult(result)); + } else { + pushBoolean(luaState, false); + } + auto env = getScriptEnv(); + env->setScriptId(scriptId, &g_luaEnvironment); + g_luaEnvironment.callFunction(1); + + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + }; + } + g_databaseTasks.addTask(getString(L, -1), callback, true); + return 0; +} + +int LuaScriptInterface::luaDatabaseEscapeString(lua_State* L) +{ + pushString(L, Database::getInstance()->escapeString(getString(L, -1))); + return 1; +} + +int LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) +{ + uint32_t length = getNumber(L, 2); + pushString(L, Database::getInstance()->escapeBlob(getString(L, 1).c_str(), length)); + return 1; +} + +int LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) +{ + lua_pushnumber(L, Database::getInstance()->getLastInsertId()); + return 1; +} + +int LuaScriptInterface::luaDatabaseTableExists(lua_State* L) +{ + pushBoolean(L, DatabaseManager::tableExists(getString(L, -1))); + return 1; +} + +const luaL_Reg LuaScriptInterface::luaResultTable[] = { + {"getNumber", LuaScriptInterface::luaResultGetNumber}, + {"getString", LuaScriptInterface::luaResultGetString}, + {"getStream", LuaScriptInterface::luaResultGetStream}, + {"next", LuaScriptInterface::luaResultNext}, + {"free", LuaScriptInterface::luaResultFree}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaResultGetNumber(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + const std::string& s = getString(L, 2); + lua_pushnumber(L, res->getNumber(s)); + return 1; +} + +int LuaScriptInterface::luaResultGetString(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + const std::string& s = getString(L, 2); + pushString(L, res->getString(s)); + return 1; +} + +int LuaScriptInterface::luaResultGetStream(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + unsigned long length; + const char* stream = res->getStream(getString(L, 2), length); + lua_pushlstring(L, stream, length); + lua_pushnumber(L, length); + return 2; +} + +int LuaScriptInterface::luaResultNext(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, -1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + pushBoolean(L, res->next()); + return 1; +} + +int LuaScriptInterface::luaResultFree(lua_State* L) +{ + pushBoolean(L, ScriptEnvironment::removeResult(getNumber(L, -1))); + return 1; +} + +// Userdata +int LuaScriptInterface::luaUserdataCompare(lua_State* L) +{ + // userdataA == userdataB + pushBoolean(L, getUserdata(L, 1) == getUserdata(L, 2)); + return 1; +} + +// _G +int LuaScriptInterface::luaIsType(lua_State* L) +{ + // isType(derived, base) + lua_getmetatable(L, -2); + lua_getmetatable(L, -2); + + lua_rawgeti(L, -2, 'p'); + uint_fast8_t parentsB = getNumber(L, 1); + + lua_rawgeti(L, -3, 'h'); + size_t hashB = getNumber(L, 1); + + lua_rawgeti(L, -3, 'p'); + uint_fast8_t parentsA = getNumber(L, 1); + for (uint_fast8_t i = parentsA; i < parentsB; ++i) { + lua_getfield(L, -3, "__index"); + lua_replace(L, -4); + } + + lua_rawgeti(L, -4, 'h'); + size_t hashA = getNumber(L, 1); + + pushBoolean(L, hashA == hashB); + return 1; +} + +int LuaScriptInterface::luaRawGetMetatable(lua_State* L) +{ + // rawgetmetatable(metatableName) + luaL_getmetatable(L, getString(L, 1).c_str()); + return 1; +} + +// random +int LuaScriptInterface::luaRandomRand(lua_State* L) +{ + // random.rand() + lua_pushnumber(L, rand()); + return 1; +} + +// os +int LuaScriptInterface::luaSystemTime(lua_State* L) +{ + // os.mtime() + lua_pushnumber(L, OTSYS_TIME()); + return 1; +} + +// table +int LuaScriptInterface::luaTableCreate(lua_State* L) +{ + // table.create(arrayLength, keyLength) + lua_createtable(L, getNumber(L, 1), getNumber(L, 2)); + return 1; +} + +// Game +int LuaScriptInterface::luaGameGetSpectators(lua_State* L) +{ + // Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]]) + const Position& position = getPosition(L, 1); + bool multifloor = getBoolean(L, 2, false); + bool onlyPlayers = getBoolean(L, 3, false); + int32_t minRangeX = getNumber(L, 4, 0); + int32_t maxRangeX = getNumber(L, 5, 0); + int32_t minRangeY = getNumber(L, 6, 0); + int32_t maxRangeY = getNumber(L, 7, 0); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, multifloor, onlyPlayers, minRangeX, maxRangeX, minRangeY, maxRangeY); + + lua_createtable(L, spectators.size(), 0); + + int index = 0; + for (Creature* creature : spectators) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetPlayers(lua_State* L) +{ + // Game.getPlayers() + lua_createtable(L, g_game.getPlayersOnline(), 0); + + int index = 0; + for (const auto& playerEntry : g_game.getPlayers()) { + pushUserdata(L, playerEntry.second); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameLoadMap(lua_State* L) +{ + // Game.loadMap(path) + const std::string& path = getString(L, 1); + g_dispatcher.addTask(createTask(std::bind(&Game::loadMap, &g_game, path))); + return 0; +} + +int LuaScriptInterface::luaGameGetExperienceStage(lua_State* L) +{ + // Game.getExperienceStage(level) + uint32_t level = getNumber(L, 1); + lua_pushnumber(L, g_game.getExperienceStage(level)); + return 1; +} + +int LuaScriptInterface::luaGameGetMonsterCount(lua_State* L) +{ + // Game.getMonsterCount() + lua_pushnumber(L, g_game.getMonstersOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetPlayerCount(lua_State* L) +{ + // Game.getPlayerCount() + lua_pushnumber(L, g_game.getPlayersOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetNpcCount(lua_State* L) +{ + // Game.getNpcCount() + lua_pushnumber(L, g_game.getNpcsOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetTowns(lua_State* L) +{ + // Game.getTowns() + const auto& towns = g_game.map.towns.getTowns(); + lua_createtable(L, towns.size(), 0); + + int index = 0; + for (auto townEntry : towns) { + pushUserdata(L, townEntry.second); + setMetatable(L, -1, "Town"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetHouses(lua_State* L) +{ + // Game.getHouses() + const auto& houses = g_game.map.houses.getHouses(); + lua_createtable(L, houses.size(), 0); + + int index = 0; + for (auto houseEntry : houses) { + pushUserdata(L, houseEntry.second); + setMetatable(L, -1, "House"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetGameState(lua_State* L) +{ + // Game.getGameState() + lua_pushnumber(L, g_game.getGameState()); + return 1; +} + +int LuaScriptInterface::luaGameSetGameState(lua_State* L) +{ + // Game.setGameState(state) + GameState_t state = getNumber(L, 1); + g_game.setGameState(state); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGameGetWorldType(lua_State* L) +{ + // Game.getWorldType() + lua_pushnumber(L, g_game.getWorldType()); + return 1; +} + +int LuaScriptInterface::luaGameSetWorldType(lua_State* L) +{ + // Game.setWorldType(type) + WorldType_t type = getNumber(L, 1); + g_game.setWorldType(type); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGameGetReturnMessage(lua_State* L) +{ + // Game.getReturnMessage(value) + ReturnValue value = getNumber(L, 1); + pushString(L, getReturnMessage(value)); + return 1; +} + +int LuaScriptInterface::luaGameCreateItem(lua_State* L) +{ + // Game.createItem(itemId[, count[, position]]) + uint16_t count = getNumber(L, 2, 1); + uint16_t id; + if (isNumber(L, 1)) { + id = getNumber(L, 1); + } else { + id = Item::items.getItemIdByName(getString(L, 1)); + if (id == 0) { + lua_pushnil(L); + return 1; + } + } + + const ItemType& it = Item::items[id]; + if (it.stackable) { + count = std::min(count, 100); + } + + Item* item = Item::CreateItem(id, count); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) >= 3) { + const Position& position = getPosition(L, 3); + Tile* tile = g_game.map.getTile(position); + if (!tile) { + delete item; + lua_pushnil(L); + return 1; + } + + g_game.internalAddItem(tile, item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } else { + getScriptEnv()->addTempItem(item); + item->setParent(VirtualCylinder::virtualCylinder); + } + + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; +} + +int LuaScriptInterface::luaGameCreateContainer(lua_State* L) +{ + // Game.createContainer(itemId, size[, position]) + uint16_t size = getNumber(L, 2); + uint16_t id; + if (isNumber(L, 1)) { + id = getNumber(L, 1); + } else { + id = Item::items.getItemIdByName(getString(L, 1)); + if (id == 0) { + lua_pushnil(L); + return 1; + } + } + + Container* container = Item::CreateItemAsContainer(id, size); + if (!container) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) >= 3) { + const Position& position = getPosition(L, 3); + Tile* tile = g_game.map.getTile(position); + if (!tile) { + delete container; + lua_pushnil(L); + return 1; + } + + g_game.internalAddItem(tile, container, INDEX_WHEREEVER, FLAG_NOLIMIT); + } else { + getScriptEnv()->addTempItem(container); + container->setParent(VirtualCylinder::virtualCylinder); + } + + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + return 1; +} + +int LuaScriptInterface::luaGameCreateMonster(lua_State* L) +{ + // Game.createMonster(monsterName, position[, extended = false[, force = false]]) + Monster* monster = Monster::createMonster(getString(L, 1)); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + bool extended = getBoolean(L, 3, false); + bool force = getBoolean(L, 4, false); + if (g_game.placeCreature(monster, position, extended, force)) { + pushUserdata(L, monster); + setMetatable(L, -1, "Monster"); + } else { + delete monster; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGameCreateNpc(lua_State* L) +{ + // Game.createNpc(npcName, position[, extended = false[, force = false]]) + Npc* npc = Npc::createNpc(getString(L, 1)); + if (!npc) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + bool extended = getBoolean(L, 3, false); + bool force = getBoolean(L, 4, false); + if (g_game.placeCreature(npc, position, extended, force)) { + pushUserdata(L, npc); + setMetatable(L, -1, "Npc"); + } else { + delete npc; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGameCreateTile(lua_State* L) +{ + // Game.createTile(x, y, z[, isDynamic = false]) + // Game.createTile(position[, isDynamic = false]) + Position position; + bool isDynamic; + if (isTable(L, 1)) { + position = getPosition(L, 1); + isDynamic = getBoolean(L, 2, false); + } else { + position.x = getNumber(L, 1); + position.y = getNumber(L, 2); + position.z = getNumber(L, 3); + isDynamic = getBoolean(L, 4, false); + } + + Tile* tile = g_game.map.getTile(position); + if (!tile) { + if (isDynamic) { + tile = new DynamicTile(position.x, position.y, position.z); + } else { + tile = new StaticTile(position.x, position.y, position.z); + } + + g_game.map.setTile(position, tile); + } + + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + return 1; +} + +int LuaScriptInterface::luaGameStartRaid(lua_State* L) +{ + // Game.startRaid(raidName) + const std::string& raidName = getString(L, 1); + + Raid* raid = g_game.raids.getRaidByName(raidName); + if (!raid || !raid->isLoaded()) { + lua_pushnil(L); + return 1; + } + + if (g_game.raids.getRunning()) { + lua_pushnil(L); + return 1; + } + + g_game.raids.setRunning(raid); + raid->startRaid(); + lua_pushnumber(L, RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaGameReload(lua_State* L) +{ + // Game.reload(reloadType) + ReloadTypes_t reloadType = getNumber(L, 1); + if (!reloadType) { + lua_pushnil(L); + return 1; + } + + if (reloadType == RELOAD_TYPE_GLOBAL) { + pushBoolean(L, g_luaEnvironment.loadFile("data/global.lua") == 0); + } + else { + pushBoolean(L, g_game.reload(reloadType)); + } + lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); + return 1; +} + +// Variant +int LuaScriptInterface::luaVariantCreate(lua_State* L) +{ + // Variant(number or string or position or thing) + LuaVariant variant; + if (isUserdata(L, 2)) { + if (Thing* thing = getThing(L, 2)) { + variant.type = VARIANT_TARGETPOSITION; + variant.pos = thing->getPosition(); + } + } else if (isTable(L, 2)) { + variant.type = VARIANT_POSITION; + variant.pos = getPosition(L, 2); + } else if (isNumber(L, 2)) { + variant.type = VARIANT_NUMBER; + variant.number = getNumber(L, 2); + } else if (isString(L, 2)) { + variant.type = VARIANT_STRING; + variant.text = getString(L, 2); + } + pushVariant(L, variant); + return 1; +} + +int LuaScriptInterface::luaVariantGetNumber(lua_State* L) +{ + // Variant:getNumber() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_NUMBER) { + lua_pushnumber(L, variant.number); + } else { + lua_pushnumber(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaVariantGetString(lua_State* L) +{ + // Variant:getString() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_STRING) { + pushString(L, variant.text); + } else { + pushString(L, std::string()); + } + return 1; +} + +int LuaScriptInterface::luaVariantGetPosition(lua_State* L) +{ + // Variant:getPosition() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_POSITION || variant.type == VARIANT_TARGETPOSITION) { + pushPosition(L, variant.pos); + } else { + pushPosition(L, Position()); + } + return 1; +} + +// Position +int LuaScriptInterface::luaPositionCreate(lua_State* L) +{ + // Position([x = 0[, y = 0[, z = 0[, stackpos = 0]]]]) + // Position([position]) + if (lua_gettop(L) <= 1) { + pushPosition(L, Position()); + return 1; + } + + int32_t stackpos; + if (isTable(L, 2)) { + const Position& position = getPosition(L, 2, stackpos); + pushPosition(L, position, stackpos); + } else { + uint16_t x = getNumber(L, 2, 0); + uint16_t y = getNumber(L, 3, 0); + uint8_t z = getNumber(L, 4, 0); + stackpos = getNumber(L, 5, 0); + + pushPosition(L, Position(x, y, z), stackpos); + } + return 1; +} + +int LuaScriptInterface::luaPositionAdd(lua_State* L) +{ + // positionValue = position + positionEx + int32_t stackpos; + const Position& position = getPosition(L, 1, stackpos); + + Position positionEx; + if (stackpos == 0) { + positionEx = getPosition(L, 2, stackpos); + } else { + positionEx = getPosition(L, 2); + } + + pushPosition(L, position + positionEx, stackpos); + return 1; +} + +int LuaScriptInterface::luaPositionSub(lua_State* L) +{ + // positionValue = position - positionEx + int32_t stackpos; + const Position& position = getPosition(L, 1, stackpos); + + Position positionEx; + if (stackpos == 0) { + positionEx = getPosition(L, 2, stackpos); + } else { + positionEx = getPosition(L, 2); + } + + pushPosition(L, position - positionEx, stackpos); + return 1; +} + +int LuaScriptInterface::luaPositionCompare(lua_State* L) +{ + // position == positionEx + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + pushBoolean(L, position == positionEx); + return 1; +} + +int LuaScriptInterface::luaPositionGetDistance(lua_State* L) +{ + // position:getDistance(positionEx) + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + lua_pushnumber(L, std::max( + std::max( + std::abs(Position::getDistanceX(position, positionEx)), + std::abs(Position::getDistanceY(position, positionEx)) + ), + std::abs(Position::getDistanceZ(position, positionEx)) + )); + return 1; +} + +int LuaScriptInterface::luaPositionIsSightClear(lua_State* L) +{ + // position:isSightClear(positionEx[, sameFloor = true]) + bool sameFloor = getBoolean(L, 3, true); + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + pushBoolean(L, g_game.isSightClear(position, positionEx, sameFloor)); + return 1; +} + +int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) +{ + // position:sendMagicEffect(magicEffect[, player = nullptr]) + SpectatorVec list; + if (lua_gettop(L) >= 3) { + Player* player = getPlayer(L, 3); + if (player) { + list.insert(player); + } + } + + MagicEffectClasses magicEffect = getNumber(L, 2); + const Position& position = getPosition(L, 1); + if (!list.empty()) { + Game::addMagicEffect(list, position, magicEffect); + } else { + g_game.addMagicEffect(position, magicEffect); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) +{ + // position:sendDistanceEffect(positionEx, distanceEffect[, player = nullptr]) + SpectatorVec list; + if (lua_gettop(L) >= 4) { + Player* player = getPlayer(L, 4); + if (player) { + list.insert(player); + } + } + + ShootType_t distanceEffect = getNumber(L, 3); + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + if (!list.empty()) { + Game::addDistanceEffect(list, position, positionEx, distanceEffect); + } else { + g_game.addDistanceEffect(position, positionEx, distanceEffect); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPositionSendMonsterSay(lua_State * L) +{ + // position:sendMonsterSay(text) + const std::string& text = getString(L, 2); + const Position& position = getPosition(L, 1); + g_game.addMonsterSayText(position, text); + pushBoolean(L, true); + return 1; +} + +// Tile +int LuaScriptInterface::luaTileCreate(lua_State* L) +{ + // Tile(x, y, z) + // Tile(position) + Tile* tile; + if (isTable(L, 2)) { + tile = g_game.map.getTile(getPosition(L, 2)); + } else { + uint8_t z = getNumber(L, 4); + uint16_t y = getNumber(L, 3); + uint16_t x = getNumber(L, 2); + tile = g_game.map.getTile(x, y, z); + } + + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetPosition(lua_State* L) +{ + // tile:getPosition() + Tile* tile = getUserdata(L, 1); + if (tile) { + pushPosition(L, tile->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetGround(lua_State* L) +{ + // tile:getGround() + Tile* tile = getUserdata(L, 1); + if (tile && tile->getGround()) { + pushUserdata(L, tile->getGround()); + setItemMetatable(L, -1, tile->getGround()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThing(lua_State* L) +{ + // tile:getThing(index) + int32_t index = getNumber(L, 2); + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = tile->getThing(index); + if (!thing) { + lua_pushnil(L); + return 1; + } + + if (Creature* creature = thing->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else if (Item* item = thing->getItem()) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThingCount(lua_State* L) +{ + // tile:getThingCount() + Tile* tile = getUserdata(L, 1); + if (tile) { + lua_pushnumber(L, tile->getThingCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopVisibleThing(lua_State* L) +{ + // tile:getTopVisibleThing(creature) + Creature* creature = getCreature(L, 2); + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = tile->getTopVisibleThing(creature); + if (!thing) { + lua_pushnil(L); + return 1; + } + + if (Creature* visibleCreature = thing->getCreature()) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else if (Item* visibleItem = thing->getItem()) { + pushUserdata(L, visibleItem); + setItemMetatable(L, -1, visibleItem); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopTopItem(lua_State* L) +{ + // tile:getTopTopItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getTopTopItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopDownItem(lua_State* L) +{ + // tile:getTopDownItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getTopDownItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetFieldItem(lua_State* L) +{ + // tile:getFieldItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getFieldItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemById(lua_State* L) +{ + // tile:getItemById(itemId[, subType = -1]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + int32_t subType = getNumber(L, 3, -1); + + Item* item = g_game.findItemOfType(tile, itemId, false, subType); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemByType(lua_State* L) +{ + // tile:getItemByType(itemType) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + bool found; + + ItemTypes_t itemType = getNumber(L, 2); + switch (itemType) { + case ITEM_TYPE_TELEPORT: + found = tile->hasFlag(TILESTATE_TELEPORT); + break; + case ITEM_TYPE_MAGICFIELD: + found = tile->hasFlag(TILESTATE_MAGICFIELD); + break; + case ITEM_TYPE_MAILBOX: + found = tile->hasFlag(TILESTATE_MAILBOX); + break; + case ITEM_TYPE_BED: + found = tile->hasFlag(TILESTATE_BED); + break; + case ITEM_TYPE_DEPOT: + found = tile->hasFlag(TILESTATE_DEPOT); + break; + default: + found = true; + break; + } + + if (!found) { + lua_pushnil(L); + return 1; + } + + if (Item* item = tile->getGround()) { + const ItemType& it = Item::items[item->getID()]; + if (it.type == itemType) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; + } + } + + if (const TileItemVector* items = tile->getItemList()) { + for (Item* item : *items) { + const ItemType& it = Item::items[item->getID()]; + if (it.type == itemType) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; + } + } + } + + lua_pushnil(L); + return 1; +} + +int LuaScriptInterface::luaTileGetItemByTopOrder(lua_State* L) +{ + // tile:getItemByTopOrder(topOrder) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + int32_t topOrder = getNumber(L, 2); + + Item* item = tile->getItemByTopOrder(topOrder); + if (!item) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; +} + +int LuaScriptInterface::luaTileGetItemCountById(lua_State* L) +{ + // tile:getItemCountById(itemId[, subType = -1]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + int32_t subType = getNumber(L, 3, -1); + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + lua_pushnumber(L, tile->getItemTypeCount(itemId, subType)); + return 1; +} + +int LuaScriptInterface::luaTileGetBottomCreature(lua_State* L) +{ + // tile:getBottomCreature() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + const Creature* creature = tile->getBottomCreature(); + if (!creature) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + return 1; +} + +int LuaScriptInterface::luaTileGetTopCreature(lua_State* L) +{ + // tile:getTopCreature() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = tile->getTopCreature(); + if (!creature) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + return 1; +} + +int LuaScriptInterface::luaTileGetBottomVisibleCreature(lua_State* L) +{ + // tile:getBottomVisibleCreature(creature) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Creature* visibleCreature = tile->getBottomVisibleCreature(creature); + if (visibleCreature) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopVisibleCreature(lua_State* L) +{ + // tile:getTopVisibleCreature(creature) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* visibleCreature = tile->getTopVisibleCreature(creature); + if (visibleCreature) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItems(lua_State* L) +{ + // tile:getItems() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + TileItemVector* itemVector = tile->getItemList(); + if (!itemVector) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, itemVector->size(), 0); + + int index = 0; + for (Item* item : *itemVector) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemCount(lua_State* L) +{ + // tile:getItemCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getItemCount()); + return 1; +} + +int LuaScriptInterface::luaTileGetDownItemCount(lua_State* L) +{ + // tile:getDownItemCount() + Tile* tile = getUserdata(L, 1); + if (tile) { + lua_pushnumber(L, tile->getDownItemCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopItemCount(lua_State* L) +{ + // tile:getTopItemCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getTopItemCount()); + return 1; +} + +int LuaScriptInterface::luaTileGetCreatures(lua_State* L) +{ + // tile:getCreatures() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + CreatureVector* creatureVector = tile->getCreatures(); + if (!creatureVector) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creatureVector->size(), 0); + + int index = 0; + for (Creature* creature : *creatureVector) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaTileGetCreatureCount(lua_State* L) +{ + // tile:getCreatureCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getCreatureCount()); + return 1; +} + +int LuaScriptInterface::luaTileHasProperty(lua_State* L) +{ + // tile:hasProperty(property[, item]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item; + if (lua_gettop(L) >= 3) { + item = getUserdata(L, 3); + } else { + item = nullptr; + } + + ITEMPROPERTY property = getNumber(L, 2); + if (item) { + pushBoolean(L, tile->hasProperty(item, property)); + } else { + pushBoolean(L, tile->hasProperty(property)); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThingIndex(lua_State* L) +{ + // tile:getThingIndex(thing) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = getThing(L, 2); + if (thing) { + lua_pushnumber(L, tile->getThingIndex(thing)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileHasFlag(lua_State* L) +{ + // tile:hasFlag(flag) + Tile* tile = getUserdata(L, 1); + if (tile) { + tileflags_t flag = getNumber(L, 2); + pushBoolean(L, tile->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileQueryAdd(lua_State* L) +{ + // tile:queryAdd(thing[, flags]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = getThing(L, 2); + if (thing) { + uint32_t flags = getNumber(L, 3, 0); + lua_pushnumber(L, tile->queryAdd(0, *thing, 1, flags)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetHouse(lua_State* L) +{ + // tile:getHouse() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + if (HouseTile* houseTile = dynamic_cast(tile)) { + pushUserdata(L, houseTile->getHouse()); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +// NetworkMessage +int LuaScriptInterface::luaNetworkMessageCreate(lua_State* L) +{ + // NetworkMessage() + pushUserdata(L, new NetworkMessage); + setMetatable(L, -1, "NetworkMessage"); + return 1; +} + +int LuaScriptInterface::luaNetworkMessageDelete(lua_State* L) +{ + NetworkMessage** messagePtr = getRawUserdata(L, 1); + if (messagePtr && *messagePtr) { + delete *messagePtr; + *messagePtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaNetworkMessageGetByte(lua_State* L) +{ + // networkMessage:getByte() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->getByte()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU16(lua_State* L) +{ + // networkMessage:getU16() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU32(lua_State* L) +{ + // networkMessage:getU32() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU64(lua_State* L) +{ + // networkMessage:getU64() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetString(lua_State* L) +{ + // networkMessage:getString() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + pushString(L, message->getString()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetPosition(lua_State* L) +{ + // networkMessage:getPosition() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + pushPosition(L, message->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddByte(lua_State* L) +{ + // networkMessage:addByte(number) + uint8_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addByte(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU16(lua_State* L) +{ + // networkMessage:addU16(number) + uint16_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU32(lua_State* L) +{ + // networkMessage:addU32(number) + uint32_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU64(lua_State* L) +{ + // networkMessage:addU64(number) + uint64_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddString(lua_State* L) +{ + // networkMessage:addString(string) + const std::string& string = getString(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addString(string); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddPosition(lua_State* L) +{ + // networkMessage:addPosition(position) + const Position& position = getPosition(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addPosition(position); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddDouble(lua_State* L) +{ + // networkMessage:addDouble(number) + double number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addDouble(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddItem(lua_State* L) +{ + // networkMessage:addItem(item) + Item* item = getUserdata(L, 2); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addItem(item); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddItemId(lua_State* L) +{ + // networkMessage:addItemId(itemId) + NetworkMessage* message = getUserdata(L, 1); + if (!message) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + message->addItemId(itemId); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaNetworkMessageReset(lua_State* L) +{ + // networkMessage:reset() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->reset(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageSkipBytes(lua_State* L) +{ + // networkMessage:skipBytes(number) + int16_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->skipBytes(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageSendToPlayer(lua_State* L) +{ + // networkMessage:sendToPlayer(player) + NetworkMessage* message = getUserdata(L, 1); + if (!message) { + lua_pushnil(L); + return 1; + } + + Player* player = getPlayer(L, 2); + if (player) { + player->sendNetworkMessage(*message); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushnil(L); + } + return 1; +} + +// Item +int LuaScriptInterface::luaItemCreate(lua_State* L) +{ + // Item(uid) + uint32_t id = getNumber(L, 2); + + Item* item = getScriptEnv()->getItemByUID(id); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemIsItem(lua_State* L) +{ + // item:isItem() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaItemGetParent(lua_State* L) +{ + // item:getParent() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Cylinder* parent = item->getParent(); + if (!parent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, parent); + return 1; +} + +int LuaScriptInterface::luaItemGetTopParent(lua_State* L) +{ + // item:getTopParent() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Cylinder* topParent = item->getTopParent(); + if (!topParent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, topParent); + return 1; +} + +int LuaScriptInterface::luaItemGetId(lua_State* L) +{ + // item:getId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemClone(lua_State* L) +{ + // item:clone() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Item* clone = item->clone(); + if (!clone) { + lua_pushnil(L); + return 1; + } + + getScriptEnv()->addTempItem(clone); + clone->setParent(VirtualCylinder::virtualCylinder); + + pushUserdata(L, clone); + setItemMetatable(L, -1, clone); + return 1; +} + +int LuaScriptInterface::luaItemSplit(lua_State* L) +{ + // item:split([count = 1]) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item* item = *itemPtr; + if (!item || !item->isStackable()) { + lua_pushnil(L); + return 1; + } + + uint16_t count = std::min(getNumber(L, 2, 1), item->getItemCount()); + uint16_t diff = item->getItemCount() - count; + + Item* splitItem = item->clone(); + if (!splitItem) { + lua_pushnil(L); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + uint32_t uid = env->addThing(item); + + Item* newItem = g_game.transformItem(item, item->getID(), diff); + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertItem(uid, newItem); + } + + *itemPtr = newItem; + + splitItem->setParent(VirtualCylinder::virtualCylinder); + env->addTempItem(splitItem); + + pushUserdata(L, splitItem); + setItemMetatable(L, -1, splitItem); + return 1; +} + +int LuaScriptInterface::luaItemRemove(lua_State* L) +{ + // item:remove([count = -1]) + Item* item = getUserdata(L, 1); + if (item) { + int32_t count = getNumber(L, 2, -1); + pushBoolean(L, g_game.internalRemoveItem(item, count) == RETURNVALUE_NOERROR); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetMovementId(lua_State* L) +{ + // item:getMovementId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getMovementId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetActionId(lua_State* L) +{ + // item:getActionId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getActionId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetActionId(lua_State* L) +{ + // item:setActionId(actionId) + uint16_t actionId = getNumber(L, 2); + Item* item = getUserdata(L, 1); + if (item) { + item->setActionId(actionId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetMovementId(lua_State* L) +{ + // item:setMovementId(movementId) + uint16_t movementId = getNumber(L, 2); + Item* item = getUserdata(L, 1); + if (item) { + item->setMovementID(movementId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetUniqueId(lua_State * L) +{ + // item:getUniqueId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, getScriptEnv()->addThing(item)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetCount(lua_State* L) +{ + // item:getCount() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getItemCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetCharges(lua_State* L) +{ + // item:getCharges() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getCharges()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetFluidType(lua_State* L) +{ + // item:getFluidType() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getFluidType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetWeight(lua_State* L) +{ + // item:getWeight() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getWeight()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetSubType(lua_State* L) +{ + // item:getSubType() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getSubType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetName(lua_State* L) +{ + // item:getName() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetPluralName(lua_State* L) +{ + // item:getPluralName() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getPluralName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetArticle(lua_State* L) +{ + // item:getArticle() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getArticle()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetPosition(lua_State* L) +{ + // item:getPosition() + Item* item = getUserdata(L, 1); + if (item) { + pushPosition(L, item->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetTile(lua_State* L) +{ + // item:getTile() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Tile* tile = item->getTile(); + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemHasAttribute(lua_State* L) +{ + // item:hasAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + pushBoolean(L, item->hasAttribute(attribute)); + return 1; +} + +int LuaScriptInterface::luaItemGetAttribute(lua_State* L) +{ + // item:getAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + if (ItemAttributes::isIntAttrType(attribute)) { + lua_pushnumber(L, item->getIntAttr(attribute)); + } else if (ItemAttributes::isStrAttrType(attribute)) { + pushString(L, item->getStrAttr(attribute)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetAttribute(lua_State* L) +{ + // item:setAttribute(key, value) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + if (ItemAttributes::isIntAttrType(attribute)) { + item->setIntAttr(attribute, getNumber(L, 3)); + pushBoolean(L, true); + } else if (ItemAttributes::isStrAttrType(attribute)) { + item->setStrAttr(attribute, getString(L, 3)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemRemoveAttribute(lua_State* L) +{ + // item:removeAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + item->removeAttribute(attribute); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaItemMoveTo(lua_State* L) +{ + // item:moveTo(position or cylinder) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item* item = *itemPtr; + if (!item || item->isRemoved()) { + lua_pushnil(L); + return 1; + } + + Cylinder* toCylinder; + if (isUserdata(L, 2)) { + const LuaDataType type = getUserdataType(L, 2); + switch (type) { + case LuaData_Container: + toCylinder = getUserdata(L, 2); + break; + case LuaData_Player: + toCylinder = getUserdata(L, 2); + break; + case LuaData_Tile: + toCylinder = getUserdata(L, 2); + break; + default: + toCylinder = nullptr; + break; + } + } else { + toCylinder = g_game.map.getTile(getPosition(L, 2)); + } + + if (!toCylinder) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() == toCylinder) { + pushBoolean(L, true); + return 1; + } + + if (item->getParent() == VirtualCylinder::virtualCylinder) { + pushBoolean(L, g_game.internalAddItem(toCylinder, item) == RETURNVALUE_NOERROR); + } else { + Item* moveItem = nullptr; + ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + if (moveItem) { + *itemPtr = moveItem; + } + pushBoolean(L, ret == RETURNVALUE_NOERROR); + } + return 1; +} + +int LuaScriptInterface::luaItemTransform(lua_State* L) +{ + // item:transform(itemId[, count/subType = -1]) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item*& item = *itemPtr; + if (!item) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + pushBoolean(L, true); + return 1; + } + + const ItemType& it = Item::items[itemId]; + if (it.stackable) { + subType = std::min(subType, 100); + } + + ScriptEnvironment* env = getScriptEnv(); + uint32_t uid = env->addThing(item); + + Item* newItem = g_game.transformItem(item, itemId, subType); + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertItem(uid, newItem); + } + + item = newItem; + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaItemDecay(lua_State* L) +{ + // item:decay() + Item* item = getUserdata(L, 1); + if (item) { + g_game.startDecay(item); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetDescription(lua_State* L) +{ + // item:getDescription(distance) + Item* item = getUserdata(L, 1); + if (item) { + int32_t distance = getNumber(L, 2); + pushString(L, item->getDescription(distance)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemHasProperty(lua_State* L) +{ + // item:hasProperty(property) + Item* item = getUserdata(L, 1); + if (item) { + ITEMPROPERTY property = getNumber(L, 2); + pushBoolean(L, item->hasProperty(property)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Container +int LuaScriptInterface::luaContainerCreate(lua_State* L) +{ + // Container(uid) + uint32_t id = getNumber(L, 2); + + Container* container = getScriptEnv()->getContainerByUID(id); + if (container) { + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetSize(lua_State* L) +{ + // container:getSize() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetCapacity(lua_State* L) +{ + // container:getCapacity() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->capacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetEmptySlots(lua_State* L) +{ + // container:getEmptySlots([recursive = false]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint32_t slots = container->capacity() - container->size(); + bool recursive = getBoolean(L, 2, false); + if (recursive) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if (Container* tmpContainer = (*it)->getContainer()) { + slots += tmpContainer->capacity() - tmpContainer->size(); + } + } + } + lua_pushnumber(L, slots); + return 1; +} + +int LuaScriptInterface::luaContainerGetItemHoldingCount(lua_State* L) +{ + // container:getItemHoldingCount() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->getItemHoldingCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetItem(lua_State* L) +{ + // container:getItem(index) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint32_t index = getNumber(L, 2); + Item* item = container->getItemByIndex(index); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerHasItem(lua_State* L) +{ + // container:hasItem(item) + Item* item = getUserdata(L, 2); + Container* container = getUserdata(L, 1); + if (container) { + pushBoolean(L, container->isHoldingItem(item)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerAddItem(lua_State* L) +{ + // container:addItem(itemId[, count/subType = 1[, index = INDEX_WHEREEVER[, flags = 0]]]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + uint32_t subType = getNumber(L, 3, 1); + + Item* item = Item::CreateItem(itemId, std::min(subType, 100)); + if (!item) { + lua_pushnil(L); + return 1; + } + + int32_t index = getNumber(L, 4, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 5, 0); + + ReturnValue ret = g_game.internalAddItem(container, item, index, flags); + if (ret == RETURNVALUE_NOERROR) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + delete item; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerAddItemEx(lua_State* L) +{ + // container:addItemEx(item[, index = INDEX_WHEREEVER[, flags = 0]]) + Item* item = getUserdata(L, 2); + if (!item) { + lua_pushnil(L); + return 1; + } + + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushnil(L); + return 1; + } + + int32_t index = getNumber(L, 3, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 4, 0); + ReturnValue ret = g_game.internalAddItem(container, item, index, flags); + if (ret == RETURNVALUE_NOERROR) { + ScriptEnvironment::removeTempItem(item); + } + lua_pushnumber(L, ret); + return 1; +} + +int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) +{ + // container:getItemCountById(itemId[, subType = -1]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + lua_pushnumber(L, container->getItemTypeCount(itemId, subType)); + return 1; +} + +// Teleport +int LuaScriptInterface::luaTeleportCreate(lua_State* L) +{ + // Teleport(uid) + uint32_t id = getNumber(L, 2); + + Item* item = getScriptEnv()->getItemByUID(id); + if (item && item->getTeleport()) { + pushUserdata(L, item); + setMetatable(L, -1, "Teleport"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTeleportGetDestination(lua_State* L) +{ + // teleport:getDestination() + Teleport* teleport = getUserdata(L, 1); + if (teleport) { + pushPosition(L, teleport->getDestPos()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTeleportSetDestination(lua_State* L) +{ + // teleport:setDestination(position) + Teleport* teleport = getUserdata(L, 1); + if (teleport) { + teleport->setDestPos(getPosition(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +// Creature +int LuaScriptInterface::luaCreatureCreate(lua_State* L) +{ + // Creature(id or name or userdata) + Creature* creature; + if (isNumber(L, 2)) { + creature = g_game.getCreatureByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + creature = g_game.getCreatureByName(getString(L, 2)); + } else if (isUserdata(L, 2)) { + LuaDataType type = getUserdataType(L, 2); + if (type != LuaData_Player && type != LuaData_Monster && type != LuaData_Npc) { + lua_pushnil(L); + return 1; + } + creature = getUserdata(L, 2); + } else { + creature = nullptr; + } + + if (creature) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetEvents(lua_State* L) +{ + // creature:getEvents(type) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + CreatureEventType_t eventType = getNumber(L, 2); + const auto& eventList = creature->getCreatureEvents(eventType); + lua_createtable(L, eventList.size(), 0); + + int index = 0; + for (CreatureEvent* event : eventList) { + pushString(L, event->getName()); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRegisterEvent(lua_State* L) +{ + // creature:registerEvent(name) + Creature* creature = getUserdata(L, 1); + if (creature) { + const std::string& name = getString(L, 2); + pushBoolean(L, creature->registerCreatureEvent(name)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureUnregisterEvent(lua_State* L) +{ + // creature:unregisterEvent(name) + const std::string& name = getString(L, 2); + Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->unregisterCreatureEvent(name)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureIsRemoved(lua_State* L) +{ + // creature:isRemoved() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->isRemoved()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureIsCreature(lua_State* L) +{ + // creature:isCreature() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaCreatureIsInGhostMode(lua_State* L) +{ + // creature:isInGhostMode() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->isInGhostMode()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureCanSee(lua_State* L) +{ + // creature:canSee(position) + const Creature* creature = getUserdata(L, 1); + if (creature) { + const Position& position = getPosition(L, 2); + pushBoolean(L, creature->canSee(position)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureCanSeeCreature(lua_State* L) +{ + // creature:canSeeCreature(creature) + const Creature* creature = getUserdata(L, 1); + if (creature) { + const Creature* otherCreature = getCreature(L, 2); + pushBoolean(L, creature->canSeeCreature(otherCreature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetParent(lua_State* L) +{ + // creature:getParent() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Cylinder* parent = creature->getParent(); + if (!parent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, parent); + return 1; +} + +int LuaScriptInterface::luaCreatureGetId(lua_State* L) +{ + // creature:getId() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetName(lua_State* L) +{ + // creature:getName() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushString(L, creature->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetTarget(lua_State* L) +{ + // creature:getTarget() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* target = creature->getAttackedCreature(); + if (target) { + pushUserdata(L, target); + setCreatureMetatable(L, -1, target); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetTarget(lua_State* L) +{ + // creature:setTarget(target) + Creature* creature = getUserdata(L, 1); + if (creature) { + Creature* target = getCreature(L, 2); + pushBoolean(L, creature->setAttackedCreature(target)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetFollowCreature(lua_State* L) +{ + // creature:getFollowCreature() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* followCreature = creature->getFollowCreature(); + if (followCreature) { + pushUserdata(L, followCreature); + setCreatureMetatable(L, -1, followCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetFollowCreature(lua_State* L) +{ + // creature:setFollowCreature(followedCreature) + Creature* creature = getUserdata(L, 1); + if (creature) { + Creature* followCreature = getCreature(L, 2); + pushBoolean(L, creature->setFollowCreature(followCreature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetMaster(lua_State* L) +{ + // creature:getMaster() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* master = creature->getMaster(); + if (!master) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, master); + setCreatureMetatable(L, -1, master); + return 1; +} + +int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) +{ + // creature:setMaster(master) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* master = getCreature(L, 2); + if (master) { + pushBoolean(L, creature->convinceCreature(master)); + } else { + master = creature->getMaster(); + if (master) { + master->removeSummon(creature); + creature->incrementReferenceCounter(); + creature->setDropLoot(true); + } + pushBoolean(L, true); + } + + return 1; +} + +int LuaScriptInterface::luaCreatureGetLight(lua_State* L) +{ + // creature:getLight() + const Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + LightInfo lightInfo = creature->getCreatureLight(); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); + return 2; +} + +int LuaScriptInterface::luaCreatureSetLight(lua_State* L) +{ + // creature:setLight(color, level) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + LightInfo light; + light.color = getNumber(L, 2); + light.level = getNumber(L, 3); + creature->setCreatureLight(light); + g_game.changeLight(creature); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureGetSpeed(lua_State* L) +{ + // creature:getSpeed() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetBaseSpeed(lua_State* L) +{ + // creature:getBaseSpeed() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getBaseSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureChangeSpeed(lua_State* L) +{ + // creature:changeSpeed(delta) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + int32_t delta = getNumber(L, 2); + g_game.changeSpeed(creature, delta); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureSetDropLoot(lua_State* L) +{ + // creature:setDropLoot(doDrop) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setDropLoot(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPosition(lua_State* L) +{ + // creature:getPosition() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushPosition(L, creature->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetTile(lua_State* L) +{ + // creature:getTile() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Tile* tile = creature->getTile(); + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDirection(lua_State* L) +{ + // creature:getDirection() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getDirection()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetDirection(lua_State* L) +{ + // creature:setDirection(direction) + Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, g_game.internalCreatureTurn(creature, getNumber(L, 2))); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetHealth(lua_State* L) +{ + // creature:getHealth() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getHealth()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) +{ + // creature:addHealth(healthChange) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + CombatDamage damage; + damage.value = getNumber(L, 2); + if (damage.value >= 0) { + damage.type = COMBAT_HEALING; + } else { + damage.type = COMBAT_UNDEFINEDDAMAGE; + } + pushBoolean(L, g_game.combatChangeHealth(nullptr, creature, damage)); + return 1; +} + +int LuaScriptInterface::luaCreatureGetMaxHealth(lua_State* L) +{ + // creature:getMaxHealth() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getMaxHealth()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetMaxHealth(lua_State* L) +{ + // creature:setMaxHealth(maxHealth) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + creature->healthMax = getNumber(L, 2); + creature->health = std::min(creature->health, creature->healthMax); + g_game.addCreatureHealth(creature); + + Player* player = creature->getPlayer(); + if (player) { + player->sendStats(); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureSetHiddenHealth(lua_State* L) +{ + // creature:setHiddenHealth(hide) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setHiddenHealth(getBoolean(L, 2)); + g_game.addCreatureHealth(creature); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetSkull(lua_State* L) +{ + // creature:getSkull() + Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getSkull()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetSkull(lua_State* L) +{ + // creature:setSkull(skull) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setSkull(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetOutfit(lua_State* L) +{ + // creature:getOutfit() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushOutfit(L, creature->getCurrentOutfit()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetOutfit(lua_State* L) +{ + // creature:setOutfit(outfit) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->defaultOutfit = getOutfit(L, 2); + g_game.internalCreatureChangeOutfit(creature, creature->defaultOutfit); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetCondition(lua_State* L) +{ + // creature:getCondition(conditionType[, conditionId = CONDITIONID_COMBAT[, subId = 0]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + uint32_t subId = getNumber(L, 4, 0); + + Condition* condition = creature->getCondition(conditionType, conditionId, subId); + if (condition) { + pushUserdata(L, condition); + setWeakMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureAddCondition(lua_State* L) +{ + // creature:addCondition(condition[, force = false]) + Creature* creature = getUserdata(L, 1); + Condition* condition = getUserdata(L, 2); + if (creature && condition) { + bool force = getBoolean(L, 3, false); + pushBoolean(L, creature->addCondition(condition->clone(), force)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) +{ + // creature:removeCondition(conditionType[, conditionId = CONDITIONID_COMBAT[, subId = 0[, force = false]]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + uint32_t subId = getNumber(L, 4, 0); + Condition* condition = creature->getCondition(conditionType, conditionId, subId); + if (condition) { + bool force = getBoolean(L, 5, true); + creature->removeCondition(condition, force); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemove(lua_State* L) +{ + // creature:remove() + Creature** creaturePtr = getRawUserdata(L, 1); + if (!creaturePtr) { + lua_pushnil(L); + return 1; + } + + Creature* creature = *creaturePtr; + if (!creature) { + lua_pushnil(L); + return 1; + } + + Player* player = creature->getPlayer(); + if (player) { + player->kickPlayer(true); + } else { + g_game.removeCreature(creature); + } + + *creaturePtr = nullptr; + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) +{ + // creature:teleportTo(position[, pushMovement = false]) + bool pushMovement = getBoolean(L, 3, false); + + const Position& position = getPosition(L, 2); + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Position oldPosition = creature->getPosition(); + if (g_game.internalTeleport(creature, position, pushMovement) != RETURNVALUE_NOERROR) { + pushBoolean(L, false); + return 1; + } + + if (!pushMovement) { + if (oldPosition.x == position.x) { + if (oldPosition.y < position.y) { + g_game.internalCreatureTurn(creature, DIRECTION_SOUTH); + } else { + g_game.internalCreatureTurn(creature, DIRECTION_NORTH); + } + } else if (oldPosition.x > position.x) { + g_game.internalCreatureTurn(creature, DIRECTION_WEST); + } else if (oldPosition.x < position.x) { + g_game.internalCreatureTurn(creature, DIRECTION_EAST); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureSay(lua_State* L) +{ + // creature:say(text, type[, ghost = false[, target = nullptr[, position]]]) + int parameters = lua_gettop(L); + + Position position; + if (parameters >= 6) { + position = getPosition(L, 6); + if (!position.x || !position.y) { + reportErrorFunc("Invalid position specified."); + pushBoolean(L, false); + return 1; + } + } + + Creature* target = nullptr; + if (parameters >= 5) { + target = getCreature(L, 5); + } + + bool ghost = getBoolean(L, 4, false); + + SpeakClasses type = getNumber(L, 3); + const std::string& text = getString(L, 2); + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + SpectatorVec list; + if (target) { + list.insert(target); + } + + if (position.x != 0) { + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list, &position)); + } else { + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list)); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDamageMap(lua_State* L) +{ + // creature:getDamageMap() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creature->damageMap.size(), 0); + for (auto damageEntry : creature->damageMap) { + lua_createtable(L, 0, 2); + setField(L, "total", damageEntry.second.total); + setField(L, "ticks", damageEntry.second.ticks); + lua_rawseti(L, -2, damageEntry.first); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetSummons(lua_State* L) +{ + // creature:getSummons() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creature->getSummonCount(), 0); + + int index = 0; + for (Creature* summon : creature->getSummons()) { + pushUserdata(L, summon); + setCreatureMetatable(L, -1, summon); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDescription(lua_State* L) +{ + // creature:getDescription(distance) + int32_t distance = getNumber(L, 2); + Creature* creature = getUserdata(L, 1); + if (creature) { + pushString(L, creature->getDescription(distance)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) +{ + // creature:getPathTo(pos[, minTargetDist = 0[, maxTargetDist = 1[, fullPathSearch = true[, clearSight = true[, maxSearchDist = 0]]]]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + + FindPathParams fpp; + fpp.minTargetDist = getNumber(L, 3, 0); + fpp.maxTargetDist = getNumber(L, 4, 1); + fpp.fullPathSearch = getBoolean(L, 5, fpp.fullPathSearch); + fpp.clearSight = getBoolean(L, 6, fpp.clearSight); + fpp.maxSearchDist = getNumber(L, 7, fpp.maxSearchDist); + + std::forward_list dirList; + if (creature->getPathTo(position, dirList, fpp)) { + lua_newtable(L); + + int index = 0; + for (Direction dir : dirList) { + lua_pushnumber(L, dir); + lua_rawseti(L, -2, ++index); + } + } else { + pushBoolean(L, false); + } + return 1; +} + +// Player +int LuaScriptInterface::luaPlayerCreate(lua_State* L) +{ + // Player(id or name or userdata) + Player* player; + if (isNumber(L, 2)) { + player = g_game.getPlayerByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + ReturnValue ret = g_game.getPlayerByNameWildcard(getString(L, 2), player); + if (ret != RETURNVALUE_NOERROR) { + lua_pushnil(L); + lua_pushnumber(L, ret); + return 2; + } + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Player) { + lua_pushnil(L); + return 1; + } + player = getUserdata(L, 2); + } else { + player = nullptr; + } + + if (player) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsPlayer(lua_State* L) +{ + // player:isPlayer() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + + +int LuaScriptInterface::luaPlayerGetGuid(lua_State* L) +{ + // player:getGuid() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getGUID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetIp(lua_State* L) +{ + // player:getIp() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getIP()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetAccountId(lua_State* L) +{ + // player:getAccountId() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getAccount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLastLoginSaved(lua_State* L) +{ + // player:getLastLoginSaved() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLastLoginSaved()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLastLogout(lua_State* L) +{ + // player:getLastLogout() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLastLogout()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasFlag(lua_State * L) +{ + // player:hasFlag(flag) + Player* player = getUserdata(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetAccountType(lua_State* L) +{ + // player:getAccountType() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getAccountType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetAccountType(lua_State* L) +{ + // player:setAccountType(accountType) + Player* player = getUserdata(L, 1); + if (player) { + player->accountType = getNumber(L, 2); + IOLoginData::setAccountType(player->getAccount(), player->accountType); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetCapacity(lua_State* L) +{ + // player:getCapacity() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getCapacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetCapacity(lua_State* L) +{ + // player:setCapacity(capacity) + Player* player = getUserdata(L, 1); + if (player) { + player->capacity = getNumber(L, 2); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetFreeCapacity(lua_State* L) +{ + // player:getFreeCapacity() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getFreeCapacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L) +{ + // player:getDepotChest(depotId[, autoCreate = false]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t depotId = getNumber(L, 2); + bool autoCreate = getBoolean(L, 3, false); + DepotLocker* depotLocker = player->getDepotLocker(depotId, autoCreate); + if (depotLocker) { + if (!depotLocker->getParent() && player->getTile()) { + depotLocker->setParent(player->getTile()); + } + + pushUserdata(L, depotLocker); + setItemMetatable(L, -1, depotLocker); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L) +{ + // player:getMurderTimestamps() + Player* player = getUserdata(L, 1); + if (player) { + lua_createtable(L, player->murderTimeStamps.size(), 0); + + uint32_t i = 1; + for (time_t currentMurderTimestamp : player->murderTimeStamps) { + lua_pushnumber(L, static_cast(currentMurderTimestamp)); + lua_rawseti(L, -2, ++i); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetPlayerKillerEnd(lua_State* L) +{ + // player:getPlayerKillerEnd() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getPlayerKillerEnd()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetPlayerKillerEnd(lua_State* L) +{ + // player:setPlayerKillerEnd(skullTime) + Player* player = getUserdata(L, 1); + if (player) { + player->setPlayerKillerEnd(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetDeathPenalty(lua_State* L) +{ + // player:getDeathPenalty() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, static_cast(player->getLostPercent() * 100)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetExperience(lua_State* L) +{ + // player:getExperience() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getExperience()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) +{ + // player:addExperience(experience[, sendText = false]) + Player* player = getUserdata(L, 1); + if (player) { + int64_t experience = getNumber(L, 2); + bool sendText = getBoolean(L, 3, false); + player->addExperience(nullptr, experience, sendText); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveExperience(lua_State* L) +{ + // player:removeExperience(experience) + Player* player = getUserdata(L, 1); + if (player) { + int64_t experience = getNumber(L, 2); + player->removeExperience(experience); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLevel(lua_State* L) +{ + // player:getLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMagicLevel(lua_State* L) +{ + // player:getMagicLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMagicLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMagicLevel(lua_State* L) +{ + // player:getBaseMagicLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getBaseMagicLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMana(lua_State* L) +{ + // player:getMana() + const Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMana()); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddMana(lua_State* L) +{ + // player:addMana(manaChange[, animationOnLoss = false]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + int32_t manaChange = getNumber(L, 2); + bool animationOnLoss = getBoolean(L, 3, false); + if (!animationOnLoss && manaChange < 0) { + player->changeMana(manaChange); + } + else { + CombatDamage damage; + damage.value = manaChange; + damage.origin = ORIGIN_NONE; + g_game.combatChangeMana(nullptr, player, damage); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetMaxMana(lua_State* L) +{ + // player:getMaxMana() + const Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMaxMana()); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetMaxMana(lua_State* L) +{ + // player:setMaxMana(maxMana) + Player* player = getPlayer(L, 1); + if (player) { + player->manaMax = getNumber(L, 2); + player->mana = std::min(player->mana, player->manaMax); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetManaSpent(lua_State* L) +{ + // player:getManaSpent() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSpentMana()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddManaSpent(lua_State* L) +{ + // player:addManaSpent(amount) + Player* player = getUserdata(L, 1); + if (player) { + player->addManaSpent(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMaxHealth(lua_State* L) +{ + // player:getBaseMaxHealth() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->healthMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMaxMana(lua_State* L) +{ + // player:getBaseMaxMana() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->manaMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillLevel(lua_State* L) +{ + // player:getSkillLevel(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetEffectiveSkillLevel(lua_State* L) +{ + // player:getEffectiveSkillLevel(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->getSkillLevel(skillType)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillPercent(lua_State* L) +{ + // player:getSkillPercent(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].percent); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillTries(lua_State* L) +{ + // player:getSkillTries(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].tries); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddSkillTries(lua_State* L) +{ + // player:addSkillTries(skillType, tries) + Player* player = getUserdata(L, 1); + if (player) { + skills_t skillType = getNumber(L, 2); + uint64_t tries = getNumber(L, 3); + player->addSkillAdvance(skillType, tries); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddOfflineTrainingTime(lua_State* L) +{ + // player:addOfflineTrainingTime(time) + Player* player = getUserdata(L, 1); + if (player) { + int32_t time = getNumber(L, 2); + player->addOfflineTrainingTime(time); + player->sendStats(); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + + +int LuaScriptInterface::luaPlayerGetOfflineTrainingTime(lua_State* L) +{ + // player:getOfflineTrainingTime() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getOfflineTrainingTime()); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime(lua_State* L) +{ + // player:removeOfflineTrainingTime(time) + Player* player = getUserdata(L, 1); + if (player) { + int32_t time = getNumber(L, 2); + player->removeOfflineTrainingTime(time); + player->sendStats(); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddOfflineTrainingTries(lua_State* L) +{ + // player:addOfflineTrainingTries(skillType, tries) + Player* player = getUserdata(L, 1); + if (player) { + skills_t skillType = getNumber(L, 2); + uint64_t tries = getNumber(L, 3); + pushBoolean(L, player->addOfflineTrainingTries(skillType, tries)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetOfflineTrainingSkill(lua_State* L) +{ + // player:getOfflineTrainingSkill() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getOfflineTrainingSkill()); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetOfflineTrainingSkill(lua_State* L) +{ + // player:setOfflineTrainingSkill(skillId) + Player* player = getUserdata(L, 1); + if (player) { + uint32_t skillId = getNumber(L, 2); + player->setOfflineTrainingSkill(skillId); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetItemCount(lua_State* L) +{ + // player:getItemCount(itemId[, subType = -1]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + lua_pushnumber(L, player->getItemTypeCount(itemId, subType)); + return 1; +} + +int LuaScriptInterface::luaPlayerGetItemById(lua_State* L) +{ + // player:getItemById(itemId, deepSearch[, subType = -1]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + bool deepSearch = getBoolean(L, 3); + int32_t subType = getNumber(L, 4, -1); + + Item* item = g_game.findItemOfType(player, itemId, deepSearch, subType); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetVocation(lua_State* L) +{ + // player:getVocation() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getVocation()); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetVocation(lua_State* L) +{ + // player:setVocation(id or name or userdata) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Vocation* vocation; + if (isNumber(L, 2)) { + vocation = g_vocations.getVocation(getNumber(L, 2)); + } else if (isString(L, 2)) { + vocation = g_vocations.getVocation(g_vocations.getVocationId(getString(L, 2))); + } else if (isUserdata(L, 2)) { + vocation = getUserdata(L, 2); + } else { + vocation = nullptr; + } + + if (!vocation) { + pushBoolean(L, false); + return 1; + } + + player->setVocation(vocation->getId()); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetSex(lua_State* L) +{ + // player:getSex() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSex()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetSex(lua_State* L) +{ + // player:setSex(newSex) + Player* player = getUserdata(L, 1); + if (player) { + PlayerSex_t newSex = getNumber(L, 2); + player->setSex(newSex); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetTown(lua_State* L) +{ + // player:getTown() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getTown()); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetTown(lua_State* L) +{ + // player:setTown(town) + Town* town = getUserdata(L, 2); + if (!town) { + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (player) { + player->setTown(town); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuild(lua_State* L) +{ + // player:getGuild() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Guild* guild = player->getGuild(); + if (!guild) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, guild); + setMetatable(L, -1, "Guild"); + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuild(lua_State* L) +{ + // player:setGuild(guild) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + player->setGuild(getUserdata(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuildLevel(lua_State* L) +{ + // player:getGuildLevel() + Player* player = getUserdata(L, 1); + if (player && player->getGuild()) { + lua_pushnumber(L, player->getGuildRank()->level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuildLevel(lua_State* L) +{ + // player:setGuildLevel(level) + uint8_t level = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (!player || !player->getGuild()) { + lua_pushnil(L); + return 1; + } + + const GuildRank* rank = player->getGuild()->getRankByLevel(level); + if (!rank) { + pushBoolean(L, false); + } else { + player->setGuildRank(rank); + pushBoolean(L, true); + } + + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuildNick(lua_State* L) +{ + // player:getGuildNick() + Player* player = getUserdata(L, 1); + if (player) { + pushString(L, player->getGuildNick()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuildNick(lua_State* L) +{ + // player:setGuildNick(nick) + const std::string& nick = getString(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->setGuildNick(nick); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetGroup(lua_State* L) +{ + // player:getGroup() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getGroup()); + setMetatable(L, -1, "Group"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGroup(lua_State* L) +{ + // player:setGroup(group) + Group* group = getUserdata(L, 2); + if (!group) { + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (player) { + player->setGroup(group); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetStamina(lua_State* L) +{ + // player:getStamina() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getStaminaMinutes()); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetStamina(lua_State* L) +{ + // player:setStamina(stamina) + uint16_t stamina = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->staminaMinutes = std::min(3360, stamina); + player->sendStats(); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSoul(lua_State* L) +{ + // player:getSoul() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSoul()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddSoul(lua_State* L) +{ + // player:addSoul(soulChange) + int32_t soulChange = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->changeSoul(soulChange); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMaxSoul(lua_State* L) +{ + // player:getMaxSoul() + Player* player = getUserdata(L, 1); + if (player && player->vocation) { + lua_pushnumber(L, player->vocation->getSoulMax()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBankBalance(lua_State* L) +{ + // player:getBankBalance() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getBankBalance()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetBankBalance(lua_State* L) +{ + // player:setBankBalance(bankBalance) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + int64_t balance = getNumber(L, 2); + if (balance < 0) { + reportErrorFunc("Invalid bank balance value."); + lua_pushnil(L); + return 1; + } + + player->setBankBalance(balance); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L) +{ + // player:getStorageValue(key) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t key = getNumber(L, 2); + int32_t value; + if (player->getStorageValue(key, value)) { + lua_pushnumber(L, value); + } else { + lua_pushnumber(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetStorageValue(lua_State* L) +{ + // player:setStorageValue(key, value) + int32_t value = getNumber(L, 3); + uint32_t key = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->addStorageValue(key, value); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddItem(lua_State* L) +{ + // player:addItem(itemId[, count = 1[, canDropOnMap = true[, subType = 1[, slot = CONST_SLOT_WHEREEVER]]]]) + Player* player = getUserdata(L, 1); + if (!player) { + pushBoolean(L, false); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t count = getNumber(L, 3, 1); + int32_t subType = getNumber(L, 5, 1); + + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + int parameters = lua_gettop(L); + if (parameters >= 4) { + itemCount = std::max(1, count); + } else if (it.hasSubType()) { + if (it.stackable) { + itemCount = std::ceil(count / 100.f); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + bool hasTable = itemCount > 1; + if (hasTable) { + lua_newtable(L); + } else if (itemCount == 0) { + lua_pushnil(L); + return 1; + } + + bool canDropOnMap = getBoolean(L, 4, true); + slots_t slot = getNumber(L, 6, CONST_SLOT_WHEREEVER); + for (int32_t i = 1; i <= itemCount; ++i) { + int32_t stackCount = subType; + if (it.stackable) { + stackCount = std::min(stackCount, 100); + subType -= stackCount; + } + + Item* item = Item::CreateItem(itemId, stackCount); + if (!item) { + if (!hasTable) { + lua_pushnil(L); + } + return 1; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item, canDropOnMap, slot); + if (ret != RETURNVALUE_NOERROR) { + delete item; + if (!hasTable) { + lua_pushnil(L); + } + return 1; + } + + if (hasTable) { + lua_pushnumber(L, i); + pushUserdata(L, item); + setItemMetatable(L, -1, item); + lua_settable(L, -3); + } else { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddItemEx(lua_State* L) +{ + // player:addItemEx(item[, canDropOnMap = false[, index = INDEX_WHEREEVER[, flags = 0]]]) + // player:addItemEx(item[, canDropOnMap = true[, slot = CONST_SLOT_WHEREEVER]]) + Item* item = getUserdata(L, 2); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + pushBoolean(L, false); + return 1; + } + + bool canDropOnMap = getBoolean(L, 3, false); + ReturnValue returnValue; + if (canDropOnMap) { + slots_t slot = getNumber(L, 4, CONST_SLOT_WHEREEVER); + returnValue = g_game.internalPlayerAddItem(player, item, true, slot); + } else { + int32_t index = getNumber(L, 4, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 5, 0); + returnValue = g_game.internalAddItem(player, item, index, flags); + } + + if (returnValue == RETURNVALUE_NOERROR) { + ScriptEnvironment::removeTempItem(item); + } + lua_pushnumber(L, returnValue); + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveItem(lua_State* L) +{ + // player:removeItem(itemId, count[, subType = -1[, ignoreEquipped = false]]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + uint32_t count = getNumber(L, 3); + int32_t subType = getNumber(L, 4, -1); + bool ignoreEquipped = getBoolean(L, 5, false); + pushBoolean(L, player->removeItemOfType(itemId, count, subType, ignoreEquipped)); + return 1; +} + +int LuaScriptInterface::luaPlayerGetMoney(lua_State* L) +{ + // player:getMoney() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMoney()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddMoney(lua_State* L) +{ + // player:addMoney(money) + uint64_t money = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + g_game.addMoney(player, money); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L) +{ + // player:removeMoney(money) + Player* player = getUserdata(L, 1); + if (player) { + uint64_t money = getNumber(L, 2); + pushBoolean(L, g_game.removeMoney(player, money)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) +{ + // player:showTextDialog(itemId[, text[, canWrite[, length]]]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + int32_t length = getNumber(L, 5, -1); + bool canWrite = getBoolean(L, 4, false); + std::string text; + + int parameters = lua_gettop(L); + if (parameters >= 3) { + text = getString(L, 3); + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + Item* item = Item::CreateItem(itemId); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (length < 0) { + length = Item::items[item->getID()].maxTextLen; + } + + if (!text.empty()) { + item->setText(text); + length = std::max(text.size(), length); + } + + item->setParent(player); + player->setWriteItem(item, length); + player->sendTextWindow(item, length, canWrite); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerSendTextMessage(lua_State* L) +{ + // player:sendTextMessage(type, text) + TextMessage message(getNumber(L, 2), getString(L, 3)); + Player* player = getUserdata(L, 1); + if (player) { + player->sendTextMessage(message); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSendPrivateMessage(lua_State* L) +{ + // player:sendPrivateMessage(speaker, text[, type]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + const Player* speaker = getUserdata(L, 2); + const std::string& text = getString(L, 3); + SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE); + player->sendPrivateMessage(speaker, type, text); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerChannelSay(lua_State* L) +{ + // player:channelSay(speaker, type, text, channelId) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Creature* speaker = getCreature(L, 2); + SpeakClasses type = getNumber(L, 3); + const std::string& text = getString(L, 4); + uint16_t channelId = getNumber(L, 5); + player->sendToChannel(speaker, type, text, channelId); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerOpenChannel(lua_State* L) +{ + // player:openChannel(channelId) + uint16_t channelId = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + g_game.playerOpenChannel(player->getID(), channelId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSlotItem(lua_State* L) +{ + // player:getSlotItem(slot) + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t slot = getNumber(L, 2); + Thing* thing = player->getThing(slot); + if (!thing) { + lua_pushnil(L); + return 1; + } + + Item* item = thing->getItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetParty(lua_State* L) +{ + // player:getParty() + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Party* party = player->getParty(); + if (party) { + pushUserdata(L, party); + setMetatable(L, -1, "Party"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddOutfit(lua_State* L) +{ + // player:addOutfit(lookType) + Player* player = getUserdata(L, 1); + if (player) { + player->addOutfit(getNumber(L, 2), 0); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddOutfitAddon(lua_State* L) +{ + // player:addOutfitAddon(lookType, addon) + Player* player = getUserdata(L, 1); + if (player) { + uint16_t lookType = getNumber(L, 2); + uint8_t addon = getNumber(L, 3); + player->addOutfit(lookType, addon); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveOutfit(lua_State* L) +{ + // player:removeOutfit(lookType) + Player* player = getUserdata(L, 1); + if (player) { + uint16_t lookType = getNumber(L, 2); + pushBoolean(L, player->removeOutfit(lookType)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveOutfitAddon(lua_State* L) +{ + // player:removeOutfitAddon(lookType, addon) + Player* player = getUserdata(L, 1); + if (player) { + uint16_t lookType = getNumber(L, 2); + uint8_t addon = getNumber(L, 3); + pushBoolean(L, player->removeOutfitAddon(lookType, addon)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasOutfit(lua_State* L) +{ + // player:hasOutfit(lookType[, addon = 0]) + Player* player = getUserdata(L, 1); + if (player) { + uint16_t lookType = getNumber(L, 2); + uint8_t addon = getNumber(L, 3, 0); + pushBoolean(L, player->canWear(lookType, addon)); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L) +{ + // player:sendOutfitWindow() + Player* player = getUserdata(L, 1); + if (player) { + player->sendOutfitWindow(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L) +{ + // player:getPremiumDays() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->premiumDays); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddPremiumDays(lua_State* L) +{ + // player:addPremiumDays(days) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (player->premiumDays != std::numeric_limits::max()) { + uint16_t days = getNumber(L, 2); + int32_t addDays = std::min(0xFFFE - player->premiumDays, days); + if (addDays > 0) { + player->setPremiumDays(player->premiumDays + addDays); + IOLoginData::addPremiumDays(player->getAccount(), addDays); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerRemovePremiumDays(lua_State* L) +{ + // player:removePremiumDays(days) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (player->premiumDays != std::numeric_limits::max()) { + uint16_t days = getNumber(L, 2); + int32_t removeDays = std::min(player->premiumDays, days); + if (removeDays > 0) { + player->setPremiumDays(player->premiumDays - removeDays); + IOLoginData::removePremiumDays(player->getAccount(), removeDays); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerHasBlessing(lua_State* L) +{ + // player:hasBlessing(blessing) + uint8_t blessing = getNumber(L, 2) - 1; + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->hasBlessing(blessing)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddBlessing(lua_State* L) +{ + // player:addBlessing(blessing) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t blessing = getNumber(L, 2) - 1; + if (player->hasBlessing(blessing)) { + pushBoolean(L, false); + return 1; + } + + player->addBlessing(1 << blessing); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveBlessing(lua_State* L) +{ + // player:removeBlessing(blessing) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t blessing = getNumber(L, 2) - 1; + if (!player->hasBlessing(blessing)) { + pushBoolean(L, false); + return 1; + } + + player->removeBlessing(1 << blessing); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerCanLearnSpell(lua_State* L) +{ + // player:canLearnSpell(spellName) + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + const std::string& spellName = getString(L, 2); + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + if (!spell) { + reportErrorFunc("Spell \"" + spellName + "\" not found"); + pushBoolean(L, false); + return 1; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + pushBoolean(L, true); + return 1; + } + + const auto& vocMap = spell->getVocMap(); + if (vocMap.count(player->getVocationId()) == 0) { + pushBoolean(L, false); + } else if (player->getLevel() < spell->getLevel()) { + pushBoolean(L, false); + } else if (player->getMagicLevel() < spell->getMagicLevel()) { + pushBoolean(L, false); + } else { + pushBoolean(L, true); + } + return 1; +} + +int LuaScriptInterface::luaPlayerLearnSpell(lua_State* L) +{ + // player:learnSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + player->learnInstantSpell(spellName); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerForgetSpell(lua_State* L) +{ + // player:forgetSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + player->forgetInstantSpell(spellName); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasLearnedSpell(lua_State* L) +{ + // player:hasLearnedSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + pushBoolean(L, player->hasLearnedInstantSpell(spellName)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSave(lua_State* L) +{ + // player:save() + Player* player = getUserdata(L, 1); + if (player) { + player->loginPosition = player->getPosition(); + pushBoolean(L, IOLoginData::savePlayer(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsPzLocked(lua_State* L) +{ + // player:isPzLocked() + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->isPzLocked()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsFakePlayer(lua_State* L) +{ + // player:isFakePlayer() + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->isFakePlayer); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetClient(lua_State* L) +{ + // player:getClient() + Player* player = getUserdata(L, 1); + if (player) { + lua_createtable(L, 0, 2); + setField(L, "version", player->getProtocolVersion()); + setField(L, "os", player->getOperatingSystem()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetHouse(lua_State* L) +{ + // player:getHouse() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + House* house = g_game.map.houses.getHouseByPlayerId(player->getGUID()); + if (house) { + pushUserdata(L, house); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) +{ + // player:setGhostMode(enabled) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + bool enabled = getBoolean(L, 2); + if (player->isInGhostMode() == enabled) { + pushBoolean(L, true); + return 1; + } + + player->switchGhostMode(); + + Tile* tile = player->getTile(); + const Position& position = player->getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, position, true, true); + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { + if (enabled) { + tmpPlayer->sendRemoveTileThing(position, tile->getStackposOfCreature(tmpPlayer, player)); + } else { + tmpPlayer->sendCreatureAppear(player, position, true); + } + } else { + tmpPlayer->sendCreatureChangeVisible(player, !enabled); + } + } + + if (player->isInGhostMode()) { + for (const auto& it : g_game.getPlayers()) { + if (!it.second->isAccessPlayer()) { + it.second->notifyStatusChange(player, VIPSTATUS_OFFLINE); + } + } + IOLoginData::updateOnlineStatus(player->getGUID(), false); + } else { + for (const auto& it : g_game.getPlayers()) { + if (!it.second->isAccessPlayer()) { + it.second->notifyStatusChange(player, VIPSTATUS_ONLINE); + } + } + IOLoginData::updateOnlineStatus(player->getGUID(), true); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerId(lua_State* L) +{ + // player:getContainerId(container) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Container* container = getUserdata(L, 2); + if (container) { + lua_pushnumber(L, player->getContainerID(container)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerById(lua_State* L) +{ + // player:getContainerById(id) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Container* container = player->getContainerByID(getNumber(L, 2)); + if (container) { + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerIndex(lua_State* L) +{ + // player:getContainerIndex(id) + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getContainerIndex(getNumber(L, 2))); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetTotalDamage(lua_State * L) +{ + // player:getTotalDamage(attackSkill, attackValue, fightMode) + Player* player = getUserdata(L, 1); + if (player) { + uint32_t attackSkill = getNumber(L, 2); + uint32_t attackValue = getNumber(L, 3); + fightMode_t fightMode = static_cast(getNumber(L, 4, FIGHTMODE_BALANCED)); + + lua_pushnumber(L, Combat::getTotalDamage(attackSkill, attackValue, fightMode)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Monster +int LuaScriptInterface::luaMonsterCreate(lua_State* L) +{ + // Monster(id or userdata) + Monster* monster; + if (isNumber(L, 2)) { + monster = g_game.getMonsterByID(getNumber(L, 2)); + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Monster) { + lua_pushnil(L); + return 1; + } + monster = getUserdata(L, 2); + } else { + monster = nullptr; + } + + if (monster) { + pushUserdata(L, monster); + setMetatable(L, -1, "Monster"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsMonster(lua_State* L) +{ + // monster:isMonster() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaMonsterGetType(lua_State* L) +{ + // monster:getType() + const Monster* monster = getUserdata(L, 1); + if (monster) { + pushUserdata(L, monster->mType); + setMetatable(L, -1, "MonsterType"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetSpawnPosition(lua_State* L) +{ + // monster:getSpawnPosition() + const Monster* monster = getUserdata(L, 1); + if (monster) { + pushPosition(L, monster->getMasterPos()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsInSpawnRange(lua_State* L) +{ + // monster:isInSpawnRange([position]) + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->isInSpawnRange(lua_gettop(L) >= 2 ? getPosition(L, 2) : monster->getPosition())); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsIdle(lua_State* L) +{ + // monster:isIdle() + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->getIdleStatus()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSetIdle(lua_State* L) +{ + // monster:setIdle(idle) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + monster->setIdle(getBoolean(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterIsTarget(lua_State* L) +{ + // monster:isTarget(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isTarget(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsOpponent(lua_State* L) +{ + // monster:isOpponent(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isOpponent(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsFriend(lua_State* L) +{ + // monster:isFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isFriend(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterAddFriend(lua_State* L) +{ + // monster:addFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + monster->addFriend(creature); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterRemoveFriend(lua_State* L) +{ + // monster:removeFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + monster->removeFriend(creature); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetFriendList(lua_State* L) +{ + // monster:getFriendList() + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const auto& friendList = monster->getFriendList(); + lua_createtable(L, friendList.size(), 0); + + int index = 0; + for (Creature* creature : friendList) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetFriendCount(lua_State* L) +{ + // monster:getFriendCount() + Monster* monster = getUserdata(L, 1); + if (monster) { + lua_pushnumber(L, monster->getFriendList().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterAddTarget(lua_State* L) +{ + // monster:addTarget(creature[, pushFront = false]) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + bool pushFront = getBoolean(L, 3, false); + monster->addTarget(creature, pushFront); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterRemoveTarget(lua_State* L) +{ + // monster:removeTarget(creature) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + monster->removeTarget(getCreature(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterGetTargetList(lua_State* L) +{ + // monster:getTargetList() + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const auto& targetList = monster->getTargetList(); + lua_createtable(L, targetList.size(), 0); + + int index = 0; + for (Creature* creature : targetList) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetTargetCount(lua_State* L) +{ + // monster:getTargetCount() + Monster* monster = getUserdata(L, 1); + if (monster) { + lua_pushnumber(L, monster->getTargetList().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSelectTarget(lua_State* L) +{ + // monster:selectTarget(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->selectTarget(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSearchTarget(lua_State* L) +{ + // monster:searchTarget([searchType = TARGETSEARCH_ANY]) + Monster* monster = getUserdata(L, 1); + if (monster) { + TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_ANY); + pushBoolean(L, monster->searchTarget(searchType)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Npc +int LuaScriptInterface::luaNpcCreate(lua_State* L) +{ + // Npc([id or name or userdata]) + Npc* npc; + if (lua_gettop(L) >= 2) { + if (isNumber(L, 2)) { + npc = g_game.getNpcByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + npc = g_game.getNpcByName(getString(L, 2)); + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Npc) { + lua_pushnil(L); + return 1; + } + npc = getUserdata(L, 2); + } else { + npc = nullptr; + } + } else { + npc = getScriptEnv()->getNpc(); + } + + if (npc) { + pushUserdata(L, npc); + setMetatable(L, -1, "Npc"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNpcIsNpc(lua_State* L) +{ + // npc:isNpc() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaNpcSetMasterPos(lua_State* L) +{ + // npc:setMasterPos(pos[, radius]) + Npc* npc = getUserdata(L, 1); + if (!npc) { + lua_pushnil(L); + return 1; + } + + const Position& pos = getPosition(L, 2); + int32_t radius = getNumber(L, 3, 1); + npc->setMasterPos(pos, radius); + pushBoolean(L, true); + return 1; +} + +// Guild +int LuaScriptInterface::luaGuildCreate(lua_State* L) +{ + // Guild(id) + uint32_t id = getNumber(L, 2); + + Guild* guild = g_game.getGuild(id); + if (guild) { + pushUserdata(L, guild); + setMetatable(L, -1, "Guild"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetId(lua_State* L) +{ + // guild:getId() + Guild* guild = getUserdata(L, 1); + if (guild) { + lua_pushnumber(L, guild->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetName(lua_State* L) +{ + // guild:getName() + Guild* guild = getUserdata(L, 1); + if (guild) { + pushString(L, guild->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildSetGuildWarEmblem(lua_State* L) +{ + // guild:setGuildWarEmblem(guild2) + const Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + const Guild* guild2 = getUserdata(L, 2); + if (!guild2) { + lua_pushnil(L); + return 1; + } + + auto& members = guild->getMembersOnline(); + for (Player* player : members) { + GuildWarList guildWarList; + IOGuild::getWarList(player->getGuild()->getId(), guildWarList); + player->guildWarList = guildWarList; + } + + auto& members2 = guild2->getMembersOnline(); + for (Player* player : members2) { + GuildWarList guildWarList; + IOGuild::getWarList(player->getGuild()->getId(), guildWarList); + player->guildWarList = guildWarList; + g_game.updateCreatureSkull(player); + } + + for (Player* player : members) { + g_game.updateCreatureSkull(player); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGuildGetMembersOnline(lua_State* L) +{ + // guild:getMembersOnline() + const Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + const auto& members = guild->getMembersOnline(); + lua_createtable(L, members.size(), 0); + + int index = 0; + for (Player* player : members) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGuildAddRank(lua_State* L) +{ + // guild:addRank(id, name, level) + Guild* guild = getUserdata(L, 1); + if (guild) { + uint32_t id = getNumber(L, 2); + const std::string& name = getString(L, 3); + uint8_t level = getNumber(L, 4); + guild->addRank(id, name, level); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetRankById(lua_State* L) +{ + // guild:getRankById(id) + Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + uint32_t id = getNumber(L, 2); + GuildRank* rank = guild->getRankById(id); + if (rank) { + lua_createtable(L, 0, 3); + setField(L, "id", rank->id); + setField(L, "name", rank->name); + setField(L, "level", rank->level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetRankByLevel(lua_State* L) +{ + // guild:getRankByLevel(level) + const Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + uint8_t level = getNumber(L, 2); + const GuildRank* rank = guild->getRankByLevel(level); + if (rank) { + lua_createtable(L, 0, 3); + setField(L, "id", rank->id); + setField(L, "name", rank->name); + setField(L, "level", rank->level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetBankBalance(lua_State* L) +{ + // guild:getBankBalance() + Guild* guild = getUserdata(L, 1); + if (guild) { + lua_pushnumber(L, IOGuild::getGuildBalance(guild->getId())); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildIncreaseBankBalance(lua_State* L) +{ + // guild:increaseBankBalance(amount) + Guild* guild = getUserdata(L, 1); + if (guild) { + uint32_t amount = getNumber(L, 2); + bool isSuccess = IOGuild::increaseGuildBankBalance(guild->getId(), amount); + pushBoolean(L, isSuccess); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildDecreaseBankBalance(lua_State* L) +{ + // guild:decreaseBankBalance(amount) + Guild* guild = getUserdata(L, 1); + if (guild) { + uint32_t amount = getNumber(L, 2); + bool isSuccess = IOGuild::decreaseGuildBankBalance(guild->getId(), amount); + pushBoolean(L, isSuccess); + } + else { + lua_pushnil(L); + } + return 1; +} + +// Group +int LuaScriptInterface::luaGroupCreate(lua_State* L) +{ + // Group(id) + uint32_t id = getNumber(L, 2); + + Group* group = g_game.groups.getGroup(id); + if (group) { + pushUserdata(L, group); + setMetatable(L, -1, "Group"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetId(lua_State* L) +{ + // group:getId() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->id); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetName(lua_State* L) +{ + // group:getName() + Group* group = getUserdata(L, 1); + if (group) { + pushString(L, group->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetFlags(lua_State* L) +{ + // group:getFlags() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->flags); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetAccess(lua_State* L) +{ + // group:getAccess() + Group* group = getUserdata(L, 1); + if (group) { + pushBoolean(L, group->access); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetMaxDepotItems(lua_State* L) +{ + // group:getMaxDepotItems() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->maxDepotItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetMaxVipEntries(lua_State* L) +{ + // group:getMaxVipEntries() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->maxVipEntries); + } else { + lua_pushnil(L); + } + return 1; +} + +// Vocation +int LuaScriptInterface::luaVocationCreate(lua_State* L) +{ + // Vocation(id or name) + uint32_t id; + if (isNumber(L, 2)) { + id = getNumber(L, 2); + } else { + id = g_vocations.getVocationId(getString(L, 2)); + } + + Vocation* vocation = g_vocations.getVocation(id); + if (vocation) { + pushUserdata(L, vocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetId(lua_State* L) +{ + // vocation:getId() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetName(lua_State* L) +{ + // vocation:getName() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + pushString(L, vocation->getVocName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetDescription(lua_State* L) +{ + // vocation:getDescription() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + pushString(L, vocation->getVocDescription()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetRequiredSkillTries(lua_State* L) +{ + // vocation:getRequiredSkillTries(skillType, skillLevel) + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + skills_t skillType = getNumber(L, 2); + uint16_t skillLevel = getNumber(L, 3); + lua_pushnumber(L, vocation->getReqSkillTries(skillType, skillLevel)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetRequiredManaSpent(lua_State* L) +{ + // vocation:getRequiredManaSpent(magicLevel) + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + uint32_t magicLevel = getNumber(L, 2); + lua_pushnumber(L, vocation->getReqMana(magicLevel)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetCapacityGain(lua_State* L) +{ + // vocation:getCapacityGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getCapGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGain(lua_State* L) +{ + // vocation:getHealthGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHPGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGainTicks(lua_State* L) +{ + // vocation:getHealthGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHealthGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGainAmount(lua_State* L) +{ + // vocation:getHealthGainAmount() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHealthGainAmount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGain(lua_State* L) +{ + // vocation:getManaGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGainTicks(lua_State* L) +{ + // vocation:getManaGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGainAmount(lua_State* L) +{ + // vocation:getManaGainAmount() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGainAmount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetMaxSoul(lua_State* L) +{ + // vocation:getMaxSoul() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getSoulMax()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetSoulGainTicks(lua_State* L) +{ + // vocation:getSoulGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getSoulGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetAttackSpeed(lua_State* L) +{ + // vocation:getAttackSpeed() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getAttackSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetBaseSpeed(lua_State* L) +{ + // vocation:getBaseSpeed() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getBaseSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetDemotion(lua_State* L) +{ + // vocation:getDemotion() + Vocation* vocation = getUserdata(L, 1); + if (!vocation) { + lua_pushnil(L); + return 1; + } + + uint16_t fromId = vocation->getFromVocation(); + if (fromId == VOCATION_NONE) { + lua_pushnil(L); + return 1; + } + + Vocation* demotedVocation = g_vocations.getVocation(fromId); + if (demotedVocation && demotedVocation != vocation) { + pushUserdata(L, demotedVocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetPromotion(lua_State* L) +{ + // vocation:getPromotion() + Vocation* vocation = getUserdata(L, 1); + if (!vocation) { + lua_pushnil(L); + return 1; + } + + uint16_t promotedId = g_vocations.getPromotedVocation(vocation->getId()); + if (promotedId == VOCATION_NONE) { + lua_pushnil(L); + return 1; + } + + Vocation* promotedVocation = g_vocations.getVocation(promotedId); + if (promotedVocation && promotedVocation != vocation) { + pushUserdata(L, promotedVocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +// Town +int LuaScriptInterface::luaTownCreate(lua_State* L) +{ + // Town(id or name) + Town* town; + if (isNumber(L, 2)) { + town = g_game.map.towns.getTown(getNumber(L, 2)); + } else if (isString(L, 2)) { + town = g_game.map.towns.getTown(getString(L, 2)); + } else { + town = nullptr; + } + + if (town) { + pushUserdata(L, town); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetId(lua_State* L) +{ + // town:getId() + Town* town = getUserdata(L, 1); + if (town) { + lua_pushnumber(L, town->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetName(lua_State* L) +{ + // town:getName() + Town* town = getUserdata(L, 1); + if (town) { + pushString(L, town->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetTemplePosition(lua_State* L) +{ + // town:getTemplePosition() + Town* town = getUserdata(L, 1); + if (town) { + pushPosition(L, town->getTemplePosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +// House +int LuaScriptInterface::luaHouseCreate(lua_State* L) +{ + // House(id) + House* house = g_game.map.houses.getHouse(getNumber(L, 2)); + if (house) { + pushUserdata(L, house); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetId(lua_State* L) +{ + // house:getId() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetName(lua_State* L) +{ + // house:getName() + House* house = getUserdata(L, 1); + if (house) { + pushString(L, house->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTown(lua_State* L) +{ + // house:getTown() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + Town* town = g_game.map.towns.getTown(house->getTownId()); + if (town) { + pushUserdata(L, town); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetExitPosition(lua_State* L) +{ + // house:getExitPosition() + House* house = getUserdata(L, 1); + if (house) { + pushPosition(L, house->getEntryPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetRent(lua_State* L) +{ + // house:getRent() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getRent()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetOwnerGuid(lua_State* L) +{ + // house:getOwnerGuid() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getOwner()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetOwnerGuid(lua_State* L) +{ + // house:setOwnerGuid(guid[, updateDatabase = true]) + House* house = getUserdata(L, 1); + if (house) { + uint32_t guid = getNumber(L, 2); + bool updateDatabase = getBoolean(L, 3, true); + house->setOwner(guid, updateDatabase); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseStartTrade(lua_State* L) +{ + // house:startTrade(player, tradePartner) + House* house = getUserdata(L, 1); + Player* player = getUserdata(L, 2); + Player* tradePartner = getUserdata(L, 3); + + if (!player || !tradePartner || !house) { + lua_pushnil(L); + return 1; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { + lua_pushnumber(L, RETURNVALUE_TRADEPLAYERFARAWAY); + return 1; + } + + if (house->getOwner() != player->getGUID()) { + lua_pushnumber(L, RETURNVALUE_YOUDONTOWNTHISHOUSE); + return 1; + } + + if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) { + lua_pushnumber(L, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE); + return 1; + } + + if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) { + lua_pushnumber(L, RETURNVALUE_TRADEPLAYERHIGHESTBIDDER); + return 1; + } + + Item* transferItem = house->getTransferItem(); + if (!transferItem) { + lua_pushnumber(L, RETURNVALUE_YOUCANNOTTRADETHISHOUSE); + return 1; + } + + transferItem->getParent()->setParent(player); + if (!g_game.internalStartTrade(player, tradePartner, transferItem)) { + house->resetTransferItem(); + } + + lua_pushnumber(L, RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaHouseGetBeds(lua_State* L) +{ + // house:getBeds() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& beds = house->getBeds(); + lua_createtable(L, beds.size(), 0); + + int index = 0; + for (BedItem* bedItem : beds) { + pushUserdata(L, bedItem); + setItemMetatable(L, -1, bedItem); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetBedCount(lua_State* L) +{ + // house:getBedCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getBedCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetDoors(lua_State* L) +{ + // house:getDoors() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& doors = house->getDoors(); + lua_createtable(L, doors.size(), 0); + + int index = 0; + for (Door* door : doors) { + pushUserdata(L, door); + setItemMetatable(L, -1, door); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetDoorCount(lua_State* L) +{ + // house:getDoorCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getDoors().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTiles(lua_State* L) +{ + // house:getTiles() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& tiles = house->getTiles(); + lua_createtable(L, tiles.size(), 0); + + int index = 0; + for (Tile* tile : tiles) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) +{ + // house:getTileCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getTiles().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetAccessList(lua_State* L) +{ + // house:getAccessList(listId) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + std::string list; + uint32_t listId = getNumber(L, 2); + if (house->getAccessList(listId, list)) { + pushString(L, list); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetAccessList(lua_State* L) +{ + // house:setAccessList(listId, list) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + uint32_t listId = getNumber(L, 2); + const std::string& list = getString(L, 3); + house->setAccessList(listId, list); + pushBoolean(L, true); + return 1; +} + +// ItemType +int LuaScriptInterface::luaItemTypeCreate(lua_State* L) +{ + // ItemType(id or name) + uint32_t id; + if (isNumber(L, 2)) { + id = getNumber(L, 2); + } else { + id = Item::items.getItemIdByName(getString(L, 2)); + } + + const ItemType& itemType = Item::items[id]; + pushUserdata(L, &itemType); + setMetatable(L, -1, "ItemType"); + return 1; +} + +int LuaScriptInterface::luaItemTypeIsCorpse(lua_State* L) +{ + // itemType:isCorpse() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->corpse); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDoor(lua_State* L) +{ + // itemType:isDoor() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isDoor()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsContainer(lua_State* L) +{ + // itemType:isContainer() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isContainer()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsChest(lua_State * L) +{ + // itemType:isChest() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isChest()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsFluidContainer(lua_State* L) +{ + // itemType:isFluidContainer() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isFluidContainer()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsMovable(lua_State* L) +{ + // itemType:isMovable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->moveable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsRune(lua_State* L) +{ + // itemType:isRune() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isRune()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsStackable(lua_State* L) +{ + // itemType:isStackable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->stackable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsReadable(lua_State* L) +{ + // itemType:isReadable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->canReadText); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsWritable(lua_State* L) +{ + // itemType:isWritable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->canWriteText); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) +{ + // itemType:isMagicField() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isMagicField()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsSplash(lua_State* L) +{ + // itemType:isSplash() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isSplash()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsKey(lua_State* L) +{ + // itemType:isKey() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isKey()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDisguised(lua_State* L) +{ + // itemType:isDisguised() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->disguise); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDestroyable(lua_State * L) +{ + // itemType:isDestroyable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->destroy); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State * L) +{ + // itemType:isGroundTile() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->group == ITEM_GROUP_GROUND); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetType(lua_State* L) +{ + // itemType:getType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->type); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetId(lua_State* L) +{ + // itemType:getId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->id); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDisguiseId(lua_State * L) +{ + // itemType:getDisguiseId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->disguiseId); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetName(lua_State* L) +{ + // itemType:getName() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetPluralName(lua_State* L) +{ + // itemType:getPluralName() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->getPluralName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetArticle(lua_State* L) +{ + // itemType:getArticle() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->article); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDescription(lua_State* L) +{ + // itemType:getDescription() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->description); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) +{ + // itemType:getSlotPosition() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->slotPosition); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDestroyTarget(lua_State * L) +{ + // itemType:getDestroyTarget() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->destroyTarget); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetCharges(lua_State* L) +{ + // itemType:getCharges() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->charges); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetFluidSource(lua_State* L) +{ + // itemType:getFluidSource() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->fluidSource); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetCapacity(lua_State* L) +{ + // itemType:getCapacity() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->maxItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) +{ + // itemType:getWeight([count = 1]) + uint16_t count = getNumber(L, 2, 1); + + const ItemType* itemType = getUserdata(L, 1); + if (!itemType) { + lua_pushnil(L); + return 1; + } + + uint64_t weight = static_cast(itemType->weight) * std::max(1, count); + lua_pushnumber(L, weight); + return 1; +} + +int LuaScriptInterface::luaItemTypeGetShootRange(lua_State* L) +{ + // itemType:getShootRange() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->shootRange); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetAttack(lua_State* L) +{ + // itemType:getAttack() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->attack); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) +{ + // itemType:getDefense() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->defense); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) +{ + // itemType:getArmor() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->armor); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) +{ + // itemType:getWeaponType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->weaponType); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetAmmoType(lua_State* L) +{ + // itemType:getAmmoType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->ammoType); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L) +{ + // itemType:getTransformEquipId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->transformEquipTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetTransformDeEquipId(lua_State* L) +{ + // itemType:getTransformDeEquipId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->transformDeEquipTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) +{ + // itemType:getDecayId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->decayTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetNutrition(lua_State* L) +{ + // itemType:getNutrition() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->nutrition); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetRequiredLevel(lua_State* L) +{ + // itemType:getRequiredLevel() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->minReqLevel); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeHasSubType(lua_State* L) +{ + // itemType:hasSubType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->hasSubType()); + } else { + lua_pushnil(L); + } + return 1; +} + +// Combat +int LuaScriptInterface::luaCombatCreate(lua_State* L) +{ + // Combat() + pushUserdata(L, g_luaEnvironment.createCombatObject(getScriptEnv()->getScriptInterface())); + setMetatable(L, -1, "Combat"); + return 1; +} + +int LuaScriptInterface::luaCombatSetParameter(lua_State* L) +{ + // combat:setParameter(key, value) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + CombatParam_t key = getNumber(L, 2); + uint32_t value; + if (isBoolean(L, 3)) { + value = getBoolean(L, 3) ? 1 : 0; + } else { + value = getNumber(L, 3); + } + combat->setParam(key, value); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCombatSetFormula(lua_State* L) +{ + // combat:setFormula(type, mina, minb, maxa, maxb) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + formulaType_t type = getNumber(L, 2); + double mina = getNumber(L, 3); + double minb = getNumber(L, 4); + double maxa = getNumber(L, 5); + double maxb = getNumber(L, 6); + combat->setPlayerCombatValues(type, mina, minb, maxa, maxb); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCombatSetArea(lua_State* L) +{ + // combat:setArea(area) + if (getScriptEnv()->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushnil(L); + return 1; + } + + const AreaCombat* area = g_luaEnvironment.getAreaObject(getNumber(L, 2)); + if (!area) { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + Combat* combat = getUserdata(L, 1); + if (combat) { + combat->setArea(new AreaCombat(*area)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatSetCondition(lua_State* L) +{ + // combat:setCondition(condition) + Condition* condition = getUserdata(L, 2); + Combat* combat = getUserdata(L, 1); + if (combat && condition) { + combat->setCondition(condition->clone()); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatSetCallback(lua_State* L) +{ + // combat:setCallback(key, function) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + CallBackParam_t key = getNumber(L, 2); + if (!combat->setCallback(key)) { + lua_pushnil(L); + return 1; + } + + CallBack* callback = combat->getCallback(key); + if (!callback) { + lua_pushnil(L); + return 1; + } + + const std::string& function = getString(L, 3); + pushBoolean(L, callback->loadCallBack(getScriptEnv()->getScriptInterface(), function)); + return 1; +} + +int LuaScriptInterface::luaCombatSetOrigin(lua_State* L) +{ + // combat:setOrigin(origin) + Combat* combat = getUserdata(L, 1); + if (combat) { + combat->setOrigin(getNumber(L, 2)); + pushBoolean(L, true); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatExecute(lua_State* L) +{ + // combat:execute(creature, variant) + Combat* combat = getUserdata(L, 1); + if (!combat) { + pushBoolean(L, false); + return 1; + } + + Creature* creature = getCreature(L, 2); + + const LuaVariant& variant = getVariant(L, 3); + switch (variant.type) { + case VARIANT_NUMBER: { + Creature* target = g_game.getCreatureByID(variant.number); + if (!target) { + pushBoolean(L, false); + return 1; + } + + if (combat->hasArea()) { + combat->doCombat(creature, target->getPosition()); + } else { + combat->doCombat(creature, target); + } + break; + } + + case VARIANT_POSITION: { + combat->doCombat(creature, variant.pos); + break; + } + + case VARIANT_TARGETPOSITION: { + if (combat->hasArea()) { + combat->doCombat(creature, variant.pos); + } else { + combat->postCombatEffects(creature, variant.pos); + g_game.addMagicEffect(variant.pos, CONST_ME_POFF); + } + break; + } + + case VARIANT_STRING: { + Player* target = g_game.getPlayerByName(variant.text); + if (!target) { + pushBoolean(L, false); + return 1; + } + + combat->doCombat(creature, target); + break; + } + + case VARIANT_NONE: { + reportErrorFunc(getErrorDesc(LUA_ERROR_VARIANT_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + default: { + break; + } + } + + pushBoolean(L, true); + return 1; +} + +// Condition +int LuaScriptInterface::luaConditionCreate(lua_State* L) +{ + // Condition(conditionType[, conditionId = CONDITIONID_COMBAT]) + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + + Condition* condition = Condition::createCondition(conditionId, conditionType, 0, 0); + if (condition) { + pushUserdata(L, condition); + setMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionDelete(lua_State* L) +{ + // condition:delete() + Condition** conditionPtr = getRawUserdata(L, 1); + if (conditionPtr && *conditionPtr) { + delete *conditionPtr; + *conditionPtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaConditionGetId(lua_State* L) +{ + // condition:getId() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetSubId(lua_State* L) +{ + // condition:getSubId() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getSubId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetType(lua_State* L) +{ + // condition:getType() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetIcons(lua_State* L) +{ + // condition:getIcons() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getIcons()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetEndTime(lua_State* L) +{ + // condition:getEndTime() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getEndTime()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionClone(lua_State* L) +{ + // condition:clone() + Condition* condition = getUserdata(L, 1); + if (condition) { + pushUserdata(L, condition->clone()); + setMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetTicks(lua_State* L) +{ + // condition:getTicks() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetTicks(lua_State* L) +{ + // condition:setTicks(ticks) + int32_t ticks = getNumber(L, 2); + Condition* condition = getUserdata(L, 1); + if (condition) { + condition->setTicks(ticks); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetParameter(lua_State* L) +{ + // condition:setParameter(key, value) + Condition* condition = getUserdata(L, 1); + if (!condition) { + lua_pushnil(L); + return 1; + } + + ConditionParam_t key = getNumber(L, 2); + int32_t value; + if (isBoolean(L, 3)) { + value = getBoolean(L, 3) ? 1 : 0; + } else { + value = getNumber(L, 3); + } + condition->setParam(key, value); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L) +{ + // condition:setSpeedDelta(speedDelta) + int32_t speedDelta = getNumber(L, 2); + ConditionSpeed* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + condition->setSpeedDelta(speedDelta); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) +{ + // condition:setOutfit(outfit) + // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons]) + Outfit_t outfit; + if (isTable(L, 2)) { + outfit = getOutfit(L, 2); + } else { + outfit.lookAddons = getNumber(L, 8, outfit.lookAddons); + outfit.lookFeet = getNumber(L, 7); + outfit.lookLegs = getNumber(L, 6); + outfit.lookBody = getNumber(L, 5); + outfit.lookHead = getNumber(L, 4); + outfit.lookType = getNumber(L, 3); + outfit.lookTypeEx = getNumber(L, 2); + } + + ConditionOutfit* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + condition->setOutfit(outfit); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetTiming(lua_State* L) +{ + // condition:setTiming(count) + int32_t count = getNumber(L, 2); + ConditionDamage* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + if (condition->getType() == CONDITION_POISON) { + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + } else if (condition->getType() == CONDITION_FIRE) { + condition->setParam(CONDITION_PARAM_COUNT, 8); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 8); + + count /= 10; + } else if (condition->getType() == CONDITION_ENERGY) { + condition->setParam(CONDITION_PARAM_COUNT, 10); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 10); + + count /= 20; + } + + condition->setParam(CONDITION_PARAM_CYCLE, count); + + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + + +// MonsterType +int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) +{ + // MonsterType(name) + MonsterType* monsterType = g_monsters.getMonsterType(getString(L, 2)); + if (monsterType) { + pushUserdata(L, monsterType); + setMetatable(L, -1, "MonsterType"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) +{ + // monsterType:isAttackable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isAttackable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) +{ + // monsterType:isConvinceable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isConvinceable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) +{ + // monsterType:isSummonable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isSummonable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) +{ + // monsterType:isIllusionable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isIllusionable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) +{ + // monsterType:isHostile() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isHostile); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsPushable(lua_State* L) +{ + // monsterType:isPushable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.pushable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsHealthShown(lua_State* L) +{ + // monsterType:isHealthShown() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, !monsterType->info.hiddenHealth); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) +{ + // monsterType:canPushItems() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.canPushItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanPushCreatures(lua_State* L) +{ + // monsterType:canPushCreatures() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.canPushCreatures); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetName(lua_State* L) +{ + // monsterType:getName() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushString(L, monsterType->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetNameDescription(lua_State* L) +{ + // monsterType:getNameDescription() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushString(L, monsterType->nameDescription); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetHealth(lua_State* L) +{ + // monsterType:getHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.health); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetMaxHealth(lua_State* L) +{ + // monsterType:getMaxHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.healthMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetRunHealth(lua_State* L) +{ + // monsterType:getRunHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.runAwayHealth); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetExperience(lua_State* L) +{ + // monsterType:getExperience() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.experience); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCombatImmunities(lua_State* L) +{ + // monsterType:getCombatImmunities() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.damageImmunities); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetConditionImmunities(lua_State* L) +{ + // monsterType:getConditionImmunities() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.conditionImmunities); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) +{ + // monsterType:getAttackList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.attackSpells.size(), 0); + + int index = 0; + for (const auto& spellBlock : monsterType->info.attackSpells) { + lua_createtable(L, 0, 8); + + setField(L, "chance", spellBlock.chance); + setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "minCombatValue", spellBlock.minCombatValue); + setField(L, "maxCombatValue", spellBlock.maxCombatValue); + setField(L, "range", spellBlock.range); + pushUserdata(L, static_cast(spellBlock.spell)); + lua_setfield(L, -2, "spell"); + + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) +{ + // monsterType:getDefenseList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.defenseSpells.size(), 0); + + + int index = 0; + for (const auto& spellBlock : monsterType->info.defenseSpells) { + lua_createtable(L, 0, 8); + + setField(L, "chance", spellBlock.chance); + setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "minCombatValue", spellBlock.minCombatValue); + setField(L, "maxCombatValue", spellBlock.maxCombatValue); + setField(L, "range", spellBlock.range); + pushUserdata(L, static_cast(spellBlock.spell)); + lua_setfield(L, -2, "spell"); + + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) +{ + // monsterType:getElementList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.elementMap.size(), 0); + for (const auto& elementEntry : monsterType->info.elementMap) { + lua_pushnumber(L, elementEntry.second); + lua_rawseti(L, -2, elementEntry.first); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) +{ + // monsterType:getVoices() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.voiceVector.size(), 0); + for (const auto& voiceBlock : monsterType->info.voiceVector) { + lua_createtable(L, 0, 2); + setField(L, "text", voiceBlock.text); + setField(L, "yellText", voiceBlock.yellText); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) +{ + // monsterType:getLoot() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + static const std::function&)> parseLoot = [&](const std::vector& lootList) { + lua_createtable(L, lootList.size(), 0); + + int index = 0; + for (const auto& lootBlock : lootList) { + lua_createtable(L, 0, 7); + + setField(L, "itemId", lootBlock.id); + setField(L, "chance", lootBlock.chance); + setField(L, "subType", lootBlock.subType); + setField(L, "maxCount", lootBlock.countmax); + setField(L, "actionId", lootBlock.actionId); + setField(L, "text", lootBlock.text); + + parseLoot(lootBlock.childLoot); + lua_setfield(L, -2, "childLoot"); + + lua_rawseti(L, -2, ++index); + } + }; + parseLoot(monsterType->info.lootItems); + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCreatureEvents(lua_State* L) +{ + // monsterType:getCreatureEvents() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.scripts.size(), 0); + for (const std::string& creatureEvent : monsterType->info.scripts) { + pushString(L, creatureEvent); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) +{ + // monsterType:getSummonList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.summons.size(), 0); + for (const auto& summonBlock : monsterType->info.summons) { + lua_createtable(L, 0, 3); + setField(L, "name", summonBlock.name); + setField(L, "chance", summonBlock.chance); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetMaxSummons(lua_State* L) +{ + // monsterType:getMaxSummons() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.maxSummons); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetArmor(lua_State* L) +{ + // monsterType:getArmor() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.armor); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetSkill(lua_State* L) +{ + // monsterType:getSkill() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.skill); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetDefense(lua_State* L) +{ + // monsterType:getDefense() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.defense); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetOutfit(lua_State* L) +{ + // monsterType:getOutfit() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushOutfit(L, monsterType->info.outfit); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetRace(lua_State* L) +{ + // monsterType:getRace() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.race); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCorpseId(lua_State* L) +{ + // monsterType:getCorpseId() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.lookcorpse); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetManaCost(lua_State* L) +{ + // monsterType:getManaCost() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.manaCost); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetBaseSpeed(lua_State* L) +{ + // monsterType:getBaseSpeed() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.baseSpeed); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetLight(lua_State* L) +{ + // monsterType:getLight() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, monsterType->info.light.level); + lua_pushnumber(L, monsterType->info.light.color); + return 2; +} + +int LuaScriptInterface::luaMonsterTypeGetTargetDistance(lua_State* L) +{ + // monsterType:getTargetDistance() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.targetDistance); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetChangeTargetChance(lua_State* L) +{ + // monsterType:getChangeTargetChance() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.changeTargetChance); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed(lua_State* L) +{ + // monsterType:getChangeTargetSpeed() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.changeTargetSpeed); + } else { + lua_pushnil(L); + } + return 1; +} + +// Party +int LuaScriptInterface::luaPartyDisband(lua_State* L) +{ + // party:disband() + Party** partyPtr = getRawUserdata(L, 1); + if (partyPtr && *partyPtr) { + Party*& party = *partyPtr; + party->disband(); + party = nullptr; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetLeader(lua_State* L) +{ + // party:getLeader() + Party* party = getUserdata(L, 1); + if (!party) { + lua_pushnil(L); + return 1; + } + + Player* leader = party->getLeader(); + if (leader) { + pushUserdata(L, leader); + setMetatable(L, -1, "Player"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartySetLeader(lua_State* L) +{ + // party:setLeader(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->passPartyLeadership(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetMembers(lua_State* L) +{ + // party:getMembers() + Party* party = getUserdata(L, 1); + if (!party) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, party->getMemberCount(), 0); + for (Player* player : party->getMembers()) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetMemberCount(lua_State* L) +{ + // party:getMemberCount() + Party* party = getUserdata(L, 1); + if (party) { + lua_pushnumber(L, party->getMemberCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetInvitees(lua_State* L) +{ + // party:getInvitees() + Party* party = getUserdata(L, 1); + if (party) { + lua_createtable(L, party->getInvitationCount(), 0); + + int index = 0; + for (Player* player : party->getInvitees()) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetInviteeCount(lua_State* L) +{ + // party:getInviteeCount() + Party* party = getUserdata(L, 1); + if (party) { + lua_pushnumber(L, party->getInvitationCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyAddInvite(lua_State* L) +{ + // party:addInvite(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->invitePlayer(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyRemoveInvite(lua_State* L) +{ + // party:removeInvite(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->removeInvite(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyAddMember(lua_State* L) +{ + // party:addMember(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->joinParty(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyRemoveMember(lua_State* L) +{ + // party:removeMember(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->leaveParty(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyIsSharedExperienceActive(lua_State* L) +{ + // party:isSharedExperienceActive() + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->isSharedExperienceActive()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyIsSharedExperienceEnabled(lua_State* L) +{ + // party:isSharedExperienceEnabled() + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->isSharedExperienceEnabled()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyShareExperience(lua_State* L) +{ + // party:shareExperience(experience) + uint64_t experience = getNumber(L, 2); + Party* party = getUserdata(L, 1); + if (party) { + party->shareExperience(experience, nullptr); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartySetSharedExperience(lua_State* L) +{ + // party:setSharedExperience(active) + bool active = getBoolean(L, 2); + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->setSharedExperience(party->getLeader(), active)); + } else { + lua_pushnil(L); + } + return 1; +} + +// +LuaEnvironment::LuaEnvironment() : LuaScriptInterface("Main Interface") {} + +LuaEnvironment::~LuaEnvironment() +{ + delete testInterface; + closeState(); +} + +bool LuaEnvironment::initState() +{ + luaState = luaL_newstate(); + if (!luaState) { + return false; + } + + luaL_openlibs(luaState); + registerFunctions(); + + runningEventId = EVENT_ID_USER; + return true; +} + +bool LuaEnvironment::reInitState() +{ + // TODO: get children, reload children + closeState(); + return initState(); +} + +bool LuaEnvironment::closeState() +{ + if (!luaState) { + return false; + } + + for (const auto& combatEntry : combatIdMap) { + clearCombatObjects(combatEntry.first); + } + + for (const auto& areaEntry : areaIdMap) { + clearAreaObjects(areaEntry.first); + } + + for (auto& timerEntry : timerEvents) { + LuaTimerEventDesc timerEventDesc = std::move(timerEntry.second); + for (int32_t parameter : timerEventDesc.parameters) { + luaL_unref(luaState, LUA_REGISTRYINDEX, parameter); + } + luaL_unref(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + } + + combatIdMap.clear(); + areaIdMap.clear(); + timerEvents.clear(); + cacheFiles.clear(); + + lua_close(luaState); + luaState = nullptr; + return true; +} + +LuaScriptInterface* LuaEnvironment::getTestInterface() +{ + if (!testInterface) { + testInterface = new LuaScriptInterface("Test Interface"); + testInterface->initState(); + } + return testInterface; +} + +Combat* LuaEnvironment::getCombatObject(uint32_t id) const +{ + auto it = combatMap.find(id); + if (it == combatMap.end()) { + return nullptr; + } + return it->second; +} + +Combat* LuaEnvironment::createCombatObject(LuaScriptInterface* interface) +{ + Combat* combat = new Combat; + combatMap[++lastCombatId] = combat; + combatIdMap[interface].push_back(lastCombatId); + return combat; +} + +void LuaEnvironment::clearCombatObjects(LuaScriptInterface* interface) +{ + auto it = combatIdMap.find(interface); + if (it == combatIdMap.end()) { + return; + } + + for (uint32_t id : it->second) { + auto itt = combatMap.find(id); + if (itt != combatMap.end()) { + delete itt->second; + combatMap.erase(itt); + } + } + it->second.clear(); +} + +AreaCombat* LuaEnvironment::getAreaObject(uint32_t id) const +{ + auto it = areaMap.find(id); + if (it == areaMap.end()) { + return nullptr; + } + return it->second; +} + +uint32_t LuaEnvironment::createAreaObject(LuaScriptInterface* interface) +{ + areaMap[++lastAreaId] = new AreaCombat; + areaIdMap[interface].push_back(lastAreaId); + return lastAreaId; +} + +void LuaEnvironment::clearAreaObjects(LuaScriptInterface* interface) +{ + auto it = areaIdMap.find(interface); + if (it == areaIdMap.end()) { + return; + } + + for (uint32_t id : it->second) { + auto itt = areaMap.find(id); + if (itt != areaMap.end()) { + delete itt->second; + areaMap.erase(itt); + } + } + it->second.clear(); +} + +void LuaEnvironment::executeTimerEvent(uint32_t eventIndex) +{ + auto it = timerEvents.find(eventIndex); + if (it == timerEvents.end()) { + return; + } + + LuaTimerEventDesc timerEventDesc = std::move(it->second); + timerEvents.erase(it); + + //push function + lua_rawgeti(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + + //push parameters + for (auto parameter : boost::adaptors::reverse(timerEventDesc.parameters)) { + lua_rawgeti(luaState, LUA_REGISTRYINDEX, parameter); + } + + //call the function + if (reserveScriptEnv()) { + ScriptEnvironment* env = getScriptEnv(); + env->setTimerEvent(); + env->setScriptId(timerEventDesc.scriptId, this); + callFunction(timerEventDesc.parameters.size()); + } else { + std::cout << "[Error - LuaScriptInterface::executeTimerEvent] Call stack overflow" << std::endl; + } + + //free resources + luaL_unref(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + for (auto parameter : timerEventDesc.parameters) { + luaL_unref(luaState, LUA_REGISTRYINDEX, parameter); + } +} diff --git a/app/SabrehavenServer/src/luascript.h b/app/SabrehavenServer/src/luascript.h new file mode 100644 index 0000000..cbc7c4d --- /dev/null +++ b/app/SabrehavenServer/src/luascript.h @@ -0,0 +1,1283 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 +#define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 + +#if __has_include("luajit/lua.hpp") +#include +#else +#include +#endif + +#if LUA_VERSION_NUM >= 502 +#ifndef LUA_COMPAT_ALL +#ifndef LUA_COMPAT_MODULE +#define luaL_register(L, libname, l) (luaL_newlib(L, l), lua_pushvalue(L, -1), lua_setglobal(L, libname)) +#endif +#undef lua_equal +#define lua_equal(L, i1, i2) lua_compare(L, (i1), (i2), LUA_OPEQ) +#endif +#endif + +#include "database.h" +#include "enums.h" +#include "position.h" + +class Thing; +class Creature; +class Player; +class Item; +class Container; +class AreaCombat; +class Combat; +class Condition; +class Npc; +class Monster; + +enum { + EVENT_ID_LOADING = 1, + EVENT_ID_USER = 1000, +}; + +enum LuaVariantType_t { + VARIANT_NONE, + + VARIANT_NUMBER, + VARIANT_POSITION, + VARIANT_TARGETPOSITION, + VARIANT_STRING, +}; + +enum LuaDataType { + LuaData_Unknown, + + LuaData_Item, + LuaData_Container, + LuaData_Teleport, + LuaData_Player, + LuaData_Monster, + LuaData_Npc, + LuaData_Tile, +}; + +struct LuaVariant { + LuaVariantType_t type = VARIANT_NONE; + std::string text; + Position pos; + uint32_t number = 0; +}; + +struct LuaTimerEventDesc { + int32_t scriptId = -1; + int32_t function = -1; + std::list parameters; + uint32_t eventId = 0; + + LuaTimerEventDesc() = default; + LuaTimerEventDesc(LuaTimerEventDesc&& other) = default; +}; + +class LuaScriptInterface; +class Cylinder; +class Game; +class Npc; + +class ScriptEnvironment +{ + public: + ScriptEnvironment(); + ~ScriptEnvironment(); + + // non-copyable + ScriptEnvironment(const ScriptEnvironment&) = delete; + ScriptEnvironment& operator=(const ScriptEnvironment&) = delete; + + void resetEnv(); + + void setScriptId(int32_t scriptId, LuaScriptInterface* scriptInterface) { + this->scriptId = scriptId; + interface = scriptInterface; + } + bool setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface); + + int32_t getScriptId() const { + return scriptId; + } + LuaScriptInterface* getScriptInterface() { + return interface; + } + + void setTimerEvent() { + timerEvent = true; + } + + void getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const; + + void addTempItem(Item* item); + static void removeTempItem(Item* item); + uint32_t addThing(Thing* thing); + void insertItem(uint32_t uid, Item* item); + + static DBResult_ptr getResultByID(uint32_t id); + static uint32_t addResult(DBResult_ptr res); + static bool removeResult(uint32_t id); + + void setNpc(Npc* npc) { + curNpc = npc; + } + Npc* getNpc() const { + return curNpc; + } + + Thing* getThingByUID(uint32_t uid); + Item* getItemByUID(uint32_t uid); + Container* getContainerByUID(uint32_t uid); + void removeItemByUID(uint32_t uid); + + private: + typedef std::vector VariantVector; + typedef std::map StorageMap; + typedef std::map DBResultMap; + + LuaScriptInterface* interface; + + //for npc scripts + Npc* curNpc = nullptr; + + //temporary item list + static std::multimap tempItems; + + //local item map + std::unordered_map localMap; + uint32_t lastUID = std::numeric_limits::max(); + + //script file id + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + + //result map + static uint32_t lastResultId; + static DBResultMap tempResults; +}; + +#define reportErrorFunc(a) reportError(__FUNCTION__, a, true) + +enum ErrorCode_t { + LUA_ERROR_PLAYER_NOT_FOUND, + LUA_ERROR_CREATURE_NOT_FOUND, + LUA_ERROR_ITEM_NOT_FOUND, + LUA_ERROR_THING_NOT_FOUND, + LUA_ERROR_TILE_NOT_FOUND, + LUA_ERROR_HOUSE_NOT_FOUND, + LUA_ERROR_COMBAT_NOT_FOUND, + LUA_ERROR_CONDITION_NOT_FOUND, + LUA_ERROR_AREA_NOT_FOUND, + LUA_ERROR_CONTAINER_NOT_FOUND, + LUA_ERROR_VARIANT_NOT_FOUND, + LUA_ERROR_VARIANT_UNKNOWN, + LUA_ERROR_SPELL_NOT_FOUND, +}; + +class LuaScriptInterface +{ + public: + explicit LuaScriptInterface(std::string interfaceName); + virtual ~LuaScriptInterface(); + + // non-copyable + LuaScriptInterface(const LuaScriptInterface&) = delete; + LuaScriptInterface& operator=(const LuaScriptInterface&) = delete; + + virtual bool initState(); + bool reInitState(); + + int32_t loadFile(const std::string& file, Npc* npc = nullptr); + + const std::string& getFileById(int32_t scriptId); + int32_t getEvent(const std::string& eventName); + int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); + + static ScriptEnvironment* getScriptEnv() { + assert(scriptEnvIndex >= 0 && scriptEnvIndex < 16); + return scriptEnv + scriptEnvIndex; + } + + static bool reserveScriptEnv() { + return ++scriptEnvIndex < 16; + } + + static void resetScriptEnv() { + assert(scriptEnvIndex >= 0); + scriptEnv[scriptEnvIndex--].resetEnv(); + } + + static void reportError(const char* function, const std::string& error_desc, bool stack_trace = false); + + const std::string& getInterfaceName() const { + return interfaceName; + } + const std::string& getLastLuaError() const { + return lastLuaError; + } + + lua_State* getLuaState() const { + return luaState; + } + + bool pushFunction(int32_t functionId); + + static int luaErrorHandler(lua_State* L); + bool callFunction(int params); + void callVoidFunction(int params); + + //push/pop common structures + static void pushThing(lua_State* L, Thing* thing); + static void pushVariant(lua_State* L, const LuaVariant& var); + static void pushString(lua_State* L, const std::string& value); + static void pushCallback(lua_State* L, int32_t callback); + static void pushCylinder(lua_State* L, Cylinder* cylinder); + + static std::string popString(lua_State* L); + static int32_t popCallback(lua_State* L); + + // Userdata + template + static void pushUserdata(lua_State* L, T* value) + { + T** userdata = static_cast(lua_newuserdata(L, sizeof(T*))); + *userdata = value; + } + + // Metatables + static void setMetatable(lua_State* L, int32_t index, const std::string& name); + static void setWeakMetatable(lua_State* L, int32_t index, const std::string& name); + + static void setItemMetatable(lua_State* L, int32_t index, const Item* item); + static void setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature); + + // Get + template + inline static typename std::enable_if::value, T>::type + getNumber(lua_State* L, int32_t arg) + { + return static_cast(static_cast(lua_tonumber(L, arg))); + } + template + inline static typename std::enable_if::value || std::is_floating_point::value, T>::type + getNumber(lua_State* L, int32_t arg) + { + return static_cast(lua_tonumber(L, arg)); + } + template + static T getNumber(lua_State *L, int32_t arg, T defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; + } + return getNumber(L, arg); + } + template + static T* getUserdata(lua_State* L, int32_t arg) + { + T** userdata = getRawUserdata(L, arg); + if (!userdata) { + return nullptr; + } + return *userdata; + } + template + inline static T** getRawUserdata(lua_State* L, int32_t arg) + { + return static_cast(lua_touserdata(L, arg)); + } + + inline static bool getBoolean(lua_State* L, int32_t arg) + { + return lua_toboolean(L, arg) != 0; + } + inline static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; + } + return lua_toboolean(L, arg) != 0; + } + + static std::string getString(lua_State* L, int32_t arg); + static CombatDamage getCombatDamage(lua_State* L); + static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); + static Position getPosition(lua_State* L, int32_t arg); + static Outfit_t getOutfit(lua_State* L, int32_t arg); + static LuaVariant getVariant(lua_State* L, int32_t arg); + + static Thing* getThing(lua_State* L, int32_t arg); + static Creature* getCreature(lua_State* L, int32_t arg); + static Player* getPlayer(lua_State* L, int32_t arg); + + template + static T getField(lua_State* L, int32_t arg, const std::string& key) + { + lua_getfield(L, arg, key.c_str()); + return getNumber(L, -1); + } + + static std::string getFieldString(lua_State* L, int32_t arg, const std::string& key); + + static LuaDataType getUserdataType(lua_State* L, int32_t arg); + + // Is + inline static bool isNumber(lua_State* L, int32_t arg) + { + return lua_type(L, arg) == LUA_TNUMBER; + } + inline static bool isString(lua_State* L, int32_t arg) + { + return lua_isstring(L, arg) != 0; + } + inline static bool isBoolean(lua_State* L, int32_t arg) + { + return lua_isboolean(L, arg); + } + inline static bool isTable(lua_State* L, int32_t arg) + { + return lua_istable(L, arg); + } + inline static bool isFunction(lua_State* L, int32_t arg) + { + return lua_isfunction(L, arg); + } + inline static bool isUserdata(lua_State* L, int32_t arg) + { + return lua_isuserdata(L, arg) != 0; + } + + // Push + static void pushBoolean(lua_State* L, bool value); + static void pushCombatDamage(lua_State* L, const CombatDamage& damage); + static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); + static void pushOutfit(lua_State* L, const Outfit_t& outfit); + + // + inline static void setField(lua_State* L, const char* index, lua_Number value) + { + lua_pushnumber(L, value); + lua_setfield(L, -2, index); + } + + inline static void setField(lua_State* L, const char* index, const std::string& value) + { + pushString(L, value); + lua_setfield(L, -2, index); + } + + static std::string escapeString(const std::string& string); + +#ifndef LUAJIT_VERSION + static const luaL_Reg luaBitReg[7]; +#endif + static const luaL_Reg luaConfigManagerTable[4]; + static const luaL_Reg luaDatabaseTable[9]; + static const luaL_Reg luaResultTable[6]; + + static int protectedCall(lua_State* L, int nargs, int nresults); + + protected: + virtual bool closeState(); + + void registerFunctions(); + + void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); + void registerTable(const std::string& tableName); + void registerMethod(const std::string& className, const std::string& methodName, lua_CFunction func); + void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); + void registerGlobalMethod(const std::string& functionName, lua_CFunction func); + void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); + void registerGlobalVariable(const std::string& name, lua_Number value); + void registerGlobalBoolean(const std::string& name, bool value); + + std::string getStackTrace(const std::string& error_desc); + + static std::string getErrorDesc(ErrorCode_t code); + static bool getArea(lua_State* L, std::list& list, uint32_t& rows); + + //lua functions + static int luaDoCreateItem(lua_State* L); + static int luaDoCreateItemEx(lua_State* L); + static int luaDoMoveCreature(lua_State* L); + + static int luaDoPlayerAddItem(lua_State* L); + static int luaDoTileAddItemEx(lua_State* L); + static int luaDoSetCreatureLight(lua_State* L); + + //get item info + static int luaGetDepotId(lua_State* L); + + //get creature info functions + static int luaGetPlayerFlagValue(lua_State* L); + static int luaGetCreatureCondition(lua_State* L); + + static int luaGetPlayerInstantSpellInfo(lua_State* L); + static int luaGetPlayerInstantSpellCount(lua_State* L); + + static int luaGetWorldTime(lua_State* L); + static int luaGetWorldLight(lua_State* L); + static int luaGetWorldUpTime(lua_State* L); + + //type validation + static int luaIsDepot(lua_State* L); + static int luaIsMoveable(lua_State* L); + static int luaIsValidUID(lua_State* L); + + //container + static int luaDoAddContainerItem(lua_State* L); + + // + static int luaCreateCombatArea(lua_State* L); + + static int luaDoAreaCombatHealth(lua_State* L); + static int luaDoTargetCombatHealth(lua_State* L); + + // + static int luaDoAreaCombatMana(lua_State* L); + static int luaDoTargetCombatMana(lua_State* L); + + static int luaDoAreaCombatCondition(lua_State* L); + static int luaDoTargetCombatCondition(lua_State* L); + + static int luaDoAreaCombatDispel(lua_State* L); + static int luaDoTargetCombatDispel(lua_State* L); + + static int luaDoChallengeCreature(lua_State* L); + + static int luaSetCreatureOutfit(lua_State* L); + static int luaSetMonsterOutfit(lua_State* L); + static int luaSetItemOutfit(lua_State* L); + + static int luaDebugPrint(lua_State* L); + static int luaIsInArray(lua_State* L); + static int luaAddEvent(lua_State* L); + static int luaStopEvent(lua_State* L); + + static int luaSaveServer(lua_State* L); + static int luaCleanMap(lua_State* L); + + static int luaIsInWar(lua_State* L); + + static int luaGetWaypointPositionByName(lua_State* L); + + static int luaSendChannelMessage(lua_State* L); + static int luaSendGuildChannelMessage(lua_State* L); + +#ifndef LUAJIT_VERSION + static int luaBitNot(lua_State* L); + static int luaBitAnd(lua_State* L); + static int luaBitOr(lua_State* L); + static int luaBitXor(lua_State* L); + static int luaBitLeftShift(lua_State* L); + static int luaBitRightShift(lua_State* L); +#endif + + static int luaConfigManagerGetString(lua_State* L); + static int luaConfigManagerGetNumber(lua_State* L); + static int luaConfigManagerGetBoolean(lua_State* L); + + static int luaDatabaseExecute(lua_State* L); + static int luaDatabaseAsyncExecute(lua_State* L); + static int luaDatabaseStoreQuery(lua_State* L); + static int luaDatabaseAsyncStoreQuery(lua_State* L); + static int luaDatabaseEscapeString(lua_State* L); + static int luaDatabaseEscapeBlob(lua_State* L); + static int luaDatabaseLastInsertId(lua_State* L); + static int luaDatabaseTableExists(lua_State* L); + + static int luaResultGetNumber(lua_State* L); + static int luaResultGetString(lua_State* L); + static int luaResultGetStream(lua_State* L); + static int luaResultNext(lua_State* L); + static int luaResultFree(lua_State* L); + + // Userdata + static int luaUserdataCompare(lua_State* L); + + // _G + static int luaIsType(lua_State* L); + static int luaRawGetMetatable(lua_State* L); + + // random + static int luaRandomRand(lua_State* L); + + // os + static int luaSystemTime(lua_State* L); + + // table + static int luaTableCreate(lua_State* L); + + // Game + static int luaGameGetSpectators(lua_State* L); + static int luaGameGetPlayers(lua_State* L); + static int luaGameLoadMap(lua_State* L); + + static int luaGameGetExperienceStage(lua_State* L); + static int luaGameGetMonsterCount(lua_State* L); + static int luaGameGetPlayerCount(lua_State* L); + static int luaGameGetNpcCount(lua_State* L); + + static int luaGameGetTowns(lua_State* L); + static int luaGameGetHouses(lua_State* L); + + static int luaGameGetGameState(lua_State* L); + static int luaGameSetGameState(lua_State* L); + + static int luaGameGetWorldType(lua_State* L); + static int luaGameSetWorldType(lua_State* L); + + static int luaGameGetReturnMessage(lua_State* L); + + static int luaGameCreateItem(lua_State* L); + static int luaGameCreateContainer(lua_State* L); + static int luaGameCreateMonster(lua_State* L); + static int luaGameCreateNpc(lua_State* L); + static int luaGameCreateTile(lua_State* L); + + static int luaGameStartRaid(lua_State* L); + + static int luaGameReload(lua_State* L); + + // Variant + static int luaVariantCreate(lua_State* L); + + static int luaVariantGetNumber(lua_State* L); + static int luaVariantGetString(lua_State* L); + static int luaVariantGetPosition(lua_State* L); + + // Position + static int luaPositionCreate(lua_State* L); + static int luaPositionAdd(lua_State* L); + static int luaPositionSub(lua_State* L); + static int luaPositionCompare(lua_State* L); + + static int luaPositionGetDistance(lua_State* L); + static int luaPositionIsSightClear(lua_State* L); + + static int luaPositionSendMagicEffect(lua_State* L); + static int luaPositionSendDistanceEffect(lua_State* L); + static int luaPositionSendMonsterSay(lua_State* L); + + // Tile + static int luaTileCreate(lua_State* L); + + static int luaTileGetPosition(lua_State* L); + static int luaTileGetGround(lua_State* L); + static int luaTileGetThing(lua_State* L); + static int luaTileGetThingCount(lua_State* L); + static int luaTileGetTopVisibleThing(lua_State* L); + + static int luaTileGetTopTopItem(lua_State* L); + static int luaTileGetTopDownItem(lua_State* L); + static int luaTileGetFieldItem(lua_State* L); + + static int luaTileGetItemById(lua_State* L); + static int luaTileGetItemByType(lua_State* L); + static int luaTileGetItemByTopOrder(lua_State* L); + static int luaTileGetItemCountById(lua_State* L); + + static int luaTileGetBottomCreature(lua_State* L); + static int luaTileGetTopCreature(lua_State* L); + static int luaTileGetBottomVisibleCreature(lua_State* L); + static int luaTileGetTopVisibleCreature(lua_State* L); + + static int luaTileGetItems(lua_State* L); + static int luaTileGetItemCount(lua_State* L); + static int luaTileGetDownItemCount(lua_State* L); + static int luaTileGetTopItemCount(lua_State* L); + + static int luaTileGetCreatures(lua_State* L); + static int luaTileGetCreatureCount(lua_State* L); + + static int luaTileHasProperty(lua_State* L); + static int luaTileHasFlag(lua_State* L); + + static int luaTileGetThingIndex(lua_State* L); + + static int luaTileQueryAdd(lua_State* L); + + static int luaTileGetHouse(lua_State* L); + + // NetworkMessage + static int luaNetworkMessageCreate(lua_State* L); + static int luaNetworkMessageDelete(lua_State* L); + + static int luaNetworkMessageGetByte(lua_State* L); + static int luaNetworkMessageGetU16(lua_State* L); + static int luaNetworkMessageGetU32(lua_State* L); + static int luaNetworkMessageGetU64(lua_State* L); + static int luaNetworkMessageGetString(lua_State* L); + static int luaNetworkMessageGetPosition(lua_State* L); + + static int luaNetworkMessageAddByte(lua_State* L); + static int luaNetworkMessageAddU16(lua_State* L); + static int luaNetworkMessageAddU32(lua_State* L); + static int luaNetworkMessageAddU64(lua_State* L); + static int luaNetworkMessageAddString(lua_State* L); + static int luaNetworkMessageAddPosition(lua_State* L); + static int luaNetworkMessageAddDouble(lua_State* L); + static int luaNetworkMessageAddItem(lua_State* L); + static int luaNetworkMessageAddItemId(lua_State* L); + + static int luaNetworkMessageReset(lua_State* L); + static int luaNetworkMessageSkipBytes(lua_State* L); + static int luaNetworkMessageSendToPlayer(lua_State* L); + + // Item + static int luaItemCreate(lua_State* L); + + static int luaItemIsItem(lua_State* L); + + static int luaItemGetParent(lua_State* L); + static int luaItemGetTopParent(lua_State* L); + + static int luaItemGetId(lua_State* L); + + static int luaItemClone(lua_State* L); + static int luaItemSplit(lua_State* L); + static int luaItemRemove(lua_State* L); + + static int luaItemGetMovementId(lua_State* L); + static int luaItemSetMovementId(lua_State* L); + static int luaItemGetActionId(lua_State* L); + static int luaItemSetActionId(lua_State* L); + static int luaItemGetUniqueId(lua_State* L); + + static int luaItemGetCount(lua_State* L); + static int luaItemGetCharges(lua_State* L); + static int luaItemGetFluidType(lua_State* L); + static int luaItemGetWeight(lua_State* L); + + static int luaItemGetSubType(lua_State* L); + + static int luaItemGetName(lua_State* L); + static int luaItemGetPluralName(lua_State* L); + static int luaItemGetArticle(lua_State* L); + + static int luaItemGetPosition(lua_State* L); + static int luaItemGetTile(lua_State* L); + + static int luaItemHasAttribute(lua_State* L); + static int luaItemGetAttribute(lua_State* L); + static int luaItemSetAttribute(lua_State* L); + static int luaItemRemoveAttribute(lua_State* L); + + static int luaItemMoveTo(lua_State* L); + static int luaItemTransform(lua_State* L); + static int luaItemDecay(lua_State* L); + + static int luaItemGetDescription(lua_State* L); + + static int luaItemHasProperty(lua_State* L); + + // Container + static int luaContainerCreate(lua_State* L); + + static int luaContainerGetSize(lua_State* L); + static int luaContainerGetCapacity(lua_State* L); + static int luaContainerGetEmptySlots(lua_State* L); + + static int luaContainerGetItemHoldingCount(lua_State* L); + static int luaContainerGetItemCountById(lua_State* L); + + static int luaContainerGetItem(lua_State* L); + static int luaContainerHasItem(lua_State* L); + static int luaContainerAddItem(lua_State* L); + static int luaContainerAddItemEx(lua_State* L); + + // Teleport + static int luaTeleportCreate(lua_State* L); + + static int luaTeleportGetDestination(lua_State* L); + static int luaTeleportSetDestination(lua_State* L); + + // Creature + static int luaCreatureCreate(lua_State* L); + + static int luaCreatureGetEvents(lua_State* L); + static int luaCreatureRegisterEvent(lua_State* L); + static int luaCreatureUnregisterEvent(lua_State* L); + + static int luaCreatureIsRemoved(lua_State* L); + static int luaCreatureIsCreature(lua_State* L); + static int luaCreatureIsInGhostMode(lua_State* L); + + static int luaCreatureCanSee(lua_State* L); + static int luaCreatureCanSeeCreature(lua_State* L); + + static int luaCreatureGetParent(lua_State* L); + + static int luaCreatureGetId(lua_State* L); + static int luaCreatureGetName(lua_State* L); + + static int luaCreatureGetTarget(lua_State* L); + static int luaCreatureSetTarget(lua_State* L); + + static int luaCreatureGetFollowCreature(lua_State* L); + static int luaCreatureSetFollowCreature(lua_State* L); + + static int luaCreatureGetMaster(lua_State* L); + static int luaCreatureSetMaster(lua_State* L); + + static int luaCreatureGetLight(lua_State* L); + static int luaCreatureSetLight(lua_State* L); + + static int luaCreatureGetSpeed(lua_State* L); + static int luaCreatureGetBaseSpeed(lua_State* L); + static int luaCreatureChangeSpeed(lua_State* L); + + static int luaCreatureSetDropLoot(lua_State* L); + + static int luaCreatureGetPosition(lua_State* L); + static int luaCreatureGetTile(lua_State* L); + static int luaCreatureGetDirection(lua_State* L); + static int luaCreatureSetDirection(lua_State* L); + + static int luaCreatureGetHealth(lua_State* L); + static int luaCreatureAddHealth(lua_State* L); + static int luaCreatureGetMaxHealth(lua_State* L); + static int luaCreatureSetMaxHealth(lua_State* L); + static int luaCreatureSetHiddenHealth(lua_State* L); + + static int luaCreatureGetSkull(lua_State* L); + static int luaCreatureSetSkull(lua_State* L); + + static int luaCreatureGetOutfit(lua_State* L); + static int luaCreatureSetOutfit(lua_State* L); + + static int luaCreatureGetCondition(lua_State* L); + static int luaCreatureAddCondition(lua_State* L); + static int luaCreatureRemoveCondition(lua_State* L); + + static int luaCreatureRemove(lua_State* L); + static int luaCreatureTeleportTo(lua_State* L); + static int luaCreatureSay(lua_State* L); + + static int luaCreatureGetDamageMap(lua_State* L); + + static int luaCreatureGetSummons(lua_State* L); + + static int luaCreatureGetDescription(lua_State* L); + + static int luaCreatureGetPathTo(lua_State* L); + + // Player + static int luaPlayerCreate(lua_State* L); + + static int luaPlayerIsPlayer(lua_State* L); + + static int luaPlayerGetGuid(lua_State* L); + static int luaPlayerGetIp(lua_State* L); + static int luaPlayerGetAccountId(lua_State* L); + static int luaPlayerGetLastLoginSaved(lua_State* L); + static int luaPlayerGetLastLogout(lua_State* L); + static int luaPlayerHasFlag(lua_State* L); + + static int luaPlayerGetAccountType(lua_State* L); + static int luaPlayerSetAccountType(lua_State* L); + + static int luaPlayerGetCapacity(lua_State* L); + static int luaPlayerSetCapacity(lua_State* L); + + static int luaPlayerGetFreeCapacity(lua_State* L); + + static int luaPlayerGetDepotChest(lua_State* L); + + static int luaPlayerGetMurderTimestamps(lua_State* L); + static int luaPlayerGetPlayerKillerEnd(lua_State* L); + static int luaPlayerSetPlayerKillerEnd(lua_State* L); + static int luaPlayerGetDeathPenalty(lua_State* L); + + static int luaPlayerGetExperience(lua_State* L); + static int luaPlayerAddExperience(lua_State* L); + static int luaPlayerRemoveExperience(lua_State* L); + static int luaPlayerGetLevel(lua_State* L); + + static int luaPlayerGetMagicLevel(lua_State* L); + static int luaPlayerGetBaseMagicLevel(lua_State* L); + static int luaPlayerGetMana(lua_State* L); + static int luaPlayerAddMana(lua_State* L); + static int luaPlayerGetMaxMana(lua_State* L); + static int luaPlayerSetMaxMana(lua_State* L); + static int luaPlayerGetManaSpent(lua_State* L); + static int luaPlayerAddManaSpent(lua_State* L); + + static int luaPlayerGetBaseMaxHealth(lua_State* L); + static int luaPlayerGetBaseMaxMana(lua_State* L); + + static int luaPlayerGetSkillLevel(lua_State* L); + static int luaPlayerGetEffectiveSkillLevel(lua_State* L); + static int luaPlayerGetSkillPercent(lua_State* L); + static int luaPlayerGetSkillTries(lua_State* L); + static int luaPlayerAddSkillTries(lua_State* L); + + static int luaPlayerAddOfflineTrainingTime(lua_State* L); + static int luaPlayerGetOfflineTrainingTime(lua_State* L); + static int luaPlayerRemoveOfflineTrainingTime(lua_State* L); + + static int luaPlayerAddOfflineTrainingTries(lua_State* L); + + static int luaPlayerGetOfflineTrainingSkill(lua_State* L); + static int luaPlayerSetOfflineTrainingSkill(lua_State* L); + + static int luaPlayerGetItemCount(lua_State* L); + static int luaPlayerGetItemById(lua_State* L); + + static int luaPlayerGetVocation(lua_State* L); + static int luaPlayerSetVocation(lua_State* L); + + static int luaPlayerGetSex(lua_State* L); + static int luaPlayerSetSex(lua_State* L); + + static int luaPlayerGetTown(lua_State* L); + static int luaPlayerSetTown(lua_State* L); + + static int luaPlayerGetGuild(lua_State* L); + static int luaPlayerSetGuild(lua_State* L); + + static int luaPlayerGetGuildLevel(lua_State* L); + static int luaPlayerSetGuildLevel(lua_State* L); + + static int luaPlayerGetGuildNick(lua_State* L); + static int luaPlayerSetGuildNick(lua_State* L); + + static int luaPlayerGetGroup(lua_State* L); + static int luaPlayerSetGroup(lua_State* L); + + static int luaPlayerGetStamina(lua_State* L); + static int luaPlayerSetStamina(lua_State* L); + + static int luaPlayerGetSoul(lua_State* L); + static int luaPlayerAddSoul(lua_State* L); + static int luaPlayerGetMaxSoul(lua_State* L); + + static int luaPlayerGetBankBalance(lua_State* L); + static int luaPlayerSetBankBalance(lua_State* L); + + static int luaPlayerGetStorageValue(lua_State* L); + static int luaPlayerSetStorageValue(lua_State* L); + + static int luaPlayerAddItem(lua_State* L); + static int luaPlayerAddItemEx(lua_State* L); + static int luaPlayerRemoveItem(lua_State* L); + + static int luaPlayerGetMoney(lua_State* L); + static int luaPlayerAddMoney(lua_State* L); + static int luaPlayerRemoveMoney(lua_State* L); + + static int luaPlayerShowTextDialog(lua_State* L); + + static int luaPlayerSendTextMessage(lua_State* L); + static int luaPlayerSendPrivateMessage(lua_State* L); + + static int luaPlayerChannelSay(lua_State* L); + static int luaPlayerOpenChannel(lua_State* L); + + static int luaPlayerGetSlotItem(lua_State* L); + + static int luaPlayerGetParty(lua_State* L); + + static int luaPlayerAddOutfit(lua_State* L); + static int luaPlayerAddOutfitAddon(lua_State* L); + static int luaPlayerRemoveOutfit(lua_State* L); + static int luaPlayerRemoveOutfitAddon(lua_State* L); + static int luaPlayerHasOutfit(lua_State* L); + static int luaPlayerSendOutfitWindow(lua_State* L); + + static int luaPlayerGetPremiumDays(lua_State* L); + static int luaPlayerAddPremiumDays(lua_State* L); + static int luaPlayerRemovePremiumDays(lua_State* L); + + static int luaPlayerHasBlessing(lua_State* L); + static int luaPlayerAddBlessing(lua_State* L); + static int luaPlayerRemoveBlessing(lua_State* L); + + static int luaPlayerCanLearnSpell(lua_State* L); + static int luaPlayerLearnSpell(lua_State* L); + static int luaPlayerForgetSpell(lua_State* L); + static int luaPlayerHasLearnedSpell(lua_State* L); + + static int luaPlayerSave(lua_State* L); + + static int luaPlayerIsPzLocked(lua_State* L); + static int luaPlayerIsFakePlayer(lua_State* L); + + static int luaPlayerGetClient(lua_State* L); + static int luaPlayerGetHouse(lua_State* L); + + static int luaPlayerSetGhostMode(lua_State* L); + + static int luaPlayerGetContainerId(lua_State* L); + static int luaPlayerGetContainerById(lua_State* L); + static int luaPlayerGetContainerIndex(lua_State* L); + + static int luaPlayerGetTotalDamage(lua_State* L); + + // Monster + static int luaMonsterCreate(lua_State* L); + + static int luaMonsterIsMonster(lua_State* L); + + static int luaMonsterGetType(lua_State* L); + + static int luaMonsterGetSpawnPosition(lua_State* L); + static int luaMonsterIsInSpawnRange(lua_State* L); + + static int luaMonsterIsIdle(lua_State* L); + static int luaMonsterSetIdle(lua_State* L); + + static int luaMonsterIsTarget(lua_State* L); + static int luaMonsterIsOpponent(lua_State* L); + static int luaMonsterIsFriend(lua_State* L); + + static int luaMonsterAddFriend(lua_State* L); + static int luaMonsterRemoveFriend(lua_State* L); + static int luaMonsterGetFriendList(lua_State* L); + static int luaMonsterGetFriendCount(lua_State* L); + + static int luaMonsterAddTarget(lua_State* L); + static int luaMonsterRemoveTarget(lua_State* L); + static int luaMonsterGetTargetList(lua_State* L); + static int luaMonsterGetTargetCount(lua_State* L); + + static int luaMonsterSelectTarget(lua_State* L); + static int luaMonsterSearchTarget(lua_State* L); + + // Npc + static int luaNpcCreate(lua_State* L); + + static int luaNpcIsNpc(lua_State* L); + + static int luaNpcSetMasterPos(lua_State* L); + + // Guild + static int luaGuildCreate(lua_State* L); + + static int luaGuildGetId(lua_State* L); + static int luaGuildGetName(lua_State* L); + static int luaGuildGetMembersOnline(lua_State* L); + static int luaGuildSetGuildWarEmblem(lua_State* L); + + static int luaGuildAddRank(lua_State* L); + static int luaGuildGetRankById(lua_State* L); + static int luaGuildGetRankByLevel(lua_State* L); + + static int luaGuildGetBankBalance(lua_State* L); + static int luaGuildIncreaseBankBalance(lua_State* L); + static int luaGuildDecreaseBankBalance(lua_State* L); + + // Group + static int luaGroupCreate(lua_State* L); + + static int luaGroupGetId(lua_State* L); + static int luaGroupGetName(lua_State* L); + static int luaGroupGetFlags(lua_State* L); + static int luaGroupGetAccess(lua_State* L); + static int luaGroupGetMaxDepotItems(lua_State* L); + static int luaGroupGetMaxVipEntries(lua_State* L); + + // Vocation + static int luaVocationCreate(lua_State* L); + + static int luaVocationGetId(lua_State* L); + static int luaVocationGetName(lua_State* L); + static int luaVocationGetDescription(lua_State* L); + + static int luaVocationGetRequiredSkillTries(lua_State* L); + static int luaVocationGetRequiredManaSpent(lua_State* L); + + static int luaVocationGetCapacityGain(lua_State* L); + + static int luaVocationGetHealthGain(lua_State* L); + static int luaVocationGetHealthGainTicks(lua_State* L); + static int luaVocationGetHealthGainAmount(lua_State* L); + + static int luaVocationGetManaGain(lua_State* L); + static int luaVocationGetManaGainTicks(lua_State* L); + static int luaVocationGetManaGainAmount(lua_State* L); + + static int luaVocationGetMaxSoul(lua_State* L); + static int luaVocationGetSoulGainTicks(lua_State* L); + + static int luaVocationGetAttackSpeed(lua_State* L); + static int luaVocationGetBaseSpeed(lua_State* L); + + static int luaVocationGetDemotion(lua_State* L); + static int luaVocationGetPromotion(lua_State* L); + + // Town + static int luaTownCreate(lua_State* L); + + static int luaTownGetId(lua_State* L); + static int luaTownGetName(lua_State* L); + static int luaTownGetTemplePosition(lua_State* L); + + // House + static int luaHouseCreate(lua_State* L); + + static int luaHouseGetId(lua_State* L); + static int luaHouseGetName(lua_State* L); + static int luaHouseGetTown(lua_State* L); + static int luaHouseGetExitPosition(lua_State* L); + static int luaHouseGetRent(lua_State* L); + + static int luaHouseGetOwnerGuid(lua_State* L); + static int luaHouseSetOwnerGuid(lua_State* L); + static int luaHouseStartTrade(lua_State* L); + + static int luaHouseGetBeds(lua_State* L); + static int luaHouseGetBedCount(lua_State* L); + + static int luaHouseGetDoors(lua_State* L); + static int luaHouseGetDoorCount(lua_State* L); + + static int luaHouseGetTiles(lua_State* L); + static int luaHouseGetTileCount(lua_State* L); + + static int luaHouseGetAccessList(lua_State* L); + static int luaHouseSetAccessList(lua_State* L); + + // ItemType + static int luaItemTypeCreate(lua_State* L); + + static int luaItemTypeIsCorpse(lua_State* L); + static int luaItemTypeIsDoor(lua_State* L); + static int luaItemTypeIsContainer(lua_State* L); + static int luaItemTypeIsChest(lua_State* L); + static int luaItemTypeIsFluidContainer(lua_State* L); + static int luaItemTypeIsMovable(lua_State* L); + static int luaItemTypeIsRune(lua_State* L); + static int luaItemTypeIsStackable(lua_State* L); + static int luaItemTypeIsReadable(lua_State* L); + static int luaItemTypeIsWritable(lua_State* L); + static int luaItemTypeIsMagicField(lua_State* L); + static int luaItemTypeIsSplash(lua_State* L); + static int luaItemTypeIsKey(lua_State* L); + static int luaItemTypeIsDisguised(lua_State* L); + static int luaItemTypeIsDestroyable(lua_State* L); + static int luaItemTypeIsGroundTile(lua_State* L); + + static int luaItemTypeGetType(lua_State* L); + static int luaItemTypeGetId(lua_State* L); + static int luaItemTypeGetDisguiseId(lua_State* L); + static int luaItemTypeGetName(lua_State* L); + static int luaItemTypeGetPluralName(lua_State* L); + static int luaItemTypeGetArticle(lua_State* L); + static int luaItemTypeGetDescription(lua_State* L); + static int luaItemTypeGetSlotPosition(lua_State *L); + static int luaItemTypeGetDestroyTarget(lua_State* L); + + static int luaItemTypeGetCharges(lua_State* L); + static int luaItemTypeGetFluidSource(lua_State* L); + static int luaItemTypeGetCapacity(lua_State* L); + static int luaItemTypeGetWeight(lua_State* L); + + static int luaItemTypeGetShootRange(lua_State* L); + static int luaItemTypeGetAttack(lua_State* L); + static int luaItemTypeGetDefense(lua_State* L); + static int luaItemTypeGetArmor(lua_State* L); + static int luaItemTypeGetWeaponType(lua_State* L); + static int luaItemTypeGetAmmoType(lua_State* L); + + static int luaItemTypeGetTransformEquipId(lua_State* L); + static int luaItemTypeGetTransformDeEquipId(lua_State* L); + static int luaItemTypeGetDecayId(lua_State* L); + static int luaItemTypeGetNutrition(lua_State* L); + static int luaItemTypeGetRequiredLevel(lua_State* L); + + static int luaItemTypeHasSubType(lua_State* L); + + // Combat + static int luaCombatCreate(lua_State* L); + + static int luaCombatSetParameter(lua_State* L); + static int luaCombatSetFormula(lua_State* L); + + static int luaCombatSetArea(lua_State* L); + static int luaCombatSetCondition(lua_State* L); + static int luaCombatSetCallback(lua_State* L); + static int luaCombatSetOrigin(lua_State* L); + + static int luaCombatExecute(lua_State* L); + + // Condition + static int luaConditionCreate(lua_State* L); + static int luaConditionDelete(lua_State* L); + + static int luaConditionGetId(lua_State* L); + static int luaConditionGetSubId(lua_State* L); + static int luaConditionGetType(lua_State* L); + static int luaConditionGetIcons(lua_State* L); + static int luaConditionGetEndTime(lua_State* L); + + static int luaConditionClone(lua_State* L); + + static int luaConditionGetTicks(lua_State* L); + static int luaConditionSetTicks(lua_State* L); + + static int luaConditionSetParameter(lua_State* L); + static int luaConditionSetSpeedDelta(lua_State* L); + static int luaConditionSetOutfit(lua_State* L); + + static int luaConditionSetTiming(lua_State* L); + + // MonsterType + static int luaMonsterTypeCreate(lua_State* L); + + static int luaMonsterTypeIsAttackable(lua_State* L); + static int luaMonsterTypeIsConvinceable(lua_State* L); + static int luaMonsterTypeIsSummonable(lua_State* L); + static int luaMonsterTypeIsIllusionable(lua_State* L); + static int luaMonsterTypeIsHostile(lua_State* L); + static int luaMonsterTypeIsPushable(lua_State* L); + static int luaMonsterTypeIsHealthShown(lua_State* L); + + static int luaMonsterTypeCanPushItems(lua_State* L); + static int luaMonsterTypeCanPushCreatures(lua_State* L); + + static int luaMonsterTypeGetName(lua_State* L); + static int luaMonsterTypeGetNameDescription(lua_State* L); + + static int luaMonsterTypeGetHealth(lua_State* L); + static int luaMonsterTypeGetMaxHealth(lua_State* L); + static int luaMonsterTypeGetRunHealth(lua_State* L); + static int luaMonsterTypeGetExperience(lua_State* L); + + static int luaMonsterTypeGetCombatImmunities(lua_State* L); + static int luaMonsterTypeGetConditionImmunities(lua_State* L); + + static int luaMonsterTypeGetAttackList(lua_State* L); + static int luaMonsterTypeGetDefenseList(lua_State* L); + static int luaMonsterTypeGetElementList(lua_State* L); + + static int luaMonsterTypeGetVoices(lua_State* L); + static int luaMonsterTypeGetLoot(lua_State* L); + static int luaMonsterTypeGetCreatureEvents(lua_State* L); + + static int luaMonsterTypeGetSummonList(lua_State* L); + static int luaMonsterTypeGetMaxSummons(lua_State* L); + + static int luaMonsterTypeGetArmor(lua_State* L); + static int luaMonsterTypeGetSkill(lua_State* L); + static int luaMonsterTypeGetDefense(lua_State* L); + static int luaMonsterTypeGetOutfit(lua_State* L); + static int luaMonsterTypeGetRace(lua_State* L); + static int luaMonsterTypeGetCorpseId(lua_State* L); + static int luaMonsterTypeGetManaCost(lua_State* L); + static int luaMonsterTypeGetBaseSpeed(lua_State* L); + static int luaMonsterTypeGetLight(lua_State* L); + + static int luaMonsterTypeGetTargetDistance(lua_State* L); + static int luaMonsterTypeGetChangeTargetChance(lua_State* L); + static int luaMonsterTypeGetChangeTargetSpeed(lua_State* L); + + // Party + static int luaPartyDisband(lua_State* L); + + static int luaPartyGetLeader(lua_State* L); + static int luaPartySetLeader(lua_State* L); + + static int luaPartyGetMembers(lua_State* L); + static int luaPartyGetMemberCount(lua_State* L); + + static int luaPartyGetInvitees(lua_State* L); + static int luaPartyGetInviteeCount(lua_State* L); + + static int luaPartyAddInvite(lua_State* L); + static int luaPartyRemoveInvite(lua_State* L); + + static int luaPartyAddMember(lua_State* L); + static int luaPartyRemoveMember(lua_State* L); + + static int luaPartyIsSharedExperienceActive(lua_State* L); + static int luaPartyIsSharedExperienceEnabled(lua_State* L); + static int luaPartyShareExperience(lua_State* L); + static int luaPartySetSharedExperience(lua_State* L); + + // + lua_State* luaState = nullptr; + std::string lastLuaError; + + std::string interfaceName; + int32_t eventTableRef = -1; + + static ScriptEnvironment scriptEnv[16]; + static int32_t scriptEnvIndex; + + int32_t runningEventId = EVENT_ID_USER; + std::string loadingFile; + + //script file cache + std::map cacheFiles; +}; + +class LuaEnvironment : public LuaScriptInterface +{ + public: + LuaEnvironment(); + ~LuaEnvironment(); + + // non-copyable + LuaEnvironment(const LuaEnvironment&) = delete; + LuaEnvironment& operator=(const LuaEnvironment&) = delete; + + bool initState(); + bool reInitState(); + bool closeState(); + + LuaScriptInterface* getTestInterface(); + + Combat* getCombatObject(uint32_t id) const; + Combat* createCombatObject(LuaScriptInterface* interface); + void clearCombatObjects(LuaScriptInterface* interface); + + AreaCombat* getAreaObject(uint32_t id) const; + uint32_t createAreaObject(LuaScriptInterface* interface); + void clearAreaObjects(LuaScriptInterface* interface); + + private: + void executeTimerEvent(uint32_t eventIndex); + + std::unordered_map timerEvents; + std::unordered_map combatMap; + std::unordered_map areaMap; + + std::unordered_map> combatIdMap; + std::unordered_map> areaIdMap; + + LuaScriptInterface* testInterface = nullptr; + + uint32_t lastEventTimerId = 1; + uint32_t lastCombatId = 0; + uint32_t lastAreaId = 0; + + friend class LuaScriptInterface; + friend class CombatSpell; +}; + +#endif diff --git a/app/SabrehavenServer/src/mailbox.cpp b/app/SabrehavenServer/src/mailbox.cpp new file mode 100644 index 0000000..31cd67f --- /dev/null +++ b/app/SabrehavenServer/src/mailbox.cpp @@ -0,0 +1,183 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "mailbox.h" +#include "game.h" +#include "player.h" +#include "iologindata.h" +#include "town.h" + +extern Game g_game; + +ReturnValue Mailbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t, Creature*) const +{ + const Item* item = thing.getItem(); + if (item && Mailbox::canSend(item)) { + return RETURNVALUE_NOERROR; + } + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Mailbox::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const +{ + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; +} + +ReturnValue Mailbox::queryRemove(const Thing&, uint32_t, uint32_t) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +Cylinder* Mailbox::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) +{ + return this; +} + +void Mailbox::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Mailbox::addThing(int32_t, Thing* thing) +{ + Item* item = thing->getItem(); + if (item && Mailbox::canSend(item)) { + sendItem(item); + } +} + +void Mailbox::updateThing(Thing*, uint16_t, uint32_t) +{ + // +} + +void Mailbox::replaceThing(uint32_t, Thing*) +{ + // +} + +void Mailbox::removeThing(Thing*, uint32_t) +{ + // +} + +void Mailbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); +} + +bool Mailbox::sendItem(Item* item) const +{ + std::string receiver; + std::string townName; + if (!getDestination(item, receiver, townName)) { + return false; + } + + if (receiver.empty() || townName.empty()) { + return false; + } + + Town* town = g_game.map.towns.getTown(townName); + if (!town) { + return false; + } + + Player* player = g_game.getPlayerByName(receiver); + if (player) { + DepotLocker* depotLocker = player->getDepotLocker(town->getID(), true); + if (depotLocker) { + if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER, + item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + player->onReceiveMail(town->getID()); + return true; + } + } + } else { + Player tmpPlayer(nullptr); + if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) { + return false; + } + + DepotLocker* depotLocker = tmpPlayer.getDepotLocker(town->getID(), true); + if (depotLocker) { + if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER, + item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + IOLoginData::savePlayer(&tmpPlayer); + return true; + } + } + } + + return false; +} + +bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) const +{ + const Container* container = item->getContainer(); + if (container) { + for (Item* containerItem : container->getItemList()) { + if (containerItem->getID() == ITEM_LABEL && getDestination(containerItem, name, town)) { + return true; + } + } + + return false; + } + + const std::string& text = item->getText(); + if (text.empty()) { + return false; + } + + std::istringstream iss(text, std::istringstream::in); + std::string temp; + uint32_t currentLine = 1; + + while (getline(iss, temp, '\n')) { + if (currentLine == 1) { + name = temp; + } else if (currentLine == 2) { + town = temp; + } else { + break; + } + + ++currentLine; + } + + trimString(name); + trimString(town); + return true; +} + +bool Mailbox::canSend(const Item* item) +{ + return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; +} diff --git a/app/SabrehavenServer/src/mailbox.h b/app/SabrehavenServer/src/mailbox.h new file mode 100644 index 0000000..084dc26 --- /dev/null +++ b/app/SabrehavenServer/src/mailbox.h @@ -0,0 +1,66 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB +#define FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB + +#include "item.h" +#include "cylinder.h" +#include "const.h" + +class Mailbox final : public Item, public Cylinder +{ + public: + explicit Mailbox(uint16_t itemId) : Item(itemId) {} + + Mailbox* getMailbox() final { + return this; + } + const Mailbox* getMailbox() const final { + return this; + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + private: + bool getDestination(Item* item, std::string& name, std::string& town) const; + bool sendItem(Item* item) const; + + static bool canSend(const Item* item); +}; + +#endif diff --git a/app/SabrehavenServer/src/map.cpp b/app/SabrehavenServer/src/map.cpp new file mode 100644 index 0000000..94bdeef --- /dev/null +++ b/app/SabrehavenServer/src/map.cpp @@ -0,0 +1,1028 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomap.h" +#include "iomapserialize.h" +#include "combat.h" +#include "creature.h" +#include "monster.h" +#include "game.h" + +extern Game g_game; + +bool Map::loadMap(const std::string& identifier, bool loadHouses) +{ + IOMap loader; + if (!loader.loadMap(this, identifier)) { + std::cout << "[Fatal - Map::loadMap] " << loader.getLastErrorString() << std::endl; + return false; + } + + Npcs::loadNpcs(); + if (!IOMap::loadSpawns(this)) { + std::cout << "[Warning - Map::loadMap] Failed to load spawn data." << std::endl; + } + + if (loadHouses) { + if (!IOMap::loadHouses(this)) { + std::cout << "[Warning - Map::loadMap] Failed to load house data." << std::endl; + } + + IOMapSerialize::loadHouseInfo(); + IOMapSerialize::loadHouseItems(this); + } + return true; +} + +bool Map::save() +{ + bool saved = false; + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize::saveHouseInfo()) { + saved = true; + break; + } + } + + if (!saved) { + return false; + } + + saved = false; + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize::saveHouseItems()) { + saved = true; + break; + } + } + return saved; +} + +Tile* Map::getTile(uint16_t x, uint16_t y, uint8_t z) const +{ + if (z >= MAP_MAX_LAYERS) { + return nullptr; + } + + const QTreeLeafNode* leaf = QTreeNode::getLeafStatic(&root, x, y); + if (!leaf) { + return nullptr; + } + + const Floor* floor = leaf->getFloor(z); + if (!floor) { + return nullptr; + } + return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK]; +} + +void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile) +{ + if (z >= MAP_MAX_LAYERS) { + std::cout << "ERROR: Attempt to set tile on invalid coordinate " << Position(x, y, z) << "!" << std::endl; + return; + } + + QTreeLeafNode::newLeaf = false; + QTreeLeafNode* leaf = root.createLeaf(x, y, 15); + + if (QTreeLeafNode::newLeaf) { + //update north + QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE); + if (northLeaf) { + northLeaf->leafS = leaf; + } + + //update west leaf + QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y); + if (westLeaf) { + westLeaf->leafE = leaf; + } + + //update south + QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE); + if (southLeaf) { + leaf->leafS = southLeaf; + } + + //update east + QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y); + if (eastLeaf) { + leaf->leafE = eastLeaf; + } + } + + Floor* floor = leaf->createFloor(z); + uint32_t offsetX = x & FLOOR_MASK; + uint32_t offsetY = y & FLOOR_MASK; + + Tile*& tile = floor->tiles[offsetX][offsetY]; + if (tile) { + TileItemVector* items = newTile->getItemList(); + if (items) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + tile->addThing(*it); + } + items->clear(); + } + + Item* ground = newTile->getGround(); + if (ground) { + tile->addThing(ground); + newTile->setGround(nullptr); + } + delete newTile; + } else { + tile = newTile; + } +} + +bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos/* = false*/, bool forceLogin/* = false*/) +{ + bool foundTile; + bool placeInPZ; + + Tile* tile = getTile(centerPos.x, centerPos.y, centerPos.z); + if (tile) { + placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE); + + ReturnValue ret; + if (creature->getPlayer()) { + ret = tile->queryAdd(0, *creature, 1, 0); + } else { + ret = tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : FLAG_IGNOREBLOCKITEM)); + } + + foundTile = forceLogin || ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_PLAYERISNOTINVITED; + } else { + placeInPZ = false; + foundTile = false; + } + + if (!foundTile) { + static std::vector> extendedRelList { + {0, -2}, + {-1, -1}, {0, -1}, {1, -1}, + {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, + {-1, 1}, {0, 1}, {1, 1}, + {0, 2} + }; + + static std::vector> normalRelList { + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }; + + std::vector>& relList = (extendedPos ? extendedRelList : normalRelList); + + if (extendedPos) { + std::shuffle(relList.begin(), relList.begin() + 4, getRandomGenerator()); + std::shuffle(relList.begin() + 4, relList.end(), getRandomGenerator()); + } else { + std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); + } + + for (const auto& it : relList) { + Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); + + tile = getTile(tryPos.x, tryPos.y, tryPos.z); + if (!tile || (placeInPZ && !tile->hasFlag(TILESTATE_PROTECTIONZONE))) { + continue; + } + + if (tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : 0)) == RETURNVALUE_NOERROR) { + if (!extendedPos || isSightClear(centerPos, tryPos, false)) { + foundTile = true; + break; + } + } + } + + if (!foundTile) { + return false; + } + } + + int32_t index = 0; + uint32_t flags = 0; + Item* toItem = nullptr; + + Cylinder* toCylinder = tile->queryDestination(index, *creature, &toItem, flags); + toCylinder->internalAddThing(creature); + + const Position& dest = toCylinder->getPosition(); + getQTNode(dest.x, dest.y)->addCreature(creature); + return true; +} + +void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = false*/) +{ + Tile& oldTile = *creature.getTile(); + + Position oldPos = oldTile.getPosition(); + Position newPos = newTile.getPosition(); + + bool teleport = forceTeleport || !newTile.getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); + + SpectatorVec list; + getSpectators(list, oldPos, true); + getSpectators(list, newPos, true); + + std::vector oldStackPosVector; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + if (tmpPlayer->canSeeCreature(&creature)) { + oldStackPosVector.push_back(oldTile.getClientIndexOfCreature(tmpPlayer, &creature)); + } else { + oldStackPosVector.push_back(-1); + } + } + } + + //remove the creature + oldTile.removeThing(&creature, 0); + + QTreeLeafNode* leaf = getQTNode(oldPos.x, oldPos.y); + QTreeLeafNode* new_leaf = getQTNode(newPos.x, newPos.y); + + // Switch the node ownership + if (leaf != new_leaf) { + leaf->removeCreature(&creature); + new_leaf->addCreature(&creature); + } + + //add the creature + newTile.addThing(&creature); + + if (!teleport) { + if (oldPos.y > newPos.y) { + creature.setDirection(DIRECTION_NORTH); + } else if (oldPos.y < newPos.y) { + creature.setDirection(DIRECTION_SOUTH); + } + + if (oldPos.x < newPos.x) { + creature.setDirection(DIRECTION_EAST); + } else if (oldPos.x > newPos.x) { + creature.setDirection(DIRECTION_WEST); + } + } + + //send to client + size_t i = 0; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + //Use the correct stackpos + int32_t stackpos = oldStackPosVector[i++]; + if (stackpos != -1) { + tmpPlayer->sendCreatureMove(&creature, newPos, newTile.getStackposOfCreature(tmpPlayer, &creature), oldPos, stackpos, teleport); + } + } + } + + //event method + for (Creature* spectator : list) { + spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport); + } + + oldTile.postRemoveNotification(&creature, &newTile, 0); + newTile.postAddNotification(&creature, &oldTile, 0); +} + +void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const +{ + int_fast16_t min_y = centerPos.y + minRangeY; + int_fast16_t min_x = centerPos.x + minRangeX; + int_fast16_t max_y = centerPos.y + maxRangeY; + int_fast16_t max_x = centerPos.x + maxRangeX; + + int32_t minoffset = centerPos.getZ() - maxRangeZ; + uint16_t x1 = std::min(0xFFFF, std::max(0, (min_x + minoffset))); + uint16_t y1 = std::min(0xFFFF, std::max(0, (min_y + minoffset))); + + int32_t maxoffset = centerPos.getZ() - minRangeZ; + uint16_t x2 = std::min(0xFFFF, std::max(0, (max_x + maxoffset))); + uint16_t y2 = std::min(0xFFFF, std::max(0, (max_y + maxoffset))); + + int32_t startx1 = x1 - (x1 % FLOOR_SIZE); + int32_t starty1 = y1 - (y1 % FLOOR_SIZE); + int32_t endx2 = x2 - (x2 % FLOOR_SIZE); + int32_t endy2 = y2 - (y2 % FLOOR_SIZE); + + const QTreeLeafNode* startLeaf = QTreeNode::getLeafStatic(&root, startx1, starty1); + const QTreeLeafNode* leafS = startLeaf; + const QTreeLeafNode* leafE; + + for (int_fast32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { + leafE = leafS; + for (int_fast32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { + if (leafE) { + const CreatureVector& node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list); + for (Creature* creature : node_list) { + const Position& cpos = creature->getPosition(); + if (minRangeZ > cpos.z || maxRangeZ < cpos.z) { + continue; + } + + int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos); + if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) { + continue; + } + + list.insert(creature); + } + leafE = leafE->leafE; + } else { + leafE = QTreeNode::getLeafStatic(&root, nx + FLOOR_SIZE, ny); + } + } + + if (leafS) { + leafS = leafS->leafS; + } else { + leafS = QTreeNode::getLeafStatic(&root, startx1, ny + FLOOR_SIZE); + } + } +} + +void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) +{ + if (centerPos.z >= MAP_MAX_LAYERS) { + return; + } + + bool foundCache = false; + bool cacheResult = false; + + minRangeX = (minRangeX == 0 ? -maxViewportX : -minRangeX); + maxRangeX = (maxRangeX == 0 ? maxViewportX : maxRangeX); + minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY); + maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY); + + if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) { + if (onlyPlayers) { + auto it = playersSpectatorCache.find(centerPos); + if (it != playersSpectatorCache.end()) { + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = it->second; + } + + foundCache = true; + } + } + + if (!foundCache) { + auto it = spectatorCache.find(centerPos); + if (it != spectatorCache.end()) { + if (!onlyPlayers) { + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = it->second; + } + } else { + const SpectatorVec& cachedList = it->second; + for (Creature* spectator : cachedList) { + if (spectator->getPlayer()) { + list.insert(spectator); + } + } + } + + foundCache = true; + } else { + cacheResult = true; + } + } + } + + if (!foundCache) { + int32_t minRangeZ; + int32_t maxRangeZ; + + if (multifloor) { + if (centerPos.z > 7) { + //underground + + //8->15 + minRangeZ = std::max(centerPos.getZ() - 2, 0); + maxRangeZ = std::min(centerPos.getZ() + 2, MAP_MAX_LAYERS - 1); + } else if (centerPos.z == 6) { + minRangeZ = 0; + maxRangeZ = 8; + } else if (centerPos.z == 7) { + minRangeZ = 0; + maxRangeZ = 9; + } else { + minRangeZ = 0; + maxRangeZ = 7; + } + } else { + minRangeZ = centerPos.z; + maxRangeZ = centerPos.z; + } + + getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + + if (cacheResult) { + if (onlyPlayers) { + playersSpectatorCache[centerPos] = list; + } else { + spectatorCache[centerPos] = list; + } + } + } +} + +void Map::clearSpectatorCache() +{ + spectatorCache.clear(); + playersSpectatorCache.clear(); +} + +bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const +{ + //z checks + //underground 8->15 + //ground level and above 7->0 + if ((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8)) { + return false; + } + + int32_t deltaz = Position::getDistanceZ(fromPos, toPos); + if (deltaz > 2) { + return false; + } + + if ((Position::getDistanceX(fromPos, toPos) - deltaz) > rangex) { + return false; + } + + //distance checks + if ((Position::getDistanceY(fromPos, toPos) - deltaz) > rangey) { + return false; + } + + if (!checkLineOfSight) { + return true; + } + return isSightClear(fromPos, toPos, false); +} + +bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const +{ + if (fromPos == toPos) { + return true; + } + + Position start(fromPos.z > toPos.z ? toPos : fromPos); + Position destination(fromPos.z > toPos.z ? fromPos : toPos); + + const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1; + const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1; + + int32_t A = Position::getOffsetY(destination, start); + int32_t B = Position::getOffsetX(start, destination); + int32_t C = -(A * destination.x + B * destination.y); + + while (start.x != destination.x || start.y != destination.y) { + int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C); + int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C); + int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C); + + if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) { + start.y += my; + } + + if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) { + start.x += mx; + } + + const Tile* tile = getTile(start.x, start.y, start.z); + if (tile && tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) { + return false; + } + } + + // now we need to perform a jump between floors to see if everything is clear (literally) + while (start.z != destination.z) { + const Tile* tile = getTile(start.x, start.y, start.z); + if (tile && tile->getThingCount() > 0) { + return false; + } + + start.z++; + } + + return true; +} + +bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +{ + if (floorCheck && fromPos.z != toPos.z) { + return false; + } + + // Cast two converging rays and see if either yields a result. + return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); +} + +const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const +{ + int32_t walkCache = creature.getWalkCache(pos); + if (walkCache == 0) { + return nullptr; + } else if (walkCache == 1) { + return getTile(pos.x, pos.y, pos.z); + } + + //used for non-cached tiles + Tile* tile = getTile(pos.x, pos.y, pos.z); + if (creature.getTile() != tile) { + if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING) != RETURNVALUE_NOERROR) { + return nullptr; + } + } + return tile; +} + +bool Map::getPathMatching(const Creature& creature, std::forward_list& dirList, const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const +{ + Position pos = creature.getPosition(); + Position endPos; + + AStarNodes nodes(pos.x, pos.y); + + int32_t bestMatch = 0; + + static int_fast32_t dirNeighbors[8][5][2] = { + {{-1, 0}, {0, 1}, {1, 0}, {1, 1}, {-1, 1}}, + {{-1, 0}, {0, 1}, {0, -1}, {-1, -1}, {-1, 1}}, + {{-1, 0}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}}, + {{0, 1}, {1, 0}, {0, -1}, {1, -1}, {1, 1}}, + {{1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}}, + {{-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}}, + {{0, 1}, {1, 0}, {1, -1}, {1, 1}, {-1, 1}}, + {{-1, 0}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}} + }; + static int_fast32_t allNeighbors[8][2] = { + {-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1} + }; + + const Position startPos = pos; + + AStarNode* found = nullptr; + while (fpp.maxSearchDist != 0 || nodes.getClosedNodes() < 100) { + AStarNode* n = nodes.getBestNode(); + if (!n) { + if (found) { + break; + } + return false; + } + + const int_fast32_t x = n->x; + const int_fast32_t y = n->y; + pos.x = x; + pos.y = y; + if (pathCondition(startPos, pos, fpp, bestMatch)) { + found = n; + endPos = pos; + if (bestMatch == 0) { + break; + } + } + + uint_fast32_t dirCount; + int_fast32_t* neighbors; + if (n->parent) { + const int_fast32_t offset_x = n->parent->x - x; + const int_fast32_t offset_y = n->parent->y - y; + if (offset_y == 0) { + if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_WEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_EAST]; + } + } else if (!fpp.allowDiagonal || offset_x == 0) { + if (offset_y == -1) { + neighbors = *dirNeighbors[DIRECTION_NORTH]; + } else { + neighbors = *dirNeighbors[DIRECTION_SOUTH]; + } + } else if (offset_y == -1) { + if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_NORTHWEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_NORTHEAST]; + } + } else if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_SOUTHWEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_SOUTHEAST]; + } + dirCount = fpp.allowDiagonal ? 5 : 3; + } else { + dirCount = 8; + neighbors = *allNeighbors; + } + + const int_fast32_t f = n->f; + for (uint_fast32_t i = 0; i < dirCount; ++i) { + pos.x = x + *neighbors++; + pos.y = y + *neighbors++; + + if (fpp.maxSearchDist != 0 && (Position::getDistanceX(startPos, pos) > fpp.maxSearchDist || Position::getDistanceY(startPos, pos) > fpp.maxSearchDist)) { + continue; + } + + if (fpp.keepDistance && !pathCondition.isInRange(startPos, pos, fpp)) { + continue; + } + + const Tile* tile; + AStarNode* neighborNode = nodes.getNodeByPosition(pos.x, pos.y); + if (neighborNode) { + tile = getTile(pos.x, pos.y, pos.z); + } else { + tile = canWalkTo(creature, pos); + if (!tile) { + continue; + } + } + + //The cost (g) for this neighbor + const int_fast32_t cost = AStarNodes::getMapWalkCost(n, pos); + const int_fast32_t extraCost = AStarNodes::getTileWalkCost(creature, tile); + const int_fast32_t newf = f + cost + extraCost; + + if (neighborNode) { + if (neighborNode->f <= newf) { + //The node on the closed/open list is cheaper than this one + continue; + } + + neighborNode->f = newf; + neighborNode->parent = n; + nodes.openNode(neighborNode); + } else { + //Does not exist in the open/closed list, create a new node + neighborNode = nodes.createOpenNode(n, pos.x, pos.y, newf); + if (!neighborNode) { + if (found) { + break; + } + return false; + } + } + } + + nodes.closeNode(n); + } + + if (!found) { + return false; + } + + int_fast32_t prevx = endPos.x; + int_fast32_t prevy = endPos.y; + + found = found->parent; + while (found) { + pos.x = found->x; + pos.y = found->y; + + int_fast32_t dx = pos.getX() - prevx; + int_fast32_t dy = pos.getY() - prevy; + + prevx = pos.x; + prevy = pos.y; + + if (dx == 1 && dy == 1) { + dirList.push_front(DIRECTION_NORTHWEST); + } else if (dx == -1 && dy == 1) { + dirList.push_front(DIRECTION_NORTHEAST); + } else if (dx == 1 && dy == -1) { + dirList.push_front(DIRECTION_SOUTHWEST); + } else if (dx == -1 && dy == -1) { + dirList.push_front(DIRECTION_SOUTHEAST); + } else if (dx == 1) { + dirList.push_front(DIRECTION_WEST); + } else if (dx == -1) { + dirList.push_front(DIRECTION_EAST); + } else if (dy == 1) { + dirList.push_front(DIRECTION_NORTH); + } else if (dy == -1) { + dirList.push_front(DIRECTION_SOUTH); + } + + found = found->parent; + } + return true; +} + +// AStarNodes + +AStarNodes::AStarNodes(uint32_t x, uint32_t y) + : nodes(), openNodes() +{ + curNode = 1; + closedNodes = 0; + openNodes[0] = true; + + AStarNode& startNode = nodes[0]; + startNode.parent = nullptr; + startNode.x = x; + startNode.y = y; + startNode.f = 0; + nodeTable[(x << 16) | y] = nodes; +} + +AStarNode* AStarNodes::createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f) +{ + if (curNode >= MAX_NODES) { + return nullptr; + } + + size_t retNode = curNode++; + openNodes[retNode] = true; + + AStarNode* node = nodes + retNode; + nodeTable[(x << 16) | y] = node; + node->parent = parent; + node->x = x; + node->y = y; + node->f = f; + return node; +} + +AStarNode* AStarNodes::getBestNode() +{ + if (curNode == 0) { + return nullptr; + } + + int32_t best_node_f = std::numeric_limits::max(); + int32_t best_node = -1; + for (size_t i = 0; i < curNode; i++) { + if (openNodes[i] && nodes[i].f < best_node_f) { + best_node_f = nodes[i].f; + best_node = i; + } + } + + if (best_node >= 0) { + return nodes + best_node; + } + return nullptr; +} + +void AStarNodes::closeNode(AStarNode* node) +{ + size_t index = node - nodes; + assert(index < MAX_NODES); + openNodes[index] = false; + ++closedNodes; +} + +void AStarNodes::openNode(AStarNode* node) +{ + size_t index = node - nodes; + assert(index < MAX_NODES); + if (!openNodes[index]) { + openNodes[index] = true; + --closedNodes; + } +} + +int_fast32_t AStarNodes::getClosedNodes() const +{ + return closedNodes; +} + +AStarNode* AStarNodes::getNodeByPosition(uint32_t x, uint32_t y) +{ + auto it = nodeTable.find((x << 16) | y); + if (it == nodeTable.end()) { + return nullptr; + } + return it->second; +} + +int_fast32_t AStarNodes::getMapWalkCost(AStarNode* node, const Position& neighborPos) +{ + if (std::abs(node->x - neighborPos.x) == std::abs(node->y - neighborPos.y)) { + //diagonal movement extra cost + return MAP_DIAGONALWALKCOST; + } + return MAP_NORMALWALKCOST; +} + +int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* tile) +{ + int_fast32_t cost = 0; + if (tile->getTopVisibleCreature(&creature) != nullptr) { + if (const Monster* monster = creature.getMonster()) { + if (monster->canPushCreatures()) { + return cost; + } + } + + //destroy creature cost + cost += MAP_NORMALWALKCOST * 3; + } + + if (const MagicField* field = tile->getFieldItem()) { + CombatType_t combatType = field->getCombatType(); + if (combatType != COMBAT_NONE) { + if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { + cost += MAP_NORMALWALKCOST * 18; + } + } + } + + return cost; +} + +// Floor +Floor::~Floor() +{ + for (auto& row : tiles) { + for (auto tile : row) { + delete tile; + } + } +} + +// QTreeNode +QTreeNode::~QTreeNode() +{ + for (auto* ptr : child) { + delete ptr; + } +} + +QTreeLeafNode* QTreeNode::getLeaf(uint32_t x, uint32_t y) +{ + if (leaf) { + return static_cast(this); + } + + QTreeNode* node = child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; + if (!node) { + return nullptr; + } + return node->getLeaf(x << 1, y << 1); +} + +QTreeLeafNode* QTreeNode::createLeaf(uint32_t x, uint32_t y, uint32_t level) +{ + if (!isLeaf()) { + uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); + if (!child[index]) { + if (level != FLOOR_BITS) { + child[index] = new QTreeNode(); + } else { + child[index] = new QTreeLeafNode(); + QTreeLeafNode::newLeaf = true; + } + } + return child[index]->createLeaf(x * 2, y * 2, level - 1); + } + return static_cast(this); +} + +// QTreeLeafNode +bool QTreeLeafNode::newLeaf = false; + +QTreeLeafNode::~QTreeLeafNode() +{ + for (auto* ptr : array) { + delete ptr; + } +} + +Floor* QTreeLeafNode::createFloor(uint32_t z) +{ + if (!array[z]) { + array[z] = new Floor(); + } + return array[z]; +} + +void QTreeLeafNode::addCreature(Creature* c) +{ + creature_list.push_back(c); + + if (c->getPlayer()) { + player_list.push_back(c); + } +} + +void QTreeLeafNode::removeCreature(Creature* c) +{ + auto iter = std::find(creature_list.begin(), creature_list.end(), c); + assert(iter != creature_list.end()); + *iter = creature_list.back(); + creature_list.pop_back(); + + if (c->getPlayer()) { + iter = std::find(player_list.begin(), player_list.end(), c); + assert(iter != player_list.end()); + *iter = player_list.back(); + player_list.pop_back(); + } +} + +uint32_t Map::clean() const +{ + uint64_t start = OTSYS_TIME(); + size_t count = 0, tiles = 0; + + if (g_game.getGameState() == GAME_STATE_NORMAL) { + g_game.setGameState(GAME_STATE_MAINTAIN); + } + + std::vector nodes { + &root + }; + std::vector toRemove; + do { + const QTreeNode* node = nodes.back(); + nodes.pop_back(); + if (node->isLeaf()) { + const QTreeLeafNode* leafNode = static_cast(node); + for (uint8_t z = 0; z < MAP_MAX_LAYERS; ++z) { + Floor* floor = leafNode->getFloor(z); + if (!floor) { + continue; + } + + for (auto& row : floor->tiles) { + for (auto tile : row) { + if (!tile || tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + continue; + } + + TileItemVector* itemList = tile->getItemList(); + if (!itemList) { + continue; + } + + ++tiles; + for (Item* item : *itemList) { + if (item->isCleanable()) { + toRemove.push_back(item); + } + } + + for (Item* item : toRemove) { + g_game.internalRemoveItem(item, -1); + } + count += toRemove.size(); + toRemove.clear(); + } + } + } + } else { + for (auto childNode : node->child) { + if (childNode) { + nodes.push_back(childNode); + } + } + } + } while (!nodes.empty()); + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + g_game.setGameState(GAME_STATE_NORMAL); + } + + std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") + << " from " << tiles << " tile" << (tiles != 1 ? "s" : "") << " in " + << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return count; +} diff --git a/app/SabrehavenServer/src/map.h b/app/SabrehavenServer/src/map.h new file mode 100644 index 0000000..3e8df8d --- /dev/null +++ b/app/SabrehavenServer/src/map.h @@ -0,0 +1,287 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MAP_H_E3953D57C058461F856F5221D359DAFA +#define FS_MAP_H_E3953D57C058461F856F5221D359DAFA + +#include "position.h" +#include "item.h" +#include "fileloader.h" + +#include "tools.h" +#include "tile.h" +#include "town.h" +#include "house.h" +#include "spawn.h" + +class Creature; +class Player; +class Game; +class Tile; +class Map; + +static constexpr int32_t MAP_MAX_LAYERS = 16; + +struct FindPathParams; +struct AStarNode { + AStarNode* parent; + int_fast32_t f; + uint16_t x, y; +}; + +static constexpr int32_t MAX_NODES = 512; + +static constexpr int32_t MAP_NORMALWALKCOST = 10; +static constexpr int32_t MAP_DIAGONALWALKCOST = 25; + +class AStarNodes +{ + public: + AStarNodes(uint32_t x, uint32_t y); + + AStarNode* createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f); + AStarNode* getBestNode(); + void closeNode(AStarNode* node); + void openNode(AStarNode* node); + int_fast32_t getClosedNodes() const; + AStarNode* getNodeByPosition(uint32_t x, uint32_t y); + + static int_fast32_t getMapWalkCost(AStarNode* node, const Position& neighborPos); + static int_fast32_t getTileWalkCost(const Creature& creature, const Tile* tile); + + private: + AStarNode nodes[MAX_NODES]; + bool openNodes[MAX_NODES]; + std::unordered_map nodeTable; + size_t curNode; + int_fast32_t closedNodes; +}; + +typedef std::map SpectatorCache; + +static constexpr int32_t FLOOR_BITS = 3; +static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS); +static constexpr int32_t FLOOR_MASK = (FLOOR_SIZE - 1); + +struct Floor { + constexpr Floor() = default; + ~Floor(); + + // non-copyable + Floor(const Floor&) = delete; + Floor& operator=(const Floor&) = delete; + + Tile* tiles[FLOOR_SIZE][FLOOR_SIZE] = {}; +}; + +class FrozenPathingConditionCall; +class QTreeLeafNode; + +class QTreeNode +{ + public: + constexpr QTreeNode() = default; + virtual ~QTreeNode(); + + // non-copyable + QTreeNode(const QTreeNode&) = delete; + QTreeNode& operator=(const QTreeNode&) = delete; + + bool isLeaf() const { + return leaf; + } + + QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); + + template + inline static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) + { + do { + node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; + if (!node) { + return nullptr; + } + + x <<= 1; + y <<= 1; + } while (!node->leaf); + return static_cast(node); + } + + QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); + + protected: + QTreeNode* child[4] = {}; + + bool leaf = false; + + friend class Map; +}; + +class QTreeLeafNode final : public QTreeNode +{ + public: + QTreeLeafNode() { leaf = true; newLeaf = true; } + ~QTreeLeafNode(); + + // non-copyable + QTreeLeafNode(const QTreeLeafNode&) = delete; + QTreeLeafNode& operator=(const QTreeLeafNode&) = delete; + + Floor* createFloor(uint32_t z); + Floor* getFloor(uint8_t z) const { + return array[z]; + } + + void addCreature(Creature* c); + void removeCreature(Creature* c); + + protected: + static bool newLeaf; + QTreeLeafNode* leafS = nullptr; + QTreeLeafNode* leafE = nullptr; + Floor* array[MAP_MAX_LAYERS] = {}; + CreatureVector creature_list; + CreatureVector player_list; + + friend class Map; + friend class QTreeNode; +}; + +/** + * Map class. + * Holds all the actual map-data + */ + +class Map +{ + public: + static constexpr int32_t maxViewportX = 11; //min value: maxClientViewportX + 1 + static constexpr int32_t maxViewportY = 11; //min value: maxClientViewportY + 1 + static constexpr int32_t maxClientViewportX = 8; + static constexpr int32_t maxClientViewportY = 6; + + uint32_t clean() const; + + /** + * Load a map. + * \returns true if the map was loaded successfully + */ + bool loadMap(const std::string& identifier, bool loadHouses); + + /** + * Save a map. + * \returns true if the map was saved successfully + */ + static bool save(); + + /** + * Get a single tile. + * \returns A pointer to that tile. + */ + Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const; + inline Tile* getTile(const Position& pos) const { + return getTile(pos.x, pos.y, pos.z); + } + + /** + * Set a single tile. + */ + void setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile); + void setTile(const Position& pos, Tile* newTile) { + setTile(pos.x, pos.y, pos.z, newTile); + } + + /** + * Place a creature on the map + * \param centerPos The position to place the creature + * \param creature Creature to place on the map + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forceLogin If true, placing the creature will not fail becase of obstacles (creatures/chests) + */ + bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, bool forceLogin = false); + + void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); + + void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, + int32_t minRangeX = 0, int32_t maxRangeX = 0, + int32_t minRangeY = 0, int32_t maxRangeY = 0); + + void clearSpectatorCache(); + + /** + * Checks if you can throw an object to that position + * \param fromPos from Source point + * \param toPos Destination point + * \param rangex maximum allowed range horizontially + * \param rangey maximum allowed range vertically + * \param checkLineOfSight checks if there is any blocking objects in the way + * \returns The result if you can throw there or not + */ + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; + + /** + * Checks if path is clear from fromPos to toPos + * Notice: This only checks a straight line if the path is clear, for path finding use getPathTo. + * \param fromPos from Source point + * \param toPos Destination point + * \param floorCheck if true then view is not clear if fromPos.z is not the same as toPos.z + * \returns The result if there is no obstacles + */ + bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; + bool checkSightLine(const Position& fromPos, const Position& toPos) const; + + const Tile* canWalkTo(const Creature& creature, const Position& pos) const; + + bool getPathMatching(const Creature& creature, std::forward_list& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const; + + std::map waypoints; + + QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) { + return QTreeNode::getLeafStatic(&root, x, y); + } + + Spawns spawns; + Towns towns; + Houses houses; + protected: + SpectatorCache spectatorCache; + SpectatorCache playersSpectatorCache; + + QTreeNode root; + + std::string spawnfile; + std::string housefile; + + uint32_t width = 0; + uint32_t height = 0; + + // Actually scans the map for spectators + void getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, + int32_t minRangeX, int32_t maxRangeX, + int32_t minRangeY, int32_t maxRangeY, + int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const; + + friend class Game; + friend class IOMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/monster.cpp b/app/SabrehavenServer/src/monster.cpp new file mode 100644 index 0000000..4f007cc --- /dev/null +++ b/app/SabrehavenServer/src/monster.cpp @@ -0,0 +1,2241 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "monster.h" +#include "game.h" +#include "spells.h" +#include "configmanager.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Monsters g_monsters; + +int32_t Monster::despawnRange; +int32_t Monster::despawnRadius; + +uint32_t Monster::monsterAutoID = 0x40000000; + +Monster* Monster::createMonster(const std::string& name) +{ + MonsterType* mType = g_monsters.getMonsterType(name); + if (!mType) { + return nullptr; + } + return new Monster(mType); +} + +Monster::Monster(MonsterType* mtype) : + Creature(), + strDescription(asLowerCaseString(mtype->nameDescription)), + mType(mtype) +{ + defaultOutfit = mType->info.outfit; + currentOutfit = mType->info.outfit; + skull = mType->info.skull; + health = mType->info.health; + healthMax = mType->info.healthMax; + baseSpeed = mType->info.baseSpeed; + internalLight = mType->info.light; + hiddenHealth = mType->info.hiddenHealth; + + // register creature events + for (const std::string& scriptName : mType->info.scripts) { + if (!registerCreatureEvent(scriptName)) { + std::cout << "[Warning - Monster::Monster] Unknown event name: " << scriptName << std::endl; + } + } +} + +Monster::~Monster() +{ + clearTargetList(); + clearFriendList(); +} + +void Monster::addList() +{ + g_game.addMonster(this); +} + +void Monster::removeList() +{ + g_game.removeMonster(this); +} + +int32_t Monster::getDefense() +{ + int32_t totalDefense = mType->info.defense + 1; + int32_t defenseSkill = mType->info.skill; + + fightMode_t attackMode = FIGHTMODE_BALANCED; + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; +} + +bool Monster::canSee(const Position& pos) const +{ + return Creature::canSee(getPosition(), pos, 9, 9); +} + +void Monster::onAttackedCreature(Creature* creature) +{ + if (isSummon() && getMaster()) { + master->onAttackedCreature(creature); + } +} + +void Monster::onAttackedCreatureDisappear(bool) +{ + extraMeleeAttack = true; +} + +void Monster::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (mType->info.creatureAppearEvent != -1) { + // onCreatureAppear(self, creature) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureAppear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureAppearEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureAppearEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (creature == this) { + //We just spawned lets look around to see who is there. + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + onCreatureEnter(creature); + } +} + +void Monster::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (mType->info.creatureDisappearEvent != -1) { + // onCreatureDisappear(self, creature) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureDisappear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureDisappearEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureDisappearEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (creature == this) { + if (spawn) { + spawn->startSpawnCheck(); + } + + setIdle(true); + } else { + onCreatureLeave(creature); + } +} + +void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (mType->info.creatureMoveEvent != -1) { + // onCreatureMove(self, creature, oldPosition, newPosition) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureMove] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureMoveEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureMoveEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushPosition(L, oldPos); + LuaScriptInterface::pushPosition(L, newPos); + + if (scriptInterface->callFunction(4)) { + return; + } + } + + if (creature == this) { + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + bool canSeeNewPos = canSee(newPos); + bool canSeeOldPos = canSee(oldPos); + + if (canSeeNewPos && !canSeeOldPos) { + onCreatureEnter(creature); + } else if (!canSeeNewPos && canSeeOldPos) { + onCreatureLeave(creature); + } + + if (canSeeNewPos && isSummon() && getMaster() == creature) { + isMasterInRange = true; //Follow master again + } + + updateIdleStatus(); + + if (!isSummon()) { + if (followCreature) { + const Position& followPosition = followCreature->getPosition(); + const Position& position = getPosition(); + + int32_t offset_x = Position::getDistanceX(followPosition, position); + int32_t offset_y = Position::getDistanceY(followPosition, position); + if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0 && targetChangeCooldown <= 0) { + Direction dir = getDirectionTo(position, followPosition); + const Position& checkPosition = getNextPosition(dir, position); + + Tile* tile = g_game.map.getTile(checkPosition); + if (tile) { + Creature* topCreature = tile->getTopCreature(); + if (topCreature && followCreature != topCreature && isOpponent(topCreature)) { + selectTarget(topCreature); + } + } + } + } else if (isOpponent(creature)) { + //we have no target lets try pick this one + selectTarget(creature); + } + } + } +} + +void Monster::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) +{ + Creature::onCreatureSay(creature, type, text); + + if (mType->info.creatureSayEvent != -1) { + // onCreatureSay(self, creature, type, message) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureSay] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureSayEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureSayEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + lua_pushnumber(L, type); + LuaScriptInterface::pushString(L, text); + + scriptInterface->callVoidFunction(4); + } +} + +void Monster::addFriend(Creature* creature) +{ + assert(creature != this); + auto result = friendList.insert(creature); + if (result.second) { + creature->incrementReferenceCounter(); + } +} + +void Monster::removeFriend(Creature* creature) +{ + auto it = friendList.find(creature); + if (it != friendList.end()) { + creature->decrementReferenceCounter(); + friendList.erase(it); + } +} + +void Monster::addTarget(Creature* creature, bool pushFront/* = false*/) +{ + assert(creature != this); + if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { + creature->incrementReferenceCounter(); + if (pushFront) { + targetList.push_front(creature); + } else { + targetList.push_back(creature); + } + } +} + +void Monster::removeTarget(Creature* creature) +{ + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it != targetList.end()) { + creature->decrementReferenceCounter(); + targetList.erase(it); + } +} + +void Monster::updateTargetList() +{ + auto friendIterator = friendList.begin(); + while (friendIterator != friendList.end()) { + Creature* creature = *friendIterator; + if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + creature->decrementReferenceCounter(); + friendIterator = friendList.erase(friendIterator); + } else { + ++friendIterator; + } + } + + auto targetIterator = targetList.begin(); + while (targetIterator != targetList.end()) { + Creature* creature = *targetIterator; + if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + creature->decrementReferenceCounter(); + targetIterator = targetList.erase(targetIterator); + } else { + ++targetIterator; + } + } + + SpectatorVec list; + g_game.map.getSpectators(list, position, true); + list.erase(this); + for (Creature* spectator : list) { + if (canSee(spectator->getPosition())) { + onCreatureFound(spectator); + } + } +} + +void Monster::clearTargetList() +{ + for (Creature* creature : targetList) { + creature->decrementReferenceCounter(); + } + targetList.clear(); +} + +void Monster::clearFriendList() +{ + for (Creature* creature : friendList) { + creature->decrementReferenceCounter(); + } + friendList.clear(); +} + +void Monster::onCreatureFound(Creature* creature, bool pushFront/* = false*/) +{ + if (isFriend(creature)) { + addFriend(creature); + } + + if (isOpponent(creature)) { + addTarget(creature, pushFront); + } + + updateIdleStatus(); +} + +void Monster::onCreatureEnter(Creature* creature) +{ + // std::cout << "onCreatureEnter - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Follow master again + isMasterInRange = true; + } + + onCreatureFound(creature, true); +} + +bool Monster::isFriend(const Creature* creature) const +{ + if (isSummon() && getMaster()->getPlayer()) { + const Player* masterPlayer = getMaster()->getPlayer(); + const Player* tmpPlayer = nullptr; + + if (creature->getPlayer()) { + tmpPlayer = creature->getPlayer(); + } else { + const Creature* creatureMaster = creature->getMaster(); + + if (creatureMaster && creatureMaster->getPlayer()) { + tmpPlayer = creatureMaster->getPlayer(); + } + } + + if (tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) { + return true; + } + } else if (creature->getMonster() && !creature->isSummon()) { + return true; + } + + return false; +} + +bool Monster::isOpponent(const Creature* creature) const +{ + if (isSummon() && getMaster()->getPlayer()) { + if (creature != getMaster()) { + return true; + } + } else { + if ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || + (creature->getMaster() && creature->getMaster()->getPlayer())) { + return true; + } + } + + return false; +} + +void Monster::onCreatureLeave(Creature* creature) +{ + // std::cout << "onCreatureLeave - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Take random steps and only use defense abilities (e.g. heal) until its master comes back + isMasterInRange = false; + } + + //update friendList + if (isFriend(creature)) { + removeFriend(creature); + } + + //update targetList + if (isOpponent(creature)) { + removeTarget(creature); + if (targetList.empty()) { + updateIdleStatus(); + } + } +} + +bool Monster::searchTarget(TargetSearchType_t searchType) +{ + std::list resultList; + const Position& myPos = getPosition(); + + for (Creature* creature : targetList) { + if (followCreature != creature && isTarget(creature)) { + if (searchType == TARGETSEARCH_ANY || canUseAttack(myPos, creature)) { + resultList.push_back(creature); + } + } + } + + switch (searchType) { + case TARGETSEARCH_NEAREST: { + Creature* target = nullptr; + + int32_t minRange = 0; + if (attackedCreature) { + const Position& targetPosition = attackedCreature->getPosition(); + minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + } + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + const Position& targetPosition = creature->getPosition(); + + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + if (distance < minRange) { + target = creature; + minRange = distance; + } + } + } else { + for (Creature* creature : targetList) { + if (!isTarget(creature)) { + continue; + } + + const Position& targetPosition = creature->getPosition(); + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + if (distance < minRange) { + target = creature; + minRange = distance; + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + case TARGETSEARCH_WEAKEST: { + Creature* target = nullptr; + + int32_t health = 0; + if (attackedCreature) { + health = attackedCreature->getMaxHealth(); + } + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } else { + for (Creature* creature : targetList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + case TARGETSEARCH_MOSTDAMAGE: { + Creature* target = nullptr; + + int32_t maxDamage = 0; + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } else { + for (Creature* creature : targetList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + default: { + if (!resultList.empty()) { + auto it = resultList.begin(); + std::advance(it, uniform_random(0, resultList.size() - 1)); + return selectTarget(*it); + } + + break; + } + } + + //lets just pick the first target in the list if we do not have a target + if (!attackedCreature) { + for (Creature* target : targetList) { + if (followCreature != target && selectTarget(target)) { + return true; + } + } + } + + return false; +} + +void Monster::onFollowCreatureComplete(const Creature* creature) +{ + if (creature) { + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it != targetList.end()) { + Creature* target = (*it); + targetList.erase(it); + + if (hasFollowPath) { + targetList.push_front(target); + } else if (!isSummon()) { + targetList.push_back(target); + } else { + target->decrementReferenceCounter(); + } + } + } +} + +BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); + + if (damage != 0) { + int32_t elementMod = 0; + auto it = mType->info.elementMap.find(combatType); + if (it != mType->info.elementMap.end()) { + elementMod = it->second; + } + + if (elementMod != 0) { + damage = static_cast(std::round(damage * ((100 - elementMod) / 100.))); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + } + + return blockType; +} + + +bool Monster::isTarget(const Creature* creature) const +{ + if (creature->isRemoved() || !creature->isAttackable() || + creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) { + return false; + } + + if (creature->getPosition().z != getPosition().z) { + return false; + } + return true; +} + +bool Monster::selectTarget(Creature* creature) +{ + if (!isTarget(creature)) { + return false; + } + + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it == targetList.end()) { + //Target not found in our target list. + return false; + } + + if (isHostile() || isSummon()) { + if (setAttackedCreature(creature) && !isSummon()) { + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + } + + // without this task, monster would randomly start dancing until next game round + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + targetChangeCooldown += 3000; + return setFollowCreature(creature); +} + +void Monster::setIdle(bool idle) +{ + if (isRemoved() || getHealth() <= 0) { + return; + } + + isIdle = idle; + + if (!isIdle) { + g_game.addCreatureCheck(this); + } else { + onIdleStatus(); + clearTargetList(); + clearFriendList(); + Game::removeCreatureCheck(this); + } +} + +void Monster::updateIdleStatus() +{ + bool idle = false; + + if (conditions.empty()) { + if (!isSummon() && targetList.empty()) { + idle = true; + } + } + + setIdle(idle); +} + +void Monster::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onEndCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onThink(uint32_t interval) +{ + if (OTSYS_TIME() < earliestWakeUpTime) { + return; + } + + Creature::onThink(interval); + + if (mType->info.thinkEvent != -1) { + // onThink(self, interval) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onThink] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.thinkEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.thinkEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + lua_pushnumber(L, interval); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (!isInSpawnRange(position) || (lifetime > 0 && (OTSYS_TIME() >= lifetime))) { + // Despawn creatures if they are out of their spawn zone + g_game.removeCreature(this); + g_game.addMagicEffect(getPosition(), CONST_ME_POFF); + } else { + updateIdleStatus(); + + if (!isIdle) { + addEventWalk(); + + if (isSummon()) { + if (!attackedCreature) { + if (getMaster() && getMaster()->getAttackedCreature()) { + selectTarget(getMaster()->getAttackedCreature()); + } else if (getMaster() != followCreature) { + //Our master has not ordered us to attack anything, lets follow him around instead. + setFollowCreature(getMaster()); + } + } else if (attackedCreature == this) { + setFollowCreature(nullptr); + } else if (followCreature != attackedCreature) { + setFollowCreature(attackedCreature); + } + + if (master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1 && !monster->hasFollowPath) { + setFollowCreature(master); + setAttackedCreature(nullptr); + } + } + } + } else if (!targetList.empty()) { + if (!followCreature || !hasFollowPath) { + searchTarget(TARGETSEARCH_ANY); + } else if (isFleeing()) { + if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { + searchTarget(TARGETSEARCH_NEAREST); + } + } + } + + onThinkTarget(interval); + onThinkYell(interval); + onThinkDefense(interval); + } + } +} + +void Monster::doExtraMeleeAttack() +{ + if (!attackedCreature || (isSummon() && attackedCreature == this)) { + return; + } + + bool updateLook = false; + bool isCloseAttack = false; + + if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { + updateLook = true; + isCloseAttack = Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED); + if (isCloseAttack) { + egibleToDance = true; + earliestAttackTime = OTSYS_TIME() + 2000; + removeCondition(CONDITION_AGGRESSIVE, true); + extraMeleeAttack = false; + } + + if (!isCloseAttack) { + //melee swing out of reach + extraMeleeAttack = true; + } + } + + if (updateLook) { + updateLookDirection(); + } +} + +void Monster::doAttacking(uint32_t) +{ + if (!attackedCreature || (isSummon() && attackedCreature == this)) { + return; + } + + const Position& myPos = getPosition(); + const Position& targetPos = attackedCreature->getPosition(); + + bool updateLook = false; + bool isCloseAttack = false; + + if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { + updateLook = true; + isCloseAttack = Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED); + if (isCloseAttack) { + egibleToDance = true; + earliestAttackTime = OTSYS_TIME() + 2000; + removeCondition(CONDITION_AGGRESSIVE, true); + extraMeleeAttack = false; + } + + if (!isCloseAttack) { + //melee swing out of reach + extraMeleeAttack = true; + } + } + + for (spellBlock_t& spellBlock : mType->info.attackSpells) { + if (spellBlock.range != 0 && std::max(Position::getDistanceX(myPos, targetPos), Position::getDistanceY(myPos, targetPos)) <= spellBlock.range) { + if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { + updateLookDirection(); + + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; + + spellBlock.spell->castSpell(this, attackedCreature); + egibleToDance = true; + } + } + } + + if (updateLook) { + updateLookDirection(); + } +} + +bool Monster::canUseAttack(const Position& pos, const Creature* target) const +{ + if (isHostile()) { + const Position& targetPos = target->getPosition(); + uint32_t distance = std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)); + for (const spellBlock_t& spellBlock : mType->info.attackSpells) { + if (spellBlock.range != 0 && distance <= spellBlock.range) { + return g_game.isSightClear(pos, targetPos, true); + } + } + return false; + } + return true; +} + +void Monster::onThinkTarget(uint32_t interval) +{ + if (!isSummon()) { + if (mType->info.changeTargetSpeed != 0) { + bool canChangeTarget = true; + + if (targetChangeCooldown > 0) { + targetChangeCooldown -= interval; + + if (targetChangeCooldown <= 0) { + targetChangeCooldown = 0; + targetChangeTicks = mType->info.changeTargetSpeed; + } else { + canChangeTarget = false; + } + } + + if (canChangeTarget) { + targetChangeTicks += interval; + + if (targetChangeTicks >= mType->info.changeTargetSpeed) { + targetChangeTicks = 0; + + if (mType->info.changeTargetChance > uniform_random(0, 99)) { + // search target strategies, if no strategy succeeds, target is not switched + int32_t random = uniform_random(0, 99); + int32_t current_strategy = 0; + + TargetSearchType_t searchType = TARGETSEARCH_ANY; + + do + { + int32_t strategy = 0; + + if (current_strategy == 0) { + strategy = mType->info.strategyNearestEnemy; + searchType = TARGETSEARCH_NEAREST; + } else if (current_strategy == 1) { + strategy = mType->info.strategyWeakestEnemy; + searchType = TARGETSEARCH_WEAKEST; + } else if (current_strategy == 2) { + strategy = mType->info.strategyMostDamageEnemy; + searchType = TARGETSEARCH_MOSTDAMAGE; + } else if (current_strategy == 3) { + strategy = mType->info.strategyRandomEnemy; + searchType = TARGETSEARCH_RANDOM; + } + + if (random < strategy) { + break; + } + + current_strategy++; + random -= strategy; + } while (current_strategy <= 3); + + if (searchType != TARGETSEARCH_ANY) { + searchTarget(searchType); + } + } + } + } + } + } +} + +void Monster::onThinkDefense(uint32_t) +{ + for (const spellBlock_t& spellBlock : mType->info.defenseSpells) { + if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; + spellBlock.spell->castSpell(this, this); + } + } + + if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) { + for (const summonBlock_t& summonBlock : mType->info.summons) { + if (summons.size() >= mType->info.maxSummons) { + continue; + } + + uint32_t summonCount = 0; + for (Creature* summon : summons) { + if (summon->getName() == summonBlock.name) { + ++summonCount; + } + } + + if (summonCount >= summonBlock.max) { + continue; + } + + if (normal_random(0, summonBlock.chance) == 0 && (health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { + Monster* summon = Monster::createMonster(summonBlock.name); + if (summon) { + const Position& summonPos = getPosition(); + + addSummon(summon); + + if (!g_game.placeCreature(summon, summonPos, false, summonBlock.force)) { + removeSummon(summon); + } else { + g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); + } + } + } + } + } +} + +void Monster::onThinkYell(uint32_t) +{ + if (mType->info.voiceVector.empty()) { + return; + } + + int32_t randomResult = rand(); + if (rand() == 50 * (randomResult / 50)) { + uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1); + const voiceBlock_t& voice = mType->info.voiceVector[index]; + + if (voice.yellText) { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, voice.text, false); + } + else { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, voice.text, false); + } + } +} + +void Monster::onWalk() +{ + Creature::onWalk(); +} + +bool Monster::pushItem(Item* item) +{ + const Position& centerPos = item->getPosition(); + + static std::vector> relList { + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }; + + std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); + + for (const auto& it : relList) { + Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); + Tile* tile = g_game.map.getTile(tryPos); + if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) { + if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr) == RETURNVALUE_NOERROR) { + return true; + } + } + } + return false; +} + +void Monster::pushItems(Tile* tile) +{ + //We can not use iterators here since we can push the item to another tile + //which will invalidate the iterator. + //start from the end to minimize the amount of traffic + if (TileItemVector* items = tile->getItemList()) { + uint32_t moveCount = 0; + uint32_t removeCount = 0; + + int32_t downItemSize = tile->getDownItemCount(); + for (int32_t i = downItemSize; --i >= 0;) { + Item* item = items->at(i); + if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) + || item->hasProperty(CONST_PROP_BLOCKSOLID))) { + if (moveCount < 20 && Monster::pushItem(item)) { + ++moveCount; + } else if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { + ++removeCount; + } + } + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF); + } + } +} + +bool Monster::pushCreature(Creature* creature) +{ + static std::vector dirList { + DIRECTION_NORTH, + DIRECTION_WEST, DIRECTION_EAST, + DIRECTION_SOUTH + }; + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + + for (Direction dir : dirList) { + const Position& tryPos = Spells::getCasterPosition(creature, dir); + Tile* toTile = g_game.map.getTile(tryPos); + if (toTile && !toTile->hasFlag(TILESTATE_BLOCKPATH)) { + if (g_game.internalMoveCreature(creature, dir) == RETURNVALUE_NOERROR) { + return true; + } + } + } + return false; +} + +void Monster::pushCreatures(Tile* tile) +{ + //We can not use iterators here since we can push a creature to another tile + //which will invalidate the iterator. + if (CreatureVector* creatures = tile->getCreatures()) { + uint32_t removeCount = 0; + Monster* lastPushedMonster = nullptr; + + for (size_t i = 0; i < creatures->size();) { + Monster* monster = creatures->at(i)->getMonster(); + if (monster && monster->isPushable()) { + if (monster != lastPushedMonster && Monster::pushCreature(monster)) { + lastPushedMonster = monster; + continue; + } + + monster->changeHealth(-monster->getHealth()); + monster->setDropLoot(false); + removeCount++; + } + + ++i; + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), CONST_ME_BLOCKHIT); + } + } +} + +bool Monster::getNextStep(Direction& direction, uint32_t& flags) +{ + if (isIdle || getHealth() <= 0) { + //we dont have anyone watching might aswell stop walking + eventWalk = 0; + return false; + } + + bool result = false; + if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { + if (OTSYS_TIME() >= nextDanceStepRound) { + updateLookDirection(); + nextDanceStepRound = OTSYS_TIME() + getStepDuration(); + + //choose a random direction + result = getRandomStep(getPosition(), direction); + } + } else if ((isSummon() && isMasterInRange) || followCreature) { + result = Creature::getNextStep(direction, flags); + if (result) { + flags |= FLAG_PATHFINDING; + } else { + //target dancing + if (attackedCreature && attackedCreature == followCreature) { + if (isFleeing()) { + result = getDanceStep(getPosition(), direction, false, false); + } else if (egibleToDance && OTSYS_TIME() >= earliestDanceTime) { + if (mType->info.targetDistance >= 4) { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::getDistanceX(myPos, targetPos) == 4 || Position::getDistanceY(myPos, targetPos) == 4) { + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + if (danceRandomResult <= 3 && canWalkTo(myPos, direction)) { + int32_t xTest = targetPos.x - currentX; + if (currentX - targetPos.x > -1) { + xTest = currentX - targetPos.x; + } + + int32_t yTest = targetPos.y - currentY; + if (currentY - targetPos.y > -1) { + yTest = currentY - targetPos.y; + } + + int32_t realTest = yTest; + + if (xTest >= yTest) { + realTest = xTest; + } + + if (realTest == 4) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } + } else { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::areInRange<1, 1>(myPos, targetPos)) { + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + Position position = myPos; + position.x = currentX; + position.y = currentY; + + if (danceRandomResult <= 3 && + canWalkTo(myPos, direction) && + Position::areInRange<1, 1>(position, targetPos)) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } + } + } + } + } + + if (result && (canPushItems() || canPushCreatures())) { + const Position& pos = Spells::getCasterPosition(this, direction); + Tile* tile = g_game.map.getTile(pos); + if (tile) { + if (canPushItems()) { + Monster::pushItems(tile); + } + + if (canPushCreatures()) { + Monster::pushCreatures(tile); + } + } + } + + return result; +} + +bool Monster::getRandomStep(const Position& creaturePos, Direction& direction) const +{ + static std::vector dirList{ + DIRECTION_NORTH, + DIRECTION_WEST, DIRECTION_EAST, + DIRECTION_SOUTH + }; + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + + for (Direction dir : dirList) { + if (canWalkTo(creaturePos, dir)) { + direction = dir; + return true; + } + } + return false; +} + +bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, + bool keepAttack /*= true*/, bool keepDistance /*= true*/) +{ + bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); + + assert(attackedCreature != nullptr); + const Position& centerPos = attackedCreature->getPosition(); + + int_fast32_t offset_x = Position::getOffsetX(creaturePos, centerPos); + int_fast32_t offset_y = Position::getOffsetY(creaturePos, centerPos); + + int_fast32_t distance_x = std::abs(offset_x); + int_fast32_t distance_y = std::abs(offset_y); + + uint32_t centerToDist = std::max(distance_x, distance_y); + + std::vector dirList; + + if (!keepDistance || offset_y >= 0) { + uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY())); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_NORTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_NORTH); + } + } + } + + if (!keepDistance || offset_y <= 0) { + uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() + 1) - centerPos.getY())); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_SOUTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_SOUTH); + } + } + } + + if (!keepDistance || offset_x <= 0) { + uint32_t tmpDist = std::max(std::abs((creaturePos.getX() + 1) - centerPos.getX()), distance_y); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_EAST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_EAST); + } + } + } + + if (!keepDistance || offset_x >= 0) { + uint32_t tmpDist = std::max(std::abs((creaturePos.getX() - 1) - centerPos.getX()), distance_y); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_WEST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_WEST); + } + } + } + + if (!dirList.empty()) { + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + direction = dirList[uniform_random(0, dirList.size() - 1)]; + return true; + } + return false; +} + +bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, bool flee /* = false */) +{ + const Position& creaturePos = getPosition(); + + int_fast32_t dx = Position::getDistanceX(creaturePos, targetPos); + int_fast32_t dy = Position::getDistanceY(creaturePos, targetPos); + + int32_t distance = std::max(dx, dy); + + if (!flee && (distance > mType->info.targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) { + return false; // let the A* calculate it + } else if (!flee && distance == mType->info.targetDistance) { + return true; // we don't really care here, since it's what we wanted to reach (a dancestep will take of dancing in that position) + } + + int_fast32_t offsetx = Position::getOffsetX(creaturePos, targetPos); + int_fast32_t offsety = Position::getOffsetY(creaturePos, targetPos); + + if (dx <= 1 && dy <= 1) { + //seems like a target is near, it this case we need to slow down our movements (as a monster) + if (stepDuration < 2) { + stepDuration++; + } + } else if (stepDuration > 0) { + stepDuration--; + } + + if (offsetx == 0 && offsety == 0) { + return getRandomStep(creaturePos, direction); // player is "on" the monster so let's get some random step and rest will be taken care later. + } + + if (dx == dy) { + //player is diagonal to the monster + if (offsetx >= 1 && offsety >= 1) { + // player is NW + //escape to SE, S or E [and some extra] + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (s && e) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } else if (canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_SOUTHEAST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + + if (flee) { + if (n && w) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_WEST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_WEST; + } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_NORTH; + } + + return true; + } else if (offsetx <= -1 && offsety <= -1) { + //player is SE + //escape to NW , W or N [and some extra] + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + + if (w && n) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } + + if (canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_NORTHWEST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (flee) { + if (s && e) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + if (s && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_SOUTH; + } else if (e && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_EAST; + } + + return true; + } else if (offsetx >= 1 && offsety <= -1) { + //player is SW + //escape to NE, N, E [and some extra] + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (n && e) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + + if (canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_NORTHEAST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + + if (flee) { + if (s && w) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_WEST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_WEST; + } else if (s && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_SOUTH; + } + + return true; + } else if (offsetx <= -1 && offsety >= 1) { + // player is NE + //escape to SW, S, W [and some extra] + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (w && s) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_SOUTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_SOUTHWEST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (flee) { + if (n && e) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + if (e && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_EAST; + } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_NORTH; + } + + return true; + } + } + + //Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. + if (dy > dx) { + Direction playerDir = offsety < 0 ? DIRECTION_SOUTH : DIRECTION_NORTH; + switch (playerDir) { + case DIRECTION_NORTH: { + // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose WEST or EAST and again if we can't we need to decide about some diagonal movements. + if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { + direction = DIRECTION_SOUTH; + return true; + } + + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (w && e && offsetx == 0) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w && offsetx <= 0) { + direction = DIRECTION_WEST; + return true; + } else if (e && offsetx >= 0) { + direction = DIRECTION_EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); + bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); + if (sw || se) { + // we can move both dirs + if (sw && se) { + direction = boolean_random() ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST; + } else if (w) { + direction = DIRECTION_WEST; + } else if (sw) { + direction = DIRECTION_SOUTHWEST; + } else if (e) { + direction = DIRECTION_EAST; + } else if (se) { + direction = DIRECTION_SOUTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_NORTH)) { + // towards player, yea + direction = DIRECTION_NORTH; + return true; + } + + /* end of fleeing */ + break; + } + + case DIRECTION_SOUTH: { + if (canWalkTo(creaturePos, DIRECTION_NORTH)) { + direction = DIRECTION_NORTH; + return true; + } + + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (w && e && offsetx == 0) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w && offsetx <= 0) { + direction = DIRECTION_WEST; + return true; + } else if (e && offsetx >= 0) { + direction = DIRECTION_EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); + bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); + if (nw || ne) { + // we can move both dirs + if (nw && ne) { + direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST; + } else if (w) { + direction = DIRECTION_WEST; + } else if (nw) { + direction = DIRECTION_NORTHWEST; + } else if (e) { + direction = DIRECTION_EAST; + } else if (ne) { + direction = DIRECTION_NORTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_SOUTH)) { + // towards player, yea + direction = DIRECTION_SOUTH; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } else { + Direction playerDir = offsetx < 0 ? DIRECTION_EAST : DIRECTION_WEST; + switch (playerDir) { + case DIRECTION_WEST: { + if (canWalkTo(creaturePos, DIRECTION_EAST)) { + direction = DIRECTION_EAST; + return true; + } + + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (n && s && offsety == 0) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n && offsety <= 0) { + direction = DIRECTION_NORTH; + return true; + } else if (s && offsety >= 0) { + direction = DIRECTION_SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); + bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); + if (se || ne) { + if (se && ne) { + direction = boolean_random() ? DIRECTION_SOUTHEAST : DIRECTION_NORTHEAST; + } else if (s) { + direction = DIRECTION_SOUTH; + } else if (se) { + direction = DIRECTION_SOUTHEAST; + } else if (n) { + direction = DIRECTION_NORTH; + } else if (ne) { + direction = DIRECTION_NORTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_WEST)) { + // towards player, yea + direction = DIRECTION_WEST; + return true; + } + + /* end of fleeing */ + break; + } + + case DIRECTION_EAST: { + if (canWalkTo(creaturePos, DIRECTION_WEST)) { + direction = DIRECTION_WEST; + return true; + } + + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (n && s && offsety == 0) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n && offsety <= 0) { + direction = DIRECTION_NORTH; + return true; + } else if (s && offsety >= 0) { + direction = DIRECTION_SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); + bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); + if (nw || sw) { + if (nw && sw) { + direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_SOUTHWEST; + } else if (n) { + direction = DIRECTION_NORTH; + } else if (nw) { + direction = DIRECTION_NORTHWEST; + } else if (s) { + direction = DIRECTION_SOUTH; + } else if (sw) { + direction = DIRECTION_SOUTHWEST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_EAST)) { + // towards player, yea + direction = DIRECTION_EAST; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } + + return true; +} + +bool Monster::canWalkTo(Position pos, Direction direction) const +{ + pos = getNextPosition(direction, pos); + if (isInSpawnRange(pos)) { + if (getWalkCache(pos) == 0) { + return false; + } + + Tile* tile = g_game.map.getTile(pos); + if (tile && tile->getTopVisibleCreature(this) == nullptr && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) { + return true; + } + } + return false; +} + +void Monster::death(Creature*) +{ + setAttackedCreature(nullptr); + + for (Creature* summon : summons) { + summon->changeHealth(-summon->getHealth()); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + summons.clear(); + + clearTargetList(); + clearFriendList(); + onIdleStatus(); +} + +Item* Monster::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) +{ + Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + if (mostDamageCreature) { + if (mostDamageCreature->getPlayer()) { + corpse->setCorpseOwner(mostDamageCreature->getID()); + } else { + const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); + if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) { + corpse->setCorpseOwner(mostDamageCreatureMaster->getID()); + } + } + } + } + return corpse; +} + +bool Monster::isInSpawnRange(const Position& pos) const +{ + if (!spawn) { + return true; + } + + if (Monster::despawnRadius == 0) { + return true; + } + + if (!Spawns::isInZone(masterPos, Monster::despawnRadius, pos)) { + return false; + } + + if (Monster::despawnRange == 0) { + return true; + } + + if (Position::getDistanceZ(pos, masterPos) > Monster::despawnRange) { + return false; + } + + return true; +} + +bool Monster::getCombatValues(int32_t& min, int32_t& max) +{ + if (minCombatValue == 0 && maxCombatValue == 0) { + return false; + } + + min = minCombatValue; + max = maxCombatValue; + return true; +} + +void Monster::updateLookDirection() +{ + Direction newDir = getDirection(); + + if (attackedCreature) { + const Position& pos = getPosition(); + const Position& attackedCreaturePos = attackedCreature->getPosition(); + int_fast32_t offsetx = Position::getOffsetX(attackedCreaturePos, pos); + int_fast32_t offsety = Position::getOffsetY(attackedCreaturePos, pos); + + int32_t dx = std::abs(offsetx); + int32_t dy = std::abs(offsety); + if (dx > dy) { + //look EAST/WEST + if (offsetx < 0) { + newDir = DIRECTION_WEST; + } + else { + newDir = DIRECTION_EAST; + } + } + else if (dx < dy) { + //look NORTH/SOUTH + if (offsety < 0) { + newDir = DIRECTION_NORTH; + } + else { + newDir = DIRECTION_SOUTH; + } + } + else { + Direction dir = getDirection(); + if (offsetx < 0 && offsety < 0) { + if (offsetx == -1 && offsety == -1) { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_WEST; + } + } + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_WEST; + } + else if (dir == DIRECTION_EAST) { + newDir = DIRECTION_NORTH; + } + } + else if (offsetx < 0 && offsety > 0) { + if (offsetx == -1 && offsety == 1) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_WEST; + } + } + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_WEST; + } + else if (dir == DIRECTION_EAST) { + newDir = DIRECTION_SOUTH; + } + } + else if (offsetx > 0 && offsety < 0) { + if (offsetx == 1 && offsety == -1) { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_EAST; + } + } + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_EAST; + } + else if (dir == DIRECTION_WEST) { + newDir = DIRECTION_NORTH; + } + } + else { + if (offsetx == 1 && offsety == 1) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_EAST; + } + } + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_EAST; + } + else if (dir == DIRECTION_WEST) { + newDir = DIRECTION_SOUTH; + } + } + } + } + + if (direction != newDir) { + g_game.internalCreatureTurn(this, newDir); + } +} + +void Monster::dropLoot(Container* corpse, Creature*) +{ + if (corpse && lootDrop) { + mType->createLoot(corpse); + } +} + +void Monster::setNormalCreatureLight() +{ + internalLight = mType->info.light; +} + +void Monster::drainHealth(Creature* attacker, int32_t damage) +{ + Creature::drainHealth(attacker, damage); + if (isInvisible()) { + removeCondition(CONDITION_INVISIBLE); + } +} + +void Monster::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + //In case a player with ignore flag set attacks the monster + setIdle(false); + Creature::changeHealth(healthChange, sendHealthChange); +} + +bool Monster::challengeCreature(Creature* creature) +{ + if (isSummon()) { + return false; + } + + bool result = selectTarget(creature); + if (result) { + targetChangeCooldown = 1000; + targetChangeTicks = 0; + } + return result; +} + +bool Monster::convinceCreature(Creature* creature) +{ + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (!mType->info.isConvinceable) { + return false; + } + } + + if (isSummon()) { + if (getMaster() == creature) { + return false; + } + + Creature* oldMaster = getMaster(); + oldMaster->removeSummon(this); + } + + creature->addSummon(this); + + setFollowCreature(nullptr); + setAttackedCreature(nullptr); + + //destroy summons + for (Creature* summon : summons) { + summon->changeHealth(-summon->getHealth()); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + summons.clear(); + + isMasterInRange = true; + updateTargetList(); + updateIdleStatus(); + + //Notify surrounding about the change + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + g_game.map.getSpectators(list, creature->getPosition(), true); + for (Creature* spectator : list) { + spectator->onCreatureConvinced(creature, this); + } + + if (spawn) { + spawn->removeMonster(this); + spawn = nullptr; + } + return true; +} + +void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) +{ + if (convincer != this && (isFriend(creature) || isOpponent(creature))) { + updateTargetList(); + updateIdleStatus(); + } +} + +void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + + fpp.minTargetDist = 1; + fpp.maxTargetDist = mType->info.targetDistance; + + if (isSummon()) { + if (getMaster() == creature) { + fpp.maxTargetDist = 2; + fpp.fullPathSearch = true; + } else if (mType->info.targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } + } else if (isFleeing()) { + //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) + fpp.maxTargetDist = Map::maxViewportX; + fpp.clearSight = false; + fpp.keepDistance = true; + fpp.fullPathSearch = false; + } else if (mType->info.targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } +} diff --git a/app/SabrehavenServer/src/monster.h b/app/SabrehavenServer/src/monster.h new file mode 100644 index 0000000..d44e8ec --- /dev/null +++ b/app/SabrehavenServer/src/monster.h @@ -0,0 +1,290 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 +#define FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 + +#include "tile.h" +#include "monsters.h" +#include "configmanager.h" + +class Creature; +class Game; +class Spawn; +class Combat; + +extern ConfigManager g_config; + +typedef std::unordered_set CreatureHashSet; +typedef std::list CreatureList; + +enum TargetSearchType_t { + TARGETSEARCH_ANY, + TARGETSEARCH_RANDOM, + TARGETSEARCH_NEAREST, + TARGETSEARCH_WEAKEST, + TARGETSEARCH_MOSTDAMAGE, +}; + +class Monster final : public Creature +{ + public: + static Monster* createMonster(const std::string& name); + static int32_t despawnRange; + static int32_t despawnRadius; + + explicit Monster(MonsterType* mtype); + ~Monster(); + + // non-copyable + Monster(const Monster&) = delete; + Monster& operator=(const Monster&) = delete; + + Monster* getMonster() final { + return this; + } + const Monster* getMonster() const final { + return this; + } + + void setID() final { + if (id == 0) { + id = monsterAutoID++; + } + } + + void removeList() final; + void addList() final; + + const std::string& getName() const final { + return mType->name; + } + const std::string& getNameDescription() const final { + return mType->nameDescription; + } + std::string getDescription(int32_t) const final { + return strDescription + '.'; + } + + const Position& getMasterPos() const { + return masterPos; + } + void setMasterPos(Position pos) { + masterPos = pos; + } + + RaceType_t getRace() const final { + return mType->info.race; + } + int32_t getArmor() const final { + int32_t armor = mType->info.armor; + if (armor > 1) { + return rand() % (mType->info.armor >> 1) + (mType->info.armor >> 1); + } + + return armor; + } + int32_t getDefense() final; + bool isPushable() const final { + return mType->info.pushable && baseSpeed != 0; + } + bool isAttackable() const final { + return mType->info.isAttackable; + } + + bool canPushItems() const { + return mType->info.canPushItems; + } + bool canPushCreatures() const { + return mType->info.canPushCreatures; + } + bool isHostile() const { + return mType->info.isHostile; + } + bool canSee(const Position& pos) const final; + bool canSeeInvisibility() const final { + return isImmune(CONDITION_INVISIBLE); + } + uint32_t getManaCost() const { + return mType->info.manaCost; + } + void setSpawn(Spawn* spawn) { + this->spawn = spawn; + } + + void onAttackedCreature(Creature* creature) final; + void onAttackedCreatureDisappear(bool isLogout) override; + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) final; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final; + + void drainHealth(Creature* attacker, int32_t damage) final; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; + void onWalk() final; + bool getNextStep(Direction& direction, uint32_t& flags) final; + void onFollowCreatureComplete(const Creature* creature) final; + + void onThink(uint32_t interval) final; + + bool challengeCreature(Creature* creature) final; + bool convinceCreature(Creature* creature) final; + + void setNormalCreatureLight() final; + bool getCombatValues(int32_t& min, int32_t& max) final; + + void doExtraMeleeAttack(); + void doAttacking(uint32_t interval) final; + bool hasExtraSwing() override { + return extraMeleeAttack; + } + + bool searchTarget(TargetSearchType_t searchType); + bool selectTarget(Creature* creature); + + const CreatureList& getTargetList() const { + return targetList; + } + const CreatureHashSet& getFriendList() const { + return friendList; + } + + bool isTarget(const Creature* creature) const; + bool isFleeing() const { + return !isSummon() && getHealth() <= mType->info.runAwayHealth; + } + + bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false); + bool isTargetNearby() const { + return stepDuration >= 1; + } + + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false); + + static uint32_t monsterAutoID; + + private: + CreatureHashSet friendList; + CreatureList targetList; + + std::string strDescription; + + MonsterType* mType; + Spawn* spawn = nullptr; + + int64_t lifetime = 0; + int64_t nextDanceStepRound = 0; + int64_t earliestAttackTime = 0; + int64_t earliestWakeUpTime = 0; + int64_t earliestDanceTime = 0; + + uint32_t targetChangeTicks = 0; + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t targetChangeCooldown = 0; + int32_t stepDuration = 0; + + Position masterPos; + + bool isIdle = true; + bool extraMeleeAttack = false; + bool isMasterInRange = false; + bool egibleToDance = true; + + void onCreatureEnter(Creature* creature); + void onCreatureLeave(Creature* creature); + void onCreatureFound(Creature* creature, bool pushFront = false); + + void updateLookDirection(); + + void addFriend(Creature* creature); + void removeFriend(Creature* creature); + void addTarget(Creature* creature, bool pushFront = false); + void removeTarget(Creature* creature); + + void updateTargetList(); + void clearTargetList(); + void clearFriendList(); + + void death(Creature* lastHitCreature) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + + void setIdle(bool idle); + void updateIdleStatus(); + bool getIdleStatus() const { + return isIdle; + } + + void onAddCondition(ConditionType_t type) final; + void onEndCondition(ConditionType_t type) final; + void onCreatureConvinced(const Creature* convincer, const Creature* creature) final; + + bool canUseAttack(const Position& pos, const Creature* target) const; + bool getRandomStep(const Position& creaturePos, Direction& direction) const; + bool getDanceStep(const Position& creaturePos, Direction& direction, + bool keepAttack = true, bool keepDistance = true); + bool isInSpawnRange(const Position& pos) const; + bool canWalkTo(Position pos, Direction direction) const; + + static bool pushItem(Item* item); + static void pushItems(Tile* tile); + static bool pushCreature(Creature* creature); + static void pushCreatures(Tile* tile); + + void onThinkTarget(uint32_t interval); + void onThinkYell(uint32_t interval); + void onThinkDefense(uint32_t interval); + + bool isFriend(const Creature* creature) const; + bool isOpponent(const Creature* creature) const; + + uint64_t getLostExperience() const final { + return skillLoss ? mType->info.experience : 0; + } + uint16_t getLookCorpse() const final { + if (!g_config.getBoolean(ConfigManager::CORPSE_OWNER_ENABLED)) { + const ItemType& itemtype = Item::items[mType->info.lookcorpse]; + if (itemtype.decayTo != 0) { + return itemtype.decayTo; + } + } + + return mType->info.lookcorpse; + } + void dropLoot(Container* corpse, Creature* lastHitCreature) final; + uint32_t getDamageImmunities() const final { + return mType->info.damageImmunities; + } + uint32_t getConditionImmunities() const final { + return mType->info.conditionImmunities; + } + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; + bool useCacheMap() const final { + return true; + } + + friend class LuaScriptInterface; + friend class AreaSpawnEvent; + friend class Combat; + friend class Creature; +}; + +#endif diff --git a/app/SabrehavenServer/src/monsters.cpp b/app/SabrehavenServer/src/monsters.cpp new file mode 100644 index 0000000..1d11d2e --- /dev/null +++ b/app/SabrehavenServer/src/monsters.cpp @@ -0,0 +1,1100 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "monsters.h" +#include "monster.h" +#include "spells.h" +#include "combat.h" +#include "configmanager.h" +#include "game.h" + +#include "pugicast.h" + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern ConfigManager g_config; + +spellBlock_t::~spellBlock_t() +{ + if (combatSpell) { + delete spell; + } +} + +uint32_t Monsters::getLootRandom() +{ + return uniform_random(0, MAX_LOOTCHANCE); +} + +void MonsterType::createLoot(Container* corpse) +{ + if (g_config.getNumber(ConfigManager::RATE_LOOT) == 0) { + corpse->startDecaying(); + return; + } + + Item* bagItem = Item::CreateItem(ITEM_BAG, 1); + if (!bagItem) { + return; + } + + Container* bagContainer = bagItem->getContainer(); + if (!bagContainer) { + return; + } + + if (g_game.internalAddItem(corpse, bagItem) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(bagItem); + } + + bool includeBagLoot = false; + for (auto it = info.lootItems.rbegin(), end = info.lootItems.rend(); it != end; ++it) { + std::vector itemList = createLootItem(*it); + if (itemList.empty()) { + continue; + } + + for (Item* item : itemList) { + //check containers + if (Container* container = item->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + continue; + } + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.weaponType != WEAPON_NONE || + itemType.stopTime || + itemType.decayTime) { + includeBagLoot = true; + if (g_game.internalAddItem(bagContainer, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } + } else { + if (g_game.internalAddItem(corpse, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } + } + } + } + + if (!includeBagLoot) { + g_game.internalRemoveItem(bagItem); + } + + if (g_config.getBoolean(ConfigManager::SHOW_MONSTER_LOOT)) { + Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); + if (owner) { + std::ostringstream ss; + ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); + + if (owner->getParty()) { + owner->getParty()->broadcastPartyLoot(ss.str()); + } else { + owner->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + } + } + } + + corpse->startDecaying(); +} + +std::vector MonsterType::createLootItem(const LootBlock& lootBlock) +{ + int32_t itemCount = 0; + + uint32_t randvalue = Monsters::getLootRandom(); + uint32_t extraMoney = g_config.getNumber(ConfigManager::MONEY_RATE); + uint32_t countMax = lootBlock.countmax + 1; + + if (randvalue < g_config.getNumber(ConfigManager::RATE_LOOT) * lootBlock.chance) { + if (Item::items[lootBlock.id].stackable) { + if (lootBlock.id == 3031) { + countMax *= extraMoney; + } + + itemCount = randvalue % countMax; + } else { + itemCount = 1; + } + } + + std::vector itemList; + while (itemCount > 0) { + uint16_t n = static_cast(std::min(itemCount, 100)); + Item* tmpItem = Item::CreateItem(lootBlock.id, n); + if (!tmpItem) { + break; + } + + itemCount -= n; + + if (lootBlock.subType != -1) { + tmpItem->setSubType(lootBlock.subType); + } + + if (lootBlock.actionId != -1) { + tmpItem->setActionId(lootBlock.actionId); + } + + if (!lootBlock.text.empty()) { + tmpItem->setText(lootBlock.text); + } + + itemList.push_back(tmpItem); + } + return itemList; +} + +bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) +{ + auto it = lootblock.childLoot.begin(), end = lootblock.childLoot.end(); + if (it == end) { + return true; + } + + for (; it != end && parent->size() < parent->capacity(); ++it) { + auto itemList = createLootItem(*it); + for (Item* tmpItem : itemList) { + if (Container* container = tmpItem->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + } else { + parent->internalAddThing(container); + } + } else { + parent->internalAddThing(tmpItem); + } + } + } + return !parent->empty(); +} + +bool Monsters::loadFromXml(bool reloading /*= false*/) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml"); + if (!result) { + printXMLError("Error - Monsters::loadFromXml", "data/monster/monsters.xml", result); + return false; + } + + loaded = true; + + std::list> monsterScriptList; + for (auto monsterNode : doc.child("monsters").children()) { + loadMonster("data/monster/" + std::string(monsterNode.attribute("file").as_string()), monsterNode.attribute("name").as_string(), monsterScriptList, reloading); + } + + if (!monsterScriptList.empty()) { + if (!scriptInterface) { + scriptInterface.reset(new LuaScriptInterface("Monster Interface")); + scriptInterface->initState(); + } + + for (const auto& scriptEntry : monsterScriptList) { + MonsterType* mType = scriptEntry.first; + if (scriptInterface->loadFile("data/monster/scripts/" + scriptEntry.second) == 0) { + mType->info.scriptInterface = scriptInterface.get(); + mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); + mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); + mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); + mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); + mType->info.thinkEvent = scriptInterface->getEvent("onThink"); + } else { + std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << scriptEntry.second << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + } + } + } + return true; +} + +bool Monsters::reload() +{ + loaded = false; + + scriptInterface.reset(); + + return loadFromXml(true); +} + +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t cycle, int32_t count, int32_t max_count) +{ + ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); + + condition->setParam(CONDITION_PARAM_CYCLE, cycle); + condition->setParam(CONDITION_PARAM_COUNT, count); + condition->setParam(CONDITION_PARAM_MAX_COUNT, max_count); + return condition; +} + +bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description) +{ + std::string name; + std::string scriptName; + bool isScripted; + + pugi::xml_attribute attr; + if ((attr = node.attribute("script"))) { + scriptName = attr.as_string(); + isScripted = true; + } else if ((attr = node.attribute("name"))) { + name = attr.as_string(); + isScripted = false; + } else { + return false; + } + + if ((attr = node.attribute("chance"))) { + uint32_t chance = pugi::cast(attr.value()); + if (chance > 100) { + chance = 100; + } + + if (chance == 0) { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Spell chance is zero: " << name << std::endl; + } + + sb.chance = chance; + } + + if ((attr = node.attribute("range"))) { + uint32_t range = pugi::cast(attr.value()); + if (range > (Map::maxViewportX * 2)) { + range = Map::maxViewportX * 2; + } + sb.range = range; + } else { + sb.range = Map::maxClientViewportX; + } + + if ((attr = node.attribute("min"))) { + sb.minCombatValue = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("max"))) { + sb.maxCombatValue = pugi::cast(attr.value()); + + //normalize values + if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { + int32_t value = sb.maxCombatValue; + sb.maxCombatValue = sb.minCombatValue; + sb.minCombatValue = value; + } + } + + if (auto spell = g_spells->getSpellByName(name)) { + sb.spell = spell; + return true; + } + + CombatSpell* combatSpell = nullptr; + bool needTarget = false; + bool needDirection = false; + + if (isScripted) { + if ((attr = node.attribute("direction"))) { + needDirection = attr.as_bool(); + } + + if ((attr = node.attribute("target"))) { + needTarget = attr.as_bool(); + } + + std::unique_ptr combatSpellPtr(new CombatSpell(nullptr, needTarget, needDirection)); + if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + scriptName)) { + return false; + } + + if (!combatSpellPtr->loadScriptCombat()) { + return false; + } + + combatSpell = combatSpellPtr.release(); + combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + } else { + Combat* combat = new Combat; + if ((attr = node.attribute("length"))) { + int32_t length = pugi::cast(attr.value()); + if (length > 0) { + int32_t spread = 3; + + //need direction spell + if ((attr = node.attribute("spread"))) { + spread = std::max(0, pugi::cast(attr.value())); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(length, spread); + combat->setArea(area); + + needDirection = true; + } + } + + if ((attr = node.attribute("radius"))) { + int32_t radius = pugi::cast(attr.value()); + + //target spell + if ((attr = node.attribute("target"))) { + needTarget = attr.as_bool(); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(radius); + combat->setArea(area); + } + + std::string tmpName = asLowerCaseString(name); + + if (tmpName == "physical") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); + combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); + uint32_t tD = this->getMonsterType(description)->info.targetDistance; + if (tD == 1) { + if (sb.range > 1) { + combat->setOrigin(ORIGIN_RANGED); + } + else { + combat->setOrigin(ORIGIN_MELEE); + } + } + else if (tD > 1 && sb.range > 1) { + combat->setOrigin(ORIGIN_RANGED); + } + } else if (tmpName == "bleed") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + } else if (tmpName == "poison" || tmpName == "earth") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE); + } else if (tmpName == "fire") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE); + } else if (tmpName == "energy") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE); + } else if (tmpName == "drown") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE); + } else if (tmpName == "lifedrain") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN); + } else if (tmpName == "manadrain") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_MANADRAIN); + } else if (tmpName == "healing") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HEALING); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } else if (tmpName == "speed") { + int32_t speedChange = 0; + int32_t variation = 0; + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("speedchange"))) { + speedChange = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("variation"))) { + variation = pugi::cast(attr.value()); + } + + ConditionType_t conditionType; + if (speedChange > 0) { + conditionType = CONDITION_HASTE; + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } else { + conditionType = CONDITION_PARALYZE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + condition->setVariation(variation); + condition->setSpeedDelta(speedChange); + combat->setCondition(condition); + } else if (tmpName == "outfit") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("monster"))) { + MonsterType* mType = g_monsters.getMonsterType(attr.as_string()); + if (mType) { + ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->setOutfit(mType->info.outfit); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if ((attr = node.attribute("item"))) { + Outfit_t outfit; + outfit.lookTypeEx = pugi::cast(attr.value()); + + ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->setOutfit(outfit); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if (tmpName == "invisible") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } else if (tmpName == "drunk") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + combat->setCondition(condition); + } else if (tmpName == "firefield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); + } else if (tmpName == "poisonfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); + } else if (tmpName == "energyfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); + } else if (tmpName == "firecondition" || tmpName == "energycondition" || + tmpName == "earthcondition" || tmpName == "poisoncondition" || + tmpName == "icecondition" || tmpName == "freezecondition" || + tmpName == "physicalcondition" || tmpName == "drowncondition") { + ConditionType_t conditionType = CONDITION_NONE; + + if (tmpName == "firecondition") { + conditionType = CONDITION_FIRE; + } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { + conditionType = CONDITION_POISON; + } else if (tmpName == "energycondition") { + conditionType = CONDITION_ENERGY; + } else if (tmpName == "drowncondition") { + conditionType = CONDITION_DROWN; + } + + int32_t cycle = 0; + if ((attr = node.attribute("count"))) { + cycle = std::abs(pugi::cast(attr.value())); + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing count attribute" << std::endl; + delete combat; + return false; + } + + int32_t count = 0; + + if (conditionType == CONDITION_POISON) { + count = 3; + } else if (conditionType == CONDITION_FIRE) { + count = 8; + cycle /= 10; + } else if (conditionType == CONDITION_ENERGY) { + count = 10; + cycle /= 20; + } + + Condition* condition = getDamageCondition(conditionType, cycle, count, count); + combat->setCondition(condition); + } else if (tmpName == "strength") { + // + } else if (tmpName == "effect") { + // + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; + delete combat; + return false; + } + + combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + + combatSpell = new CombatSpell(combat, needTarget, needDirection); + + for (auto attributeNode : node.children()) { + if ((attr = attributeNode.attribute("key"))) { + const char* value = attr.value(); + if (strcasecmp(value, "shooteffect") == 0) { + if ((attr = attributeNode.attribute("value"))) { + ShootType_t shoot = getShootType(attr.as_string()); + if (shoot != CONST_ANI_NONE) { + combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); + } else { + std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown shootEffect: " << attr.as_string() << std::endl; + } + } + } else if (strcasecmp(value, "areaeffect") == 0) { + if ((attr = attributeNode.attribute("value"))) { + MagicEffectClasses effect = getMagicEffect(attr.as_string()); + if (effect != CONST_ME_NONE) { + combat->setParam(COMBAT_PARAM_EFFECT, effect); + } else { + std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown areaEffect: " << attr.as_string() << std::endl; + } + } + } else { + std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << attr.as_string() << "\" does not exist." << std::endl; + } + } + } + } + + sb.spell = combatSpell; + if (combatSpell) { + sb.combatSpell = true; + } + return true; +} + +bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading /*= false*/) +{ + MonsterType* mType = nullptr; + bool new_mType = true; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(file.c_str()); + if (!result) { + printXMLError("Error - Monsters::loadMonster", file, result); + return false; + } + + pugi::xml_node monsterNode = doc.child("monster"); + if (!monsterNode) { + std::cout << "[Error - Monsters::loadMonster] Missing monster node in: " << file << std::endl; + return false; + } + + pugi::xml_attribute attr; + if (!(attr = monsterNode.attribute("name"))) { + std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file << std::endl; + return false; + } + + if (reloading) { + mType = getMonsterType(monsterName); + if (mType != nullptr) { + new_mType = false; + mType->info = {}; + } + } + + if (new_mType) { + mType = &monsters[asLowerCaseString(monsterName)]; + } + + mType->name = attr.as_string(); + + if ((attr = monsterNode.attribute("nameDescription"))) { + mType->nameDescription = attr.as_string(); + } else { + mType->nameDescription = "a " + asLowerCaseString(mType->name); + } + + if ((attr = monsterNode.attribute("race"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + uint16_t tmpInt = pugi::cast(attr.value()); + if (tmpStrValue == "venom" || tmpInt == 1) { + mType->info.race = RACE_VENOM; + } else if (tmpStrValue == "blood" || tmpInt == 2) { + mType->info.race = RACE_BLOOD; + } else if (tmpStrValue == "undead" || tmpInt == 3) { + mType->info.race = RACE_UNDEAD; + } else if (tmpStrValue == "fire" || tmpInt == 4) { + mType->info.race = RACE_FIRE; + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; + } + } + + if ((attr = monsterNode.attribute("experience"))) { + mType->info.experience = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("speed"))) { + mType->info.baseSpeed = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("manacost"))) { + mType->info.manaCost = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("skull"))) { + mType->info.skull = getSkullType(attr.as_string()); + } + + if ((attr = monsterNode.attribute("script"))) { + monsterScriptList.emplace_back(mType, attr.as_string()); + } + + pugi::xml_node node; + if ((node = monsterNode.child("health"))) { + if ((attr = node.attribute("now"))) { + mType->info.health = pugi::cast(attr.value()); + } else { + std::cout << "[Error - Monsters::loadMonster] Missing health now. " << file << std::endl; + } + + if ((attr = node.attribute("max"))) { + mType->info.healthMax = pugi::cast(attr.value()); + } else { + std::cout << "[Error - Monsters::loadMonster] Missing health max. " << file << std::endl; + } + } + + if ((node = monsterNode.child("flags"))) { + for (auto flagNode : node.children()) { + attr = flagNode.first_attribute(); + const char* attrName = attr.name(); + if (strcasecmp(attrName, "summonable") == 0) { + mType->info.isSummonable = attr.as_bool(); + } else if (strcasecmp(attrName, "attackable") == 0) { + mType->info.isAttackable = attr.as_bool(); + } else if (strcasecmp(attrName, "hostile") == 0) { + mType->info.isHostile = attr.as_bool(); + } else if (strcasecmp(attrName, "illusionable") == 0) { + mType->info.isIllusionable = attr.as_bool(); + } else if (strcasecmp(attrName, "convinceable") == 0) { + mType->info.isConvinceable = attr.as_bool(); + } else if (strcasecmp(attrName, "pushable") == 0) { + mType->info.pushable = attr.as_bool(); + } else if (strcasecmp(attrName, "canpushitems") == 0) { + mType->info.canPushItems = attr.as_bool(); + } else if (strcasecmp(attrName, "canpushcreatures") == 0) { + mType->info.canPushCreatures = attr.as_bool(); + } else if (strcasecmp(attrName, "lightlevel") == 0) { + mType->info.light.level = pugi::cast(attr.value()); + } else if (strcasecmp(attrName, "lightcolor") == 0) { + mType->info.light.color = pugi::cast(attr.value()); + } else if (strcasecmp(attrName, "targetdistance") == 0) { + mType->info.targetDistance = std::max(1, pugi::cast(attr.value())); + } else if (strcasecmp(attrName, "runonhealth") == 0) { + mType->info.runAwayHealth = pugi::cast(attr.value()); + } else if (strcasecmp(attrName, "hidehealth") == 0) { + mType->info.hiddenHealth = attr.as_bool(); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; + } + } + + //if a monster can push creatures, + // it should not be pushable + if (mType->info.canPushCreatures) { + mType->info.pushable = false; + } + } + + if ((node = monsterNode.child("targetchange"))) { + if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { + mType->info.changeTargetSpeed = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing targetchange speed. " << file << std::endl; + } + + if ((attr = node.attribute("chance"))) { + mType->info.changeTargetChance = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing targetchange chance. " << file << std::endl; + } + } + + if ((node = monsterNode.child("targetstrategy"))) { + if ((attr = node.attribute("nearest"))) { + mType->info.strategyNearestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing nearest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("weakest"))) { + mType->info.strategyWeakestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing weakest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("mostdamage"))) { + mType->info.strategyMostDamageEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing most damage enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("random"))) { + mType->info.strategyRandomEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing random enemy chance. " << file << std::endl; + } + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing target change strategies. " << file << std::endl; + } + + if ((node = monsterNode.child("look"))) { + if ((attr = node.attribute("type"))) { + mType->info.outfit.lookType = pugi::cast(attr.value()); + + if ((attr = node.attribute("head"))) { + mType->info.outfit.lookHead = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("body"))) { + mType->info.outfit.lookBody = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("legs"))) { + mType->info.outfit.lookLegs = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("feet"))) { + mType->info.outfit.lookFeet = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("addons"))) { + mType->info.outfit.lookAddons = pugi::cast(attr.value()); + } + } else if ((attr = node.attribute("typeex"))) { + mType->info.outfit.lookTypeEx = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl; + } + + if ((attr = node.attribute("corpse"))) { + mType->info.lookcorpse = pugi::cast(attr.value()); + } + } + + if ((node = monsterNode.child("attacks"))) { + if ((attr = node.attribute("attack"))) { + mType->info.attack = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("skill"))) { + mType->info.skill = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("poison"))) { + mType->info.poison = pugi::cast(attr.value()); + } + + for (auto attackNode : node.children()) { + spellBlock_t sb; + if (deserializeSpell(attackNode, sb, monsterName)) { + mType->info.attackSpells.emplace_back(std::move(sb)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("defenses"))) { + if ((attr = node.attribute("defense"))) { + mType->info.defense = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("armor"))) { + mType->info.armor = pugi::cast(attr.value()); + } + + for (auto defenseNode : node.children()) { + spellBlock_t sb; + if (deserializeSpell(defenseNode, sb, monsterName)) { + mType->info.defenseSpells.emplace_back(std::move(sb)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("immunities"))) { + for (auto immunityNode : node.children()) { + if ((attr = immunityNode.attribute("name"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + if (tmpStrValue == "physical") { + mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + } else if (tmpStrValue == "energy") { + mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->info.conditionImmunities |= CONDITION_ENERGY; + } else if (tmpStrValue == "fire") { + mType->info.damageImmunities |= COMBAT_FIREDAMAGE; + mType->info.conditionImmunities |= CONDITION_FIRE; + } else if (tmpStrValue == "drown") { + mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; + mType->info.conditionImmunities |= CONDITION_DROWN; + } else if (tmpStrValue == "poison" || + tmpStrValue == "earth") { + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; + } else if (tmpStrValue == "lifedrain") { + mType->info.damageImmunities |= COMBAT_LIFEDRAIN; + } else if (tmpStrValue == "manadrain") { + mType->info.damageImmunities |= COMBAT_MANADRAIN; + } else if (tmpStrValue == "paralyze") { + mType->info.conditionImmunities |= CONDITION_PARALYZE; + } else if (tmpStrValue == "outfit") { + mType->info.conditionImmunities |= CONDITION_OUTFIT; + } else if (tmpStrValue == "drunk") { + mType->info.conditionImmunities |= CONDITION_DRUNK; + } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { + mType->info.conditionImmunities |= CONDITION_INVISIBLE; + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; + } + } else if ((attr = immunityNode.attribute("physical"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + } + } else if ((attr = immunityNode.attribute("energy"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->info.conditionImmunities |= CONDITION_ENERGY; + } + } else if ((attr = immunityNode.attribute("fire"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_FIREDAMAGE; + mType->info.conditionImmunities |= CONDITION_FIRE; + } + } else if ((attr = immunityNode.attribute("drown"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; + mType->info.conditionImmunities |= CONDITION_DROWN; + } + } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; + } + } else if ((attr = immunityNode.attribute("lifedrain"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_LIFEDRAIN; + } + } else if ((attr = immunityNode.attribute("manadrain"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_MANADRAIN; + } + } else if ((attr = immunityNode.attribute("paralyze"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_PARALYZE; + } + } else if ((attr = immunityNode.attribute("outfit"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_OUTFIT; + } + } else if ((attr = immunityNode.attribute("drunk"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_DRUNK; + } + } else if ((attr = immunityNode.attribute("invisible")) || (attr = immunityNode.attribute("invisibility"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_INVISIBLE; + } + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("voices"))) { + for (auto voiceNode : node.children()) { + voiceBlock_t vb; + if ((attr = voiceNode.attribute("sentence"))) { + vb.text = attr.as_string(); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing voice sentence. " << file << std::endl; + } + + if ((attr = voiceNode.attribute("yell"))) { + vb.yellText = attr.as_bool(); + } else { + vb.yellText = false; + } + mType->info.voiceVector.emplace_back(vb); + } + } + + if ((node = monsterNode.child("loot"))) { + for (auto lootNode : node.children()) { + LootBlock lootBlock; + if (loadLootItem(lootNode, lootBlock)) { + mType->info.lootItems.emplace_back(std::move(lootBlock)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load loot. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("elements"))) { + for (auto elementNode : node.children()) { + if ((attr = elementNode.attribute("physicalPercent"))) { + mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { + mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("firePercent"))) { + mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("energyPercent"))) { + mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("lifedrainPercent"))) { + mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("manadrainPercent"))) { + mType->info.elementMap[COMBAT_MANADRAIN] = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown element percent. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("summons"))) { + if ((attr = node.attribute("maxSummons"))) { + mType->info.maxSummons = std::min(pugi::cast(attr.value()), 100); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing summons maxSummons. " << file << std::endl; + } + + for (auto summonNode : node.children()) { + int32_t chance = 100; + int32_t max = mType->info.maxSummons; + bool force = false; + + if ((attr = summonNode.attribute("chance"))) { + chance = pugi::cast(attr.value()); + } + + if ((attr = summonNode.attribute("max"))) { + max = pugi::cast(attr.value()); + } + + if ((attr = summonNode.attribute("force"))) { + force = attr.as_bool(); + } + + if ((attr = summonNode.attribute("name"))) { + summonBlock_t sb; + sb.name = attr.as_string(); + sb.chance = chance; + sb.max = max; + sb.force = force; + mType->info.summons.emplace_back(sb); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing summon name. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("script"))) { + for (auto eventNode : node.children()) { + if ((attr = eventNode.attribute("name"))) { + mType->info.scripts.emplace_back(attr.as_string()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing name for script event. " << file << std::endl; + } + } + } + + mType->info.summons.shrink_to_fit(); + mType->info.lootItems.shrink_to_fit(); + mType->info.attackSpells.shrink_to_fit(); + mType->info.defenseSpells.shrink_to_fit(); + mType->info.voiceVector.shrink_to_fit(); + mType->info.scripts.shrink_to_fit(); + return true; +} + +bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) +{ + pugi::xml_attribute attr; + if ((attr = node.attribute("id"))) { + lootBlock.id = pugi::cast(attr.value()); + } else if ((attr = node.attribute("name"))) { + auto name = attr.as_string(); + auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name)); + + if (ids.first == Item::items.nameToItems.cend()) { + std::cout << "[Warning - Monsters::loadMonster] Unknown loot item \"" << name << "\". " << std::endl; + return false; + } + + uint32_t id = ids.first->second; + + if (std::next(ids.first) != ids.second) { + std::cout << "[Warning - Monsters::loadMonster] Non-unique loot item \"" << name << "\". " << std::endl; + return false; + } + + lootBlock.id = id; + } + + if (lootBlock.id == 0) { + return false; + } + + if ((attr = node.attribute("countmax"))) { + lootBlock.countmax = std::max(1, pugi::cast(attr.value())); + } else { + lootBlock.countmax = 1; + } + + if ((attr = node.attribute("chance")) || (attr = node.attribute("chance1"))) { + lootBlock.chance = std::min(MAX_LOOTCHANCE, pugi::cast(attr.value())); + } else { + lootBlock.chance = MAX_LOOTCHANCE; + } + + if (Item::items[lootBlock.id].isContainer()) { + loadLootContainer(node, lootBlock); + } + + //optional + if ((attr = node.attribute("subtype"))) { + lootBlock.subType = pugi::cast(attr.value()); + } else { + uint32_t charges = Item::items[lootBlock.id].charges; + if (charges != 0) { + lootBlock.subType = charges; + } + } + + if ((attr = node.attribute("actionId"))) { + lootBlock.actionId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("text"))) { + lootBlock.text = attr.as_string(); + } + return true; +} + +void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) +{ + for (auto subNode : node.children()) { + LootBlock lootBlock; + if (loadLootItem(subNode, lootBlock)) { + lBlock.childLoot.emplace_back(std::move(lootBlock)); + } + } +} + +MonsterType* Monsters::getMonsterType(const std::string& name) +{ + auto it = monsters.find(asLowerCaseString(name)); + + if (it == monsters.end()) { + return nullptr; + } + return &it->second; +} diff --git a/app/SabrehavenServer/src/monsters.h b/app/SabrehavenServer/src/monsters.h new file mode 100644 index 0000000..23906ab --- /dev/null +++ b/app/SabrehavenServer/src/monsters.h @@ -0,0 +1,201 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D +#define FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D + +#include "creature.h" + +#define MAX_LOOTCHANCE 1000 +#define MAX_STATICWALK 100 + +struct LootBlock { + uint16_t id; + uint32_t countmax; + uint32_t chance; + + //optional + int32_t subType; + int32_t actionId; + std::string text; + + std::vector childLoot; + LootBlock() { + id = 0; + countmax = 0; + chance = 0; + + subType = -1; + actionId = -1; + } +}; + +struct summonBlock_t { + std::string name; + uint32_t chance; + uint32_t max; + bool force = false; +}; + +class BaseSpell; +struct spellBlock_t { + constexpr spellBlock_t() = default; + ~spellBlock_t(); + spellBlock_t(const spellBlock_t& other) = delete; + spellBlock_t& operator=(const spellBlock_t& other) = delete; + spellBlock_t(spellBlock_t&& other) : + spell(other.spell), + chance(other.chance), + range(other.range), + minCombatValue(other.minCombatValue), + maxCombatValue(other.maxCombatValue), + attack(other.attack), + skill(other.skill), + combatSpell(other.combatSpell) { + other.spell = nullptr; + } + + BaseSpell* spell = nullptr; + uint32_t chance = 100; + uint32_t range = 0; + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t attack = 0; + int32_t skill = 0; + bool combatSpell = false; +}; + +struct voiceBlock_t { + std::string text; + bool yellText; +}; + +class MonsterType +{ + struct MonsterInfo { + LuaScriptInterface* scriptInterface; + + std::map elementMap; + + std::vector voiceVector; + + std::vector lootItems; + std::vector scripts; + std::vector attackSpells; + std::vector defenseSpells; + std::vector summons; + + Skulls_t skull = SKULL_NONE; + Outfit_t outfit = {}; + RaceType_t race = RACE_BLOOD; + + LightInfo light = {}; + uint16_t lookcorpse = 0; + + uint64_t experience = 0; + + uint32_t manaCost = 0; + uint32_t maxSummons = 0; + uint32_t changeTargetSpeed = 0; + uint32_t conditionImmunities = 0; + uint32_t damageImmunities = 0; + uint32_t baseSpeed = 70; + + int32_t creatureAppearEvent = -1; + int32_t creatureDisappearEvent = -1; + int32_t creatureMoveEvent = -1; + int32_t creatureSayEvent = -1; + int32_t thinkEvent = -1; + int32_t targetDistance = 1; + int32_t runAwayHealth = 0; + int32_t health = 100; + int32_t healthMax = 100; + int32_t changeTargetChance = 0; + int32_t strategyNearestEnemy = 0; + int32_t strategyWeakestEnemy = 0; + int32_t strategyMostDamageEnemy = 0; + int32_t strategyRandomEnemy = 0; + int32_t armor = 0; + int32_t defense = 0; + int32_t attack = 0; + int32_t skill = 0; + int32_t poison = 0; + + bool canPushItems = false; + bool canPushCreatures = false; + bool pushable = true; + bool isSummonable = false; + bool isIllusionable = false; + bool isConvinceable = false; + bool isAttackable = true; + bool isHostile = true; + bool hiddenHealth = false; + }; + + public: + MonsterType() = default; + + // non-copyable + MonsterType(const MonsterType&) = delete; + MonsterType& operator=(const MonsterType&) = delete; + + std::string name; + std::string nameDescription; + + MonsterInfo info; + + void createLoot(Container* corpse); + bool createLootContainer(Container* parent, const LootBlock& lootblock); + std::vector createLootItem(const LootBlock& lootBlock); +}; + +class Monsters +{ + public: + Monsters() = default; + // non-copyable + Monsters(const Monsters&) = delete; + Monsters& operator=(const Monsters&) = delete; + + bool loadFromXml(bool reloading = false); + bool isLoaded() const { + return loaded; + } + bool reload(); + + MonsterType* getMonsterType(const std::string& name); + + static uint32_t getLootRandom(); + + private: + ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t cycles, int32_t count, int32_t max_count); + bool deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description = ""); + + bool loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading = false); + + void loadLootContainer(const pugi::xml_node& node, LootBlock&); + bool loadLootItem(const pugi::xml_node& node, LootBlock&); + + std::map monsters; + std::unique_ptr scriptInterface; + + bool loaded = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/movement.cpp b/app/SabrehavenServer/src/movement.cpp new file mode 100644 index 0000000..0dc15bf --- /dev/null +++ b/app/SabrehavenServer/src/movement.cpp @@ -0,0 +1,880 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "game.h" + +#include "pugicast.h" + +#include "movement.h" + +extern Game g_game; +extern Vocations g_vocations; + +MoveEvents::MoveEvents() : + scriptInterface("MoveEvents Interface") +{ + scriptInterface.initState(); +} + +MoveEvents::~MoveEvents() +{ + clear(); +} + +void MoveEvents::clearMap(MoveListMap& map) +{ + std::unordered_set set; + for (const auto& it : map) { + const MoveEventList& moveEventList = it.second; + for (const auto& i : moveEventList.moveEvent) { + for (MoveEvent* moveEvent : i) { + set.insert(moveEvent); + } + } + } + map.clear(); + + for (MoveEvent* moveEvent : set) { + delete moveEvent; + } +} + +void MoveEvents::clear() +{ + clearMap(itemIdMap); + clearMap(movementIdMap); + + for (const auto& it : positionMap) { + const MoveEventList& moveEventList = it.second; + for (const auto& i : moveEventList.moveEvent) { + for (MoveEvent* moveEvent : i) { + delete moveEvent; + } + } + } + positionMap.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& MoveEvents::getScriptInterface() +{ + return scriptInterface; +} + +std::string MoveEvents::getScriptBaseName() const +{ + return "movements"; +} + +Event* MoveEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "movevent") != 0) { + return nullptr; + } + return new MoveEvent(&scriptInterface); +} + +bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) +{ + MoveEvent* moveEvent = static_cast(event); //event is guaranteed to be a MoveEvent + + const MoveEvent_t eventType = moveEvent->getEventType(); + if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { + pugi::xml_attribute tileItemAttribute = node.attribute("tileitem"); + if (tileItemAttribute && pugi::cast(tileItemAttribute.value()) == 1) { + switch (eventType) { + case MOVE_EVENT_ADD_ITEM: + moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + break; + case MOVE_EVENT_REMOVE_ITEM: + moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + break; + default: + break; + } + } + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("itemid"))) { + int32_t id = pugi::cast(attr.value()); + addEvent(moveEvent, id, itemIdMap); + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + } else if ((attr = node.attribute("fromid"))) { + uint32_t id = pugi::cast(attr.value()); + uint32_t endId = pugi::cast(node.attribute("toid").value()); + + addEvent(moveEvent, id, itemIdMap); + + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + + while (++id <= endId) { + addEvent(moveEvent, id, itemIdMap); + + ItemType& tit = Item::items.getItemType(id); + tit.wieldInfo = moveEvent->getWieldInfo(); + tit.minReqLevel = moveEvent->getReqLevel(); + tit.minReqMagicLevel = moveEvent->getReqMagLv(); + tit.vocationString = moveEvent->getVocationString(); + } + } else { + while (++id <= endId) { + addEvent(moveEvent, id, itemIdMap); + } + } + } else if ((attr = node.attribute("movementid"))) { + addEvent(moveEvent, pugi::cast(attr.value()), movementIdMap); + } else if ((attr = node.attribute("frommovementid"))) { + uint32_t id = pugi::cast(attr.value()); + uint32_t endId = pugi::cast(node.attribute("tomovementid").value()); + addEvent(moveEvent, id, movementIdMap); + while (++id <= endId) { + addEvent(moveEvent, id, movementIdMap); + } + } else if ((attr = node.attribute("pos"))) { + std::vector posList = vectorAtoi(explodeString(attr.as_string(), ";")); + if (posList.size() < 3) { + return false; + } + + Position pos(posList[0], posList[1], posList[2]); + addEvent(moveEvent, pos, positionMap); + } else { + return false; + } + return true; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map) +{ + auto it = map.find(id); + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[id] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + for (MoveEvent* existingMoveEvent : moveEventList) { + if (existingMoveEvent->getSlot() == moveEvent->getSlot()) { + std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << id << std::endl; + } + } + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) +{ + uint32_t slotp; + switch (slot) { + case CONST_SLOT_HEAD: slotp = SLOTP_HEAD; break; + case CONST_SLOT_NECKLACE: slotp = SLOTP_NECKLACE; break; + case CONST_SLOT_BACKPACK: slotp = SLOTP_BACKPACK; break; + case CONST_SLOT_ARMOR: slotp = SLOTP_ARMOR; break; + case CONST_SLOT_RIGHT: slotp = SLOTP_RIGHT; break; + case CONST_SLOT_LEFT: slotp = SLOTP_LEFT; break; + case CONST_SLOT_LEGS: slotp = SLOTP_LEGS; break; + case CONST_SLOT_FEET: slotp = SLOTP_FEET; break; + case CONST_SLOT_AMMO: slotp = SLOTP_AMMO; break; + case CONST_SLOT_RING: slotp = SLOTP_RING; break; + default: slotp = 0; break; + } + + auto it = itemIdMap.find(item->getID()); + if (it != itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + for (MoveEvent* moveEvent : moveEventList) { + if ((moveEvent->getSlot() & slotp) != 0) { + return moveEvent; + } + } + } + return nullptr; +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType) +{ + MoveListMap::iterator it; + + if (item->hasAttribute(ITEM_ATTRIBUTE_MOVEMENTID)) { + it = movementIdMap.find(item->getMovementId()); + if (it != movementIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + } + + if (!item->hasCollisionEvent() && !item->hasSeparationEvent()) { + return nullptr; + } + + it = itemIdMap.find(item->getID()); + if (it != itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + + return nullptr; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map) +{ + auto it = map.find(pos); + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[pos] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + if (!moveEventList.empty()) { + std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl; + } + + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType) +{ + auto it = positionMap.find(tile->getPosition()); + if (it != positionMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + return nullptr; +} + +uint32_t MoveEvents::onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType) +{ + const Position& pos = tile->getPosition(); + + uint32_t ret = 1; + + MoveEvent* moveEvent = getEvent(tile, eventType); + if (moveEvent) { + ret &= moveEvent->fireStepEvent(creature, nullptr, pos); + } + + for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) { + Thing* thing = tile->getThing(i); + if (!thing) { + continue; + } + + Item* tileItem = thing->getItem(); + if (!tileItem) { + continue; + } + + moveEvent = getEvent(tileItem, eventType); + if (moveEvent) { + ret &= moveEvent->fireStepEvent(creature, tileItem, pos); + } + } + return ret; +} + +uint32_t MoveEvents::onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_EQUIP, slot); + if (!moveEvent) { + return 1; + } + return moveEvent->fireEquip(player, item, slot, isCheck); +} + +uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_DEEQUIP, slot); + if (!moveEvent) { + return 1; + } + return moveEvent->fireEquip(player, item, slot, true); +} + +uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) +{ + MoveEvent_t eventType1, eventType2; + if (isAdd) { + eventType1 = MOVE_EVENT_ADD_ITEM; + eventType2 = MOVE_EVENT_ADD_ITEM_ITEMTILE; + } else { + eventType1 = MOVE_EVENT_REMOVE_ITEM; + eventType2 = MOVE_EVENT_REMOVE_ITEM_ITEMTILE; + } + + uint32_t ret = 1; + MoveEvent* moveEvent = getEvent(tile, eventType1); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition()); + } + + moveEvent = getEvent(item, eventType1); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition()); + } + + for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) { + Thing* thing = tile->getThing(i); + if (!thing) { + continue; + } + + Item* tileItem = thing->getItem(); + if (!tileItem || tileItem == item) { + continue; + } + + moveEvent = getEvent(tileItem, eventType2); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, tileItem, tile->getPosition()); + } + } + return ret; +} + +MoveEvent::MoveEvent(LuaScriptInterface* interface) : Event(interface) {} + +MoveEvent::MoveEvent(const MoveEvent* copy) : + Event(copy), + eventType(copy->eventType), + stepFunction(copy->stepFunction), + moveFunction(copy->moveFunction), + equipFunction(copy->equipFunction), + slot(copy->slot), + reqLevel(copy->reqLevel), + reqMagLevel(copy->reqMagLevel), + premium(copy->premium), + vocationString(copy->vocationString), + wieldInfo(copy->wieldInfo), + vocEquipMap(copy->vocEquipMap) {} + +std::string MoveEvent::getScriptEventName() const +{ + switch (eventType) { + case MOVE_EVENT_STEP_IN: return "onStepIn"; + case MOVE_EVENT_STEP_OUT: return "onStepOut"; + case MOVE_EVENT_EQUIP: return "onEquip"; + case MOVE_EVENT_DEEQUIP: return "onDeEquip"; + case MOVE_EVENT_ADD_ITEM: return "onAddItem"; + case MOVE_EVENT_REMOVE_ITEM: return "onRemoveItem"; + default: + std::cout << "[Error - MoveEvent::getScriptEventName] Invalid event type" << std::endl; + return std::string(); + } +} + +bool MoveEvent::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute eventAttr = node.attribute("event"); + if (!eventAttr) { + std::cout << "[Error - MoveEvent::configureMoveEvent] Missing event" << std::endl; + return false; + } + + std::string tmpStr = asLowerCaseString(eventAttr.as_string()); + if (tmpStr == "stepin") { + eventType = MOVE_EVENT_STEP_IN; + } else if (tmpStr == "stepout") { + eventType = MOVE_EVENT_STEP_OUT; + } else if (tmpStr == "equip") { + eventType = MOVE_EVENT_EQUIP; + } else if (tmpStr == "deequip") { + eventType = MOVE_EVENT_DEEQUIP; + } else if (tmpStr == "additem") { + eventType = MOVE_EVENT_ADD_ITEM; + } else if (tmpStr == "removeitem") { + eventType = MOVE_EVENT_REMOVE_ITEM; + } else { + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() << std::endl; + return false; + } + + if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) { + pugi::xml_attribute slotAttribute = node.attribute("slot"); + if (slotAttribute) { + tmpStr = asLowerCaseString(slotAttribute.as_string()); + if (tmpStr == "head") { + slot = SLOTP_HEAD; + } else if (tmpStr == "necklace") { + slot = SLOTP_NECKLACE; + } else if (tmpStr == "backpack") { + slot = SLOTP_BACKPACK; + } else if (tmpStr == "armor") { + slot = SLOTP_ARMOR; + } else if (tmpStr == "right-hand") { + slot = SLOTP_RIGHT; + } else if (tmpStr == "left-hand") { + slot = SLOTP_LEFT; + } else if (tmpStr == "hand" || tmpStr == "shield") { + slot = SLOTP_RIGHT | SLOTP_LEFT; + } else if (tmpStr == "legs") { + slot = SLOTP_LEGS; + } else if (tmpStr == "feet") { + slot = SLOTP_FEET; + } else if (tmpStr == "ring") { + slot = SLOTP_RING; + } else if (tmpStr == "ammo") { + slot = SLOTP_AMMO; + } else { + std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << slotAttribute.as_string() << std::endl; + } + } + + wieldInfo = 0; + + pugi::xml_attribute levelAttribute = node.attribute("level"); + if (levelAttribute) { + reqLevel = pugi::cast(levelAttribute.value()); + if (reqLevel > 0) { + wieldInfo |= WIELDINFO_LEVEL; + } + } + + pugi::xml_attribute magLevelAttribute = node.attribute("maglevel"); + if (magLevelAttribute) { + reqMagLevel = pugi::cast(magLevelAttribute.value()); + if (reqMagLevel > 0) { + wieldInfo |= WIELDINFO_MAGLV; + } + } + + pugi::xml_attribute premiumAttribute = node.attribute("premium"); + if (premiumAttribute) { + premium = premiumAttribute.as_bool(); + if (premium) { + wieldInfo |= WIELDINFO_PREMIUM; + } + } + + //Gather vocation information + std::list vocStringList; + for (auto vocationNode : node.children()) { + pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name"); + if (!vocationNameAttribute) { + continue; + } + + int32_t vocationId = g_vocations.getVocationId(vocationNameAttribute.as_string()); + if (vocationId != -1) { + vocEquipMap[vocationId] = true; + if (vocationNode.attribute("showInDescription").as_bool(true)) { + vocStringList.push_back(asLowerCaseString(vocationNameAttribute.as_string())); + } + } + } + + if (!vocEquipMap.empty()) { + wieldInfo |= WIELDINFO_VOCREQ; + } + + for (const std::string& str : vocStringList) { + if (!vocationString.empty()) { + if (str != vocStringList.back()) { + vocationString.push_back(','); + vocationString.push_back(' '); + } else { + vocationString += " and "; + } + } + + vocationString += str; + vocationString.push_back('s'); + } + } + return true; +} + +bool MoveEvent::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "onstepinfield") == 0) { + stepFunction = StepInField; + } else if (strcasecmp(functionName, "onstepoutfield") == 0) { + stepFunction = StepOutField; + } else if (strcasecmp(functionName, "onaddfield") == 0) { + moveFunction = AddItemField; + } else if (strcasecmp(functionName, "onremovefield") == 0) { + moveFunction = RemoveItemField; + } else if (strcasecmp(functionName, "onequipitem") == 0) { + equipFunction = EquipItem; + } else if (strcasecmp(functionName, "ondeequipitem") == 0) { + equipFunction = DeEquipItem; + } else { + std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +MoveEvent_t MoveEvent::getEventType() const +{ + return eventType; +} + +void MoveEvent::setEventType(MoveEvent_t type) +{ + eventType = type; +} + +uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position&) +{ + MagicField* field = item->getMagicField(); + if (field) { + field->onStepInField(creature); + return 1; + } + + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&) +{ + return 1; +} + +uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&) +{ + if (MagicField* field = item->getMagicField()) { + Tile* tile = item->getTile(); + if (CreatureVector* creatures = tile->getCreatures()) { + for (Creature* creature : *creatures) { + field->onStepInField(creature); + } + } + return 1; + } + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&) +{ + return 1; +} + +uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck) +{ + if (player->isItemAbilityEnabled(slot)) { + return 1; + } + + if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) { + if (player->getLevel() < moveEvent->getReqLevel() || player->getMagicLevel() < moveEvent->getReqMagLv()) { + return 0; + } + + if (moveEvent->isPremium() && !player->isPremium()) { + return 0; + } + + const VocEquipMap& vocEquipMap = moveEvent->getVocEquipMap(); + if (!vocEquipMap.empty() && vocEquipMap.find(player->getVocationId()) == vocEquipMap.end()) { + return 0; + } + } + + if (isCheck) { + return 1; + } + + const ItemType& it = Item::items[item->getID()]; + if (it.transformEquipTo != 0) { + Item* newItem = g_game.transformItem(item, it.transformEquipTo); + g_game.startDecay(newItem); + } else { + player->setItemAbility(slot, true); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_INVISIBLE, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->manaShield) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_MANASHIELD, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->addConditionSuppressions(it.abilities->conditionSuppressions); + player->sendIcons(); + } + + if (it.abilities->absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] == 100) { + player->removeCondition(CONDITION_DROWN); + } + + if (it.abilities->regeneration) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); + + if (it.abilities->healthGain != 0) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain); + } + + if (it.abilities->healthTicks != 0) { + condition->setParam(CONDITION_PARAM_HEALTHTICKS, it.abilities->healthTicks); + } + + if (it.abilities->manaGain != 0) { + condition->setParam(CONDITION_PARAM_MANAGAIN, it.abilities->manaGain); + } + + if (it.abilities->manaTicks != 0) { + condition->setParam(CONDITION_PARAM_MANATICKS, it.abilities->manaTicks); + } + + player->addCondition(condition); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t slot, bool) +{ + if (!player->isItemAbilityEnabled(slot)) { + return 1; + } + + player->setItemAbility(slot, false); + + const ItemType& it = Item::items[item->getID()]; + if (it.transformDeEquipTo != 0) { + g_game.transformItem(item, it.transformDeEquipTo); + g_game.startDecay(item); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + player->removeCondition(CONDITION_INVISIBLE, static_cast(slot)); + } + + if (it.abilities->manaShield) { + player->removeCondition(CONDITION_MANASHIELD, static_cast(slot)); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, -it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->removeConditionSuppressions(it.abilities->conditionSuppressions); + player->sendIcons(); + } + + if (it.abilities->regeneration) { + player->removeCondition(CONDITION_REGENERATION, static_cast(slot)); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i] != 0) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), -it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), -it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), -static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) +{ + if (scripted) { + return executeStep(creature, item, pos); + } else { + return stepFunction(creature, item, pos); + } +} + +bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) +{ + //onStepIn(creature, item, pos, fromPosition) + //onStepOut(creature, item, pos, fromPosition) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeStep] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushPosition(L, pos); + LuaScriptInterface::pushPosition(L, creature->getLastPosition()); + + return scriptInterface->callFunction(4); +} + +uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean) +{ + if (scripted) { + return executeEquip(player, item, slot); + } else { + return equipFunction(this, player, item, slot, boolean); + } +} + +bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) +{ + //onEquip(player, item, slot) + //onDeEquip(player, item, slot) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + LuaScriptInterface::pushThing(L, item); + lua_pushnumber(L, slot); + + return scriptInterface->callFunction(3); +} + +uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + if (scripted) { + return executeAddRemItem(item, tileItem, pos); + } else { + return moveFunction(item, tileItem, pos); + } +} + +bool MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + //onaddItem(moveitem, tileitem, pos) + //onRemoveItem(moveitem, tileitem, pos) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeAddRemItem] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushThing(L, tileItem); + LuaScriptInterface::pushPosition(L, pos); + + return scriptInterface->callFunction(3); +} diff --git a/app/SabrehavenServer/src/movement.h b/app/SabrehavenServer/src/movement.h new file mode 100644 index 0000000..d8a67e4 --- /dev/null +++ b/app/SabrehavenServer/src/movement.h @@ -0,0 +1,168 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 +#define FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 + +#include "baseevents.h" +#include "item.h" +#include "luascript.h" + +enum MoveEvent_t { + MOVE_EVENT_STEP_IN, + MOVE_EVENT_STEP_OUT, + MOVE_EVENT_EQUIP, + MOVE_EVENT_DEEQUIP, + MOVE_EVENT_ADD_ITEM, + MOVE_EVENT_REMOVE_ITEM, + MOVE_EVENT_ADD_ITEM_ITEMTILE, + MOVE_EVENT_REMOVE_ITEM_ITEMTILE, + + MOVE_EVENT_LAST, + MOVE_EVENT_NONE +}; + +class MoveEvent; + +struct MoveEventList { + std::list moveEvent[MOVE_EVENT_LAST]; +}; + +typedef std::map VocEquipMap; + +class MoveEvents final : public BaseEvents +{ + public: + MoveEvents(); + ~MoveEvents(); + + // non-copyable + MoveEvents(const MoveEvents&) = delete; + MoveEvents& operator=(const MoveEvents&) = delete; + + uint32_t onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType); + uint32_t onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck); + uint32_t onPlayerDeEquip(Player* player, Item* item, slots_t slot); + uint32_t onItemMove(Item* item, Tile* tile, bool isAdd); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType); + + protected: + typedef std::map MoveListMap; + void clearMap(MoveListMap& map); + + typedef std::map MovePosListMap; + void clear() final; + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map); + + void addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map); + MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot); + + + MoveListMap movementIdMap; + MoveListMap itemIdMap; + MovePosListMap positionMap; + + LuaScriptInterface scriptInterface; +}; + +typedef uint32_t (StepFunction)(Creature* creature, Item* item, const Position& pos); +typedef uint32_t (MoveFunction)(Item* item, Item* tileItem, const Position& pos); +typedef uint32_t (EquipFunction)(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); + +class MoveEvent final : public Event +{ + public: + explicit MoveEvent(LuaScriptInterface* interface); + explicit MoveEvent(const MoveEvent* copy); + + MoveEvent_t getEventType() const; + void setEventType(MoveEvent_t type); + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos); + uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos); + uint32_t fireEquip(Player* player, Item* item, slots_t slot, bool boolean); + + uint32_t getSlot() const { + return slot; + } + + //scripting + bool executeStep(Creature* creature, Item* item, const Position& pos); + bool executeEquip(Player* player, Item* item, slots_t slot); + bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); + // + + //onEquip information + uint32_t getReqLevel() const { + return reqLevel; + } + uint32_t getReqMagLv() const { + return reqMagLevel; + } + bool isPremium() const { + return premium; + } + const std::string& getVocationString() const { + return vocationString; + } + uint32_t getWieldInfo() const { + return wieldInfo; + } + const VocEquipMap& getVocEquipMap() const { + return vocEquipMap; + } + + protected: + std::string getScriptEventName() const final; + + static StepFunction StepInField; + static StepFunction StepOutField; + + static MoveFunction AddItemField; + static MoveFunction RemoveItemField; + static EquipFunction EquipItem; + static EquipFunction DeEquipItem; + + MoveEvent_t eventType = MOVE_EVENT_NONE; + StepFunction* stepFunction = nullptr; + MoveFunction* moveFunction = nullptr; + EquipFunction* equipFunction = nullptr; + uint32_t slot = SLOTP_WHEREEVER; + + //onEquip information + uint32_t reqLevel = 0; + uint32_t reqMagLevel = 0; + bool premium = false; + std::string vocationString; + uint32_t wieldInfo = 0; + VocEquipMap vocEquipMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/networkmessage.cpp b/app/SabrehavenServer/src/networkmessage.cpp new file mode 100644 index 0000000..e503be0 --- /dev/null +++ b/app/SabrehavenServer/src/networkmessage.cpp @@ -0,0 +1,144 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "networkmessage.h" + +#include "container.h" +#include "creature.h" + +std::string NetworkMessage::getString(uint16_t stringLen/* = 0*/) +{ + if (stringLen == 0) { + stringLen = get(); + } + + if (!canRead(stringLen)) { + return std::string(); + } + + char* v = reinterpret_cast(buffer) + info.position; //does not break strict aliasing + info.position += stringLen; + return std::string(v, stringLen); +} + +Position NetworkMessage::getPosition() +{ + Position pos; + pos.x = get(); + pos.y = get(); + pos.z = getByte(); + return pos; +} + +void NetworkMessage::addString(const std::string& value) +{ + size_t stringLen = value.length(); + if (!canAdd(stringLen + 2) || stringLen > 8192) { + return; + } + + add(stringLen); + memcpy(buffer + info.position, value.c_str(), stringLen); + info.position += stringLen; + info.length += stringLen; +} + +void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/) +{ + addByte(precision); + add((value * std::pow(static_cast(10), precision)) + std::numeric_limits::max()); +} + +void NetworkMessage::addBytes(const char* bytes, size_t size) +{ + if (!canAdd(size) || size > 8192) { + return; + } + + memcpy(buffer + info.position, bytes, size); + info.position += size; + info.length += size; +} + +void NetworkMessage::addPaddingBytes(size_t n) +{ + if (!canAdd(n)) { + return; + } + + memset(buffer + info.position, 0x33, n); + info.length += n; +} + +void NetworkMessage::addPosition(const Position& pos) +{ + add(pos.x); + add(pos.y); + addByte(pos.z); +} + +void NetworkMessage::addItem(uint16_t id, uint8_t count, bool textWindow /* = false*/) +{ + const ItemType& it = Item::items[id]; + + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } + + if (!textWindow) { + if (it.stackable || it.isRune()) { + addByte(count); + } + else if (it.isSplash() || it.isFluidContainer()) { + addByte(getLiquidColor(count)); + } + } +} + +void NetworkMessage::addItem(const Item* item, bool textWindow /* = false*/) +{ + const ItemType& it = Item::items[item->getID()]; + + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } + + if (!textWindow) { + if (it.stackable) { + addByte(std::min(0xFF, item->getItemCount())); + } + else if (it.isRune()) { + addByte(std::min(0xFF, item->getCharges())); + } + else if (it.isSplash() || it.isFluidContainer()) { + addByte(getLiquidColor(item->getFluidType())); + } + } +} + +void NetworkMessage::addItemId(uint16_t itemId) +{ + add(itemId); +} diff --git a/app/SabrehavenServer/src/networkmessage.h b/app/SabrehavenServer/src/networkmessage.h new file mode 100644 index 0000000..9ced1bb --- /dev/null +++ b/app/SabrehavenServer/src/networkmessage.h @@ -0,0 +1,173 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 +#define FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 + +#include "const.h" + +class Item; +class Creature; +class Player; +struct Position; +class RSA; + +class NetworkMessage +{ + public: + typedef uint16_t MsgSize_t; + // Headers: + // 2 bytes for unencrypted message size + // 2 bytes for encrypted message size + static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 4; + enum { HEADER_LENGTH = 2 }; + enum { XTEA_MULTIPLE = 8 }; + enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - XTEA_MULTIPLE }; + enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 }; + + NetworkMessage() = default; + + void reset() { + info = {}; + } + + // simply read functions for incoming message + uint8_t getByte() { + if (!canRead(1)) { + return 0; + } + + return buffer[info.position++]; + } + + uint8_t getPreviousByte() { + return buffer[--info.position]; + } + + template + T get() { + if (!canRead(sizeof(T))) { + return 0; + } + + T v; + memcpy(&v, buffer + info.position, sizeof(T)); + info.position += sizeof(T); + return v; + } + + std::string getString(uint16_t stringLen = 0); + Position getPosition(); + + // skips count unknown/unused bytes in an incoming message + void skipBytes(int16_t count) { + info.position += count; + } + + // simply write functions for outgoing message + void addByte(uint8_t value) { + if (!canAdd(1)) { + return; + } + + buffer[info.position++] = value; + info.length++; + } + + template + void add(T value) { + if (!canAdd(sizeof(T))) { + return; + } + + memcpy(buffer + info.position, &value, sizeof(T)); + info.position += sizeof(T); + info.length += sizeof(T); + } + + void addBytes(const char* bytes, size_t size); + void addPaddingBytes(size_t n); + + void addString(const std::string& value); + + void addDouble(double value, uint8_t precision = 2); + + // write functions for complex types + void addPosition(const Position& pos); + void addItem(uint16_t id, uint8_t count, bool textWindow = false); + void addItem(const Item* item, bool textWindow = false); + void addItemId(uint16_t itemId); + + MsgSize_t getLength() const { + return info.length; + } + + void setLength(MsgSize_t newLength) { + info.length = newLength; + } + + MsgSize_t getBufferPosition() const { + return info.position; + } + + uint16_t getLengthHeader() const { + return static_cast(buffer[0] | buffer[1] << 8); + } + + bool isOverrun() const { + return info.overrun; + } + + uint8_t* getBuffer() { + return buffer; + } + + const uint8_t* getBuffer() const { + return buffer; + } + + uint8_t* getBodyBuffer() { + info.position = 2; + return buffer + HEADER_LENGTH; + } + + protected: + inline bool canAdd(size_t size) const { + return (size + info.position) < MAX_BODY_LENGTH; + } + + inline bool canRead(int32_t size) { + if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { + info.overrun = true; + return false; + } + return true; + } + + struct NetworkMessageInfo { + MsgSize_t length = 0; + MsgSize_t position = INITIAL_BUFFER_POSITION; + bool overrun = false; + }; + + NetworkMessageInfo info; + uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; +}; + +#endif // #ifndef __NETWORK_MESSAGE_H__ diff --git a/app/SabrehavenServer/src/npc.cpp b/app/SabrehavenServer/src/npc.cpp new file mode 100644 index 0000000..f058b92 --- /dev/null +++ b/app/SabrehavenServer/src/npc.cpp @@ -0,0 +1,487 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "npc.h" +#include "game.h" +#include "tools.h" +#include "position.h" +#include "player.h" +#include "spawn.h" +#include "script.h" +#include "behaviourdatabase.h" + +extern Game g_game; + +uint32_t Npc::npcAutoID = 0x80000000; + +void Npcs::loadNpcs() +{ + std::cout << ">> Loading npcs..." << std::endl; + + std::vector files; + getFilesInDirectory("data/npc/", ".npc", files); + for (auto file : files) + { + std::string npcName = file.filename().string(); + int32_t end = npcName.find_first_of('/'); + npcName = npcName.substr(end + 1, npcName.length() - end); + end = npcName.find_first_of('.'); + npcName = npcName.substr(0, end); + + Npc* npc = Npc::createNpc(npcName); + if (!npc) { + return; + } + + if (npc->getClientVersion() <= g_game.getClientVersion()) { + g_game.placeCreature(npc, npc->getMasterPos(), false, true); + } + } +} + +void Npcs::reload() +{ + const std::map& npcs = g_game.getNpcs(); + + for (const auto& it : npcs) { + it.second->reload(); + } +} + +Npc* Npc::createNpc(const std::string& name) +{ + std::unique_ptr npc(new Npc(name)); + npc->filename = "data/npc/" + name + ".npc"; + if (!npc->load()) { + return nullptr; + } + + return npc.release(); +} + +Npc::Npc(const std::string& name) : + Creature(), + filename("data/npc/" + name + ".npc"), + masterRadius(0), + staticMovementTime(0), + loaded(false), + behaviourDatabase(nullptr) +{ + baseSpeed = 5; + reset(); +} + +Npc::~Npc() +{ + reset(); +} + +void Npc::addList() +{ + g_game.addNpc(this); +} + +void Npc::removeList() +{ + g_game.removeNpc(this); +} + +bool Npc::load() +{ + if (loaded) { + return true; + } + + reset(); + + ScriptReader script; + if (!script.open(filename)) { + return false; + } + + while (true) { + script.nextToken(); + + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token != IDENTIFIER) { + script.error("identifier expected"); + return false; + } + + std::string ident = script.getIdentifier(); + script.readSymbol('='); + + if (ident == "name") { + name = script.readString(); + } else if (ident == "outfit") { + script.readSymbol('('); + uint8_t* c; + currentOutfit.lookType = script.readNumber(); + script.readSymbol(','); + if (currentOutfit.lookType > 0) { + c = script.readBytesequence(); + currentOutfit.lookHead = c[0]; + currentOutfit.lookBody = c[1]; + currentOutfit.lookLegs = c[2]; + currentOutfit.lookFeet = c[3]; + currentOutfit.lookAddons = c[4]; + } else { + currentOutfit.lookTypeEx = script.readNumber(); + } + script.readSymbol(')'); + } else if (ident == "home") { + script.readCoordinate(masterPos.x, masterPos.y, masterPos.z); + } else if (ident == "radius") { + masterRadius = script.readNumber(); + } else if (ident == "clientversion") { + clientVersion = script.readNumber(); + } else if (ident == "behaviour") { + if (behaviourDatabase) { + script.error("behaviour database already defined"); + return false; + } + + behaviourDatabase = new BehaviourDatabase(this); + if (!behaviourDatabase->loadDatabase(script)) { + return false; + } + } + } + + script.close(); + return true; +} + +void Npc::reset() +{ + loaded = false; + focusCreature = 0; + conversationEndTime = 0; + + if (behaviourDatabase) { + delete behaviourDatabase; + behaviourDatabase = nullptr; + } +} + +void Npc::reload() +{ + loaded = false; + + reset(); + load(); + + if (baseSpeed > 0) { + addEventWalk(); + } +} + +bool Npc::canSee(const Position& pos) const +{ + if (pos.z != getPosition().z) { + return false; + } + return Creature::canSee(getPosition(), pos, 3, 3); +} + +std::string Npc::getDescription(int32_t) const +{ + std::string descr; + descr.reserve(name.length() + 1); + descr.assign(name); + descr.push_back('.'); + return descr; +} + +void Npc::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (creature == this) { + if (baseSpeed > 0) { + addEventWalk(); + } + } else if (Player* player = creature->getPlayer()) { + spectators.insert(player); + updateIdleStatus(); + } +} + +void Npc::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (!behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player) { + if (player->getID() == focusCreature) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + + spectators.erase(player); + updateIdleStatus(); + } +} + +void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (!behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player && player->getID() == focusCreature) { + if (!Position::areInRange<3, 3, 0>(creature->getPosition(), getPosition())) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + } + + if (creature != this) { + if (player) { + if (player->canSee(position)) { + spectators.insert(player); + } else { + spectators.erase(player); + } + + updateIdleStatus(); + } + } +} + +void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) +{ + if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player) { + if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) { + return; + } + + lastTalkCreature = creature->getID(); + + if (focusCreature == 0) { + behaviourDatabase->react(SITUATION_ADDRESS, player, text); + } else if (focusCreature != player->getID()) { + behaviourDatabase->react(SITUATION_BUSY, player, text); + } else if (focusCreature == player->getID()) { + behaviourDatabase->react(SITUATION_NONE, player, text); + } + } +} + +void Npc::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) { + addEventWalk(); + } + + if (!behaviourDatabase) { + return; + } + + if (focusCreature) { + Player* player = g_game.getPlayerByID(focusCreature); + if (player) { + turnToCreature(player); + + if (conversationEndTime != 0 && OTSYS_TIME() > conversationEndTime) { + if (player) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + } + } + } +} + +void Npc::doSay(const std::string& text) +{ + if (lastTalkCreature == focusCreature) { + conversationEndTime = OTSYS_TIME() + 60000; + } + + g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false); +} + +bool Npc::getNextStep(Direction& dir, uint32_t& flags) +{ + if (Creature::getNextStep(dir, flags)) { + return true; + } + + if (baseSpeed <= 0) { + return false; + } + + if (focusCreature != 0) { + return false; + } + + if (OTSYS_TIME() < staticMovementTime) { + return false; + } + + if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) { + return false; + } + + return getRandomStep(dir); +} + +void Npc::setIdle(bool idle) +{ + if (isRemoved() || getHealth() <= 0) { + return; + } + + isIdle = idle; + + if (isIdle) { + onIdleStatus(); + } +} + +void Npc::updateIdleStatus() +{ + bool status = spectators.empty(); + if (status != isIdle) { + setIdle(status); + } +} + +bool Npc::canWalkTo(const Position& fromPos, Direction dir) const +{ + if (masterRadius == 0) { + return false; + } + + Position toPos = getNextPosition(dir, fromPos); + if (!Spawns::isInZone(masterPos, masterRadius, toPos)) { + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile || tile->queryAdd(0, *this, 1, 0) != RETURNVALUE_NOERROR) { + return false; + } + + if (tile->hasFlag(TILESTATE_BLOCKPATH)) { + return false; + } + + if (tile->hasHeight(1)) { + return false; + } + + return true; +} + +bool Npc::getRandomStep(Direction& dir) const +{ + std::vector dirList; + const Position& creaturePos = getPosition(); + + if (canWalkTo(creaturePos, DIRECTION_NORTH)) { + dirList.push_back(DIRECTION_NORTH); + } + + if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { + dirList.push_back(DIRECTION_SOUTH); + } + + if (canWalkTo(creaturePos, DIRECTION_EAST)) { + dirList.push_back(DIRECTION_EAST); + } + + if (canWalkTo(creaturePos, DIRECTION_WEST)) { + dirList.push_back(DIRECTION_WEST); + } + + if (dirList.empty()) { + return false; + } + + dir = dirList[uniform_random(0, dirList.size() - 1)]; + return true; +} + +void Npc::doMoveTo(const Position& target) +{ + std::forward_list listDir; + if (getPathTo(target, listDir, 1, 1, true, true)) { + startAutoWalk(listDir); + } +} + +void Npc::turnToCreature(Creature* creature) +{ + const Position& creaturePos = creature->getPosition(); + const Position& myPos = getPosition(); + const auto dx = Position::getOffsetX(myPos, creaturePos); + const auto dy = Position::getOffsetY(myPos, creaturePos); + + float tan; + if (dx != 0) { + tan = static_cast(dy) / dx; + } else { + tan = 10; + } + + Direction dir; + if (std::abs(tan) < 1) { + if (dx > 0) { + dir = DIRECTION_WEST; + } else { + dir = DIRECTION_EAST; + } + } else { + if (dy > 0) { + dir = DIRECTION_NORTH; + } else { + dir = DIRECTION_SOUTH; + } + } + g_game.internalCreatureTurn(this, dir); +} + +void Npc::setCreatureFocus(Creature* creature) +{ + if (creature) { + focusCreature = creature->getID(); + turnToCreature(creature); + } else { + focusCreature = 0; + } +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/npc.h b/app/SabrehavenServer/src/npc.h new file mode 100644 index 0000000..d3e91c9 --- /dev/null +++ b/app/SabrehavenServer/src/npc.h @@ -0,0 +1,162 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_NPC_H_B090D0CB549D4435AFA03647195D156F +#define FS_NPC_H_B090D0CB549D4435AFA03647195D156F + +#include "creature.h" + +#include + +class Npc; +class Player; +class BehaviourDatabase; + +class Npcs +{ + public: + static void loadNpcs(); + static void reload(); +}; + +class Npc final : public Creature +{ + public: + ~Npc(); + + // non-copyable + Npc(const Npc&) = delete; + Npc& operator=(const Npc&) = delete; + + Npc* getNpc() final { + return this; + } + const Npc* getNpc() const final { + return this; + } + + bool isPushable() const final { + return baseSpeed > 0; + } + + void setID() final { + if (id == 0) { + id = npcAutoID++; + } + } + + void removeList() final; + void addList() final; + + static Npc* createNpc(const std::string& name); + + bool canSee(const Position& pos) const final; + + bool load(); + void reload(); + + const std::string& getName() const final { + return name; + } + const std::string& getNameDescription() const final { + return name; + } + + void doSay(const std::string& text); + + void doMoveTo(const Position& pos); + + int32_t getMasterRadius() const { + return masterRadius; + } + int32_t getClientVersion() const { + return clientVersion; + } + const Position& getMasterPos() const { + return masterPos; + } + void setMasterPos(Position pos, int32_t radius = 1) { + masterPos = pos; + if (masterRadius == 0) { + masterRadius = radius; + } + } + + void turnToCreature(Creature* creature); + void setCreatureFocus(Creature* creature); + + static uint32_t npcAutoID; + + protected: + explicit Npc(const std::string& name); + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) final; + + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final; + void onThink(uint32_t interval) final; + std::string getDescription(int32_t lookDistance) const final; + + bool isImmune(CombatType_t) const final { + return true; + } + bool isImmune(ConditionType_t) const final { + return true; + } + bool isAttackable() const final { + return false; + } + bool getNextStep(Direction& dir, uint32_t& flags) final; + + void setIdle(bool idle); + void updateIdleStatus(); + + bool canWalkTo(const Position& fromPos, Direction dir) const; + bool getRandomStep(Direction& dir) const; + + void reset(); + + std::set spectators; + + std::string name; + std::string filename; + + Position masterPos; + + uint32_t lastTalkCreature; + uint32_t focusCreature; + uint32_t masterRadius; + uint16_t clientVersion = 0; + + int64_t conversationStartTime; + int64_t conversationEndTime; + int64_t staticMovementTime; + + bool loaded; + bool isIdle; + + BehaviourDatabase* behaviourDatabase; + + friend class Npcs; + friend class BehaviourDatabase; +}; + +#endif diff --git a/app/SabrehavenServer/src/otpch.cpp b/app/SabrehavenServer/src/otpch.cpp new file mode 100644 index 0000000..5db3123 --- /dev/null +++ b/app/SabrehavenServer/src/otpch.cpp @@ -0,0 +1,20 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" \ No newline at end of file diff --git a/app/SabrehavenServer/src/otpch.h b/app/SabrehavenServer/src/otpch.h new file mode 100644 index 0000000..a2ff7c1 --- /dev/null +++ b/app/SabrehavenServer/src/otpch.h @@ -0,0 +1,44 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define FS_OTPCH_H_F00C737DA6CA4C8D90F57430C614367F + +// Definitions should be global. +#include "definitions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include diff --git a/app/SabrehavenServer/src/otserv.cpp b/app/SabrehavenServer/src/otserv.cpp new file mode 100644 index 0000000..159c418 --- /dev/null +++ b/app/SabrehavenServer/src/otserv.cpp @@ -0,0 +1,316 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "server.h" + +#include "game.h" + +#ifndef _WIN32 +#include // for sigemptyset() +#endif + +#include "configmanager.h" +#include "scriptmanager.h" +#include "rsa.h" +#include "protocollogin.h" +#include "protocolstatus.h" +#include "databasemanager.h" +#include "scheduler.h" +#include "databasetasks.h" + +DatabaseTasks g_databaseTasks; +Dispatcher g_dispatcher; +Scheduler g_scheduler; + +Game g_game; +ConfigManager g_config; +Monsters g_monsters; +Vocations g_vocations; +RSA g_RSA; + +std::mutex g_loaderLock; +std::condition_variable g_loaderSignal; +std::unique_lock g_loaderUniqueLock(g_loaderLock); + +void startupErrorMessage(const std::string& errorStr) +{ + std::cout << "> ERROR: " << errorStr << std::endl; + g_loaderSignal.notify_all(); +} + +void mainLoader(int argc, char* argv[], ServiceManager* servicer); + +void badAllocationHandler() +{ + // Use functions that only use stack allocation + puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n"); + getchar(); + exit(-1); +} + +int main(int argc, char* argv[]) +{ + // Setup bad allocation handler + std::set_new_handler(badAllocationHandler); + +#ifndef _WIN32 + // ignore sigpipe... + struct sigaction sigh; + sigh.sa_handler = SIG_IGN; + sigh.sa_flags = 0; + sigemptyset(&sigh.sa_mask); + sigaction(SIGPIPE, &sigh, nullptr); +#endif + + ServiceManager serviceManager; + + g_dispatcher.start(); + g_scheduler.start(); + + g_dispatcher.addTask(createTask(std::bind(mainLoader, argc, argv, &serviceManager))); + + g_loaderSignal.wait(g_loaderUniqueLock); + + if (serviceManager.is_running()) { + std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl << std::endl; +#ifdef _WIN32 + SetConsoleCtrlHandler([](DWORD) -> BOOL { + g_dispatcher.addTask(createTask([]() { + g_dispatcher.addTask(createTask( + std::bind(&Game::shutdown, &g_game) + )); + g_scheduler.stop(); + g_databaseTasks.stop(); + g_dispatcher.stop(); + })); + ExitThread(0); + }, 1); +#endif + serviceManager.run(); + } else { + std::cout << ">> No services running. The server is NOT online." << std::endl; + g_scheduler.shutdown(); + g_databaseTasks.shutdown(); + g_dispatcher.shutdown(); + } + + g_scheduler.join(); + g_databaseTasks.join(); + g_dispatcher.join(); + return 0; +} + +void mainLoader(int, char*[], ServiceManager* services) +{ + //dispatcher thread + g_game.setGameState(GAME_STATE_STARTUP); + + srand(static_cast(OTSYS_TIME())); +#ifdef _WIN32 + SetConsoleTitle(STATUS_SERVER_NAME); +#endif + std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; + std::cout << "Compiled with " << BOOST_COMPILER << std::endl; + std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform "; + +#if defined(__amd64__) || defined(_M_X64) + std::cout << "x64" << std::endl; +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) + std::cout << "x86" << std::endl; +#elif defined(__arm__) + std::cout << "ARM" << std::endl; +#else + std::cout << "unknown" << std::endl; +#endif + std::cout << std::endl; + + std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; + std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl; + std::cout << std::endl; + + // read global config + std::cout << ">> Loading config" << std::endl; + if (!g_config.load()) { + startupErrorMessage("Unable to load config.lua!"); + return; + } + +#ifdef _WIN32 + const std::string& defaultPriority = g_config.getString(ConfigManager::DEFAULT_PRIORITY); + if (strcasecmp(defaultPriority.c_str(), "high") == 0) { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + } else if (strcasecmp(defaultPriority.c_str(), "above-normal") == 0) { + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + } +#endif + + //set RSA key + try { + g_RSA.loadPEM("key.pem"); + } + catch (const std::exception& e) { + startupErrorMessage(e.what()); + return; + } + + std::cout << ">> Checking client version... " << std::flush; + int32_t clientVersion = g_config.getNumber(ConfigManager::CLIENT_VERSION); + if (clientVersion == 780) { + g_game.setClientVersion(CLIENT_VERSION_780); + } + else if (clientVersion == 781) { + g_game.setClientVersion(CLIENT_VERSION_781); + } + else if (clientVersion == 790) { + g_game.setClientVersion(CLIENT_VERSION_790); + } + else if (clientVersion == 792) { + g_game.setClientVersion(CLIENT_VERSION_792); + } + else { + std::cout << std::endl; + + std::ostringstream ss; + ss << "> ERROR: Unknown client version: " << g_config.getNumber(ConfigManager::CLIENT_VERSION) << ", valid client versions are: 780, 781, 790, 792."; + startupErrorMessage(ss.str()); + return; + } + std::cout << clientVersion << std::endl; + + std::cout << ">> Establishing database connection..." << std::flush; + + Database* db = Database::getInstance(); + if (!db->connect()) { + startupErrorMessage("Failed to connect to database."); + return; + } + + std::cout << " MySQL " << Database::getClientVersion() << std::endl; + + // run database manager + std::cout << ">> Running database manager" << std::endl; + + if (!DatabaseManager::isDatabaseSetup()) { + startupErrorMessage("The database you have specified in config.lua is empty, please import the schema.sql to your database."); + return; + } + g_databaseTasks.start(); + + if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) { + std::cout << "> No tables were optimized." << std::endl; + } + + //load vocations + std::cout << ">> Loading vocations" << std::endl; + if (!g_vocations.loadFromXml()) { + startupErrorMessage("Unable to load vocations!"); + return; + } + + // load item data + std::cout << ">> Loading items" << std::endl; + if (!Item::items.loadItems()) { + startupErrorMessage("Unable to load items (SRV)!"); + return; + } + + std::cout << ">> Loading script systems" << std::endl; + if (!ScriptingManager::getInstance()->loadScriptSystems()) { + startupErrorMessage("Failed to load script systems"); + return; + } + + std::cout << ">> Loading monsters" << std::endl; + if (!g_monsters.loadFromXml()) { + startupErrorMessage("Unable to load monsters!"); + return; + } + + std::cout << ">> Loading outfits" << std::endl; + auto& outfits = Outfits::getInstance(); + if (!outfits.loadFromXml()) { + startupErrorMessage("Unable to load outfits!"); + return; + } + + std::cout << ">> Checking world type... " << std::flush; + std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE)); + if (worldType == "pvp") { + g_game.setWorldType(WORLD_TYPE_PVP); + } else if (worldType == "no-pvp") { + g_game.setWorldType(WORLD_TYPE_NO_PVP); + } else if (worldType == "pvp-enforced") { + g_game.setWorldType(WORLD_TYPE_PVP_ENFORCED); + } else { + std::cout << std::endl; + + std::ostringstream ss; + ss << "> ERROR: Unknown world type: " << g_config.getString(ConfigManager::WORLD_TYPE) << ", valid world types are: pvp, no-pvp and pvp-enforced."; + startupErrorMessage(ss.str()); + return; + } + std::cout << asUpperCaseString(worldType) << std::endl; + + std::cout << ">> Loading map" << std::endl; + if (!g_game.loadMainMap(g_config.getString(ConfigManager::MAP_NAME))) { + startupErrorMessage("Failed to load map"); + return; + } + + std::cout << ">> Initializing gamestate" << std::endl; + g_game.setGameState(GAME_STATE_INIT); + + // Game client protocols + services->add(g_config.getNumber(ConfigManager::GAME_PORT)); + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + + // OT protocols + services->add(g_config.getNumber(ConfigManager::STATUS_PORT)); + + RentPeriod_t rentPeriod; + std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + + if (strRentPeriod == "yearly") { + rentPeriod = RENTPERIOD_YEARLY; + } else if (strRentPeriod == "weekly") { + rentPeriod = RENTPERIOD_WEEKLY; + } else if (strRentPeriod == "monthly") { + rentPeriod = RENTPERIOD_MONTHLY; + } else if (strRentPeriod == "daily") { + rentPeriod = RENTPERIOD_DAILY; + } else { + rentPeriod = RENTPERIOD_NEVER; + } + + g_game.map.houses.payHouses(rentPeriod); + + std::cout << ">> Loaded all modules, server starting up..." << std::endl; + +#ifndef _WIN32 + if (getuid() == 0 || geteuid() == 0) { + std::cout << "> Warning: " << STATUS_SERVER_NAME << " has been executed as root user, please consider running it as a normal user." << std::endl; + } +#endif + + g_game.start(services); + g_game.setGameState(GAME_STATE_NORMAL); + g_loaderSignal.notify_all(); +} diff --git a/app/SabrehavenServer/src/outfit.cpp b/app/SabrehavenServer/src/outfit.cpp new file mode 100644 index 0000000..9984e9a --- /dev/null +++ b/app/SabrehavenServer/src/outfit.cpp @@ -0,0 +1,77 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "outfit.h" + +#include "pugicast.h" +#include "tools.h" + +bool Outfits::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/outfits.xml"); + if (!result) { + printXMLError("Error - Outfits::loadFromXml", "data/XML/outfits.xml", result); + return false; + } + + for (auto outfitNode : doc.child("outfits").children()) { + pugi::xml_attribute attr; + if ((attr = outfitNode.attribute("enabled")) && !attr.as_bool()) { + continue; + } + + if (!(attr = outfitNode.attribute("type"))) { + std::cout << "[Warning - Outfits::loadFromXml] Missing outfit type." << std::endl; + continue; + } + + uint16_t type = pugi::cast(attr.value()); + if (type > PLAYERSEX_LAST) { + std::cout << "[Warning - Outfits::loadFromXml] Invalid outfit type " << type << "." << std::endl; + continue; + } + + pugi::xml_attribute lookTypeAttribute = outfitNode.attribute("looktype"); + if (!lookTypeAttribute) { + std::cout << "[Warning - Outfits::loadFromXml] Missing looktype on outfit." << std::endl; + continue; + } + + outfits[type].emplace_back( + outfitNode.attribute("name").as_string(), + pugi::cast(lookTypeAttribute.value()), + outfitNode.attribute("premium").as_bool(), + outfitNode.attribute("unlocked").as_bool(true) + ); + } + return true; +} + +const Outfit* Outfits::getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const +{ + for (const Outfit& outfit : outfits[sex]) { + if (outfit.lookType == lookType) { + return &outfit; + } + } + return nullptr; +} diff --git a/app/SabrehavenServer/src/outfit.h b/app/SabrehavenServer/src/outfit.h new file mode 100644 index 0000000..50aefa6 --- /dev/null +++ b/app/SabrehavenServer/src/outfit.h @@ -0,0 +1,63 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3 +#define FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3 + +#include "enums.h" + +struct Outfit { + Outfit(std::string name, uint16_t lookType, bool premium, bool unlocked) : + name(std::move(name)), lookType(lookType), premium(premium), unlocked(unlocked) {} + + std::string name; + uint16_t lookType; + bool premium; + bool unlocked; +}; + +struct ProtocolOutfit { + ProtocolOutfit(const std::string& name, uint16_t lookType, uint8_t addons) : + name(name), lookType(lookType), addons(addons) {} + + const std::string& name; + uint16_t lookType; + uint8_t addons; +}; + +class Outfits +{ + public: + static Outfits& getInstance() { + static Outfits instance; + return instance; + } + + bool loadFromXml(); + + const Outfit* getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const; + const std::vector& getOutfits(PlayerSex_t sex) const { + return outfits[sex]; + } + + private: + std::vector outfits[PLAYERSEX_LAST + 1]; +}; + +#endif diff --git a/app/SabrehavenServer/src/outputmessage.cpp b/app/SabrehavenServer/src/outputmessage.cpp new file mode 100644 index 0000000..0d3d597 --- /dev/null +++ b/app/SabrehavenServer/src/outputmessage.cpp @@ -0,0 +1,83 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "outputmessage.h" +#include "protocol.h" +#include "lockfree.h" +#include "scheduler.h" + +extern Scheduler g_scheduler; + +const uint16_t OUTPUTMESSAGE_FREE_LIST_CAPACITY = 2048; +const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY {10}; + +class OutputMessageAllocator +{ + public: + typedef OutputMessage value_type; + template + struct rebind {typedef LockfreePoolingAllocator other;}; +}; + +void OutputMessagePool::scheduleSendAll() +{ + auto functor = std::bind(&OutputMessagePool::sendAll, this); + g_scheduler.addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), functor)); +} + +void OutputMessagePool::sendAll() +{ + //dispatcher thread + for (auto& protocol : bufferedProtocols) { + auto& msg = protocol->getCurrentBuffer(); + if (msg) { + protocol->send(std::move(msg)); + } + } + + if (!bufferedProtocols.empty()) { + scheduleSendAll(); + } +} + +void OutputMessagePool::addProtocolToAutosend(Protocol_ptr protocol) +{ + //dispatcher thread + if (bufferedProtocols.empty()) { + scheduleSendAll(); + } + bufferedProtocols.emplace_back(protocol); +} + +void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) +{ + //dispatcher thread + auto it = std::find(bufferedProtocols.begin(), bufferedProtocols.end(), protocol); + if (it != bufferedProtocols.end()) { + std::swap(*it, bufferedProtocols.back()); + bufferedProtocols.pop_back(); + } +} + +OutputMessage_ptr OutputMessagePool::getOutputMessage() +{ + return std::allocate_shared(OutputMessageAllocator()); +} diff --git a/app/SabrehavenServer/src/outputmessage.h b/app/SabrehavenServer/src/outputmessage.h new file mode 100644 index 0000000..3dde508 --- /dev/null +++ b/app/SabrehavenServer/src/outputmessage.h @@ -0,0 +1,104 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 +#define FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 + +#include "networkmessage.h" +#include "connection.h" +#include "tools.h" + +class Protocol; + +class OutputMessage : public NetworkMessage +{ +public: + OutputMessage() = default; + + // non-copyable + OutputMessage(const OutputMessage&) = delete; + OutputMessage& operator=(const OutputMessage&) = delete; + + uint8_t* getOutputBuffer() { + return buffer + outputBufferStart; + } + + void writeMessageLength() { + add_header(info.length); + } + + void addCryptoHeader() { + writeMessageLength(); + } + + inline void append(const NetworkMessage& msg) { + auto msgLen = msg.getLength(); + memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen); + info.length += msgLen; + info.position += msgLen; + } + + inline void append(const OutputMessage_ptr& msg) { + auto msgLen = msg->getLength(); + memcpy(buffer + info.position, msg->getBuffer() + 4, msgLen); + info.length += msgLen; + info.position += msgLen; + } + +protected: + template + inline void add_header(T add) { + assert(outputBufferStart >= sizeof(T)); + outputBufferStart -= sizeof(T); + memcpy(buffer + outputBufferStart, &add, sizeof(T)); + //added header size to the message size + info.length += sizeof(T); + } + + MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; +}; + +class OutputMessagePool +{ +public: + // non-copyable + OutputMessagePool(const OutputMessagePool&) = delete; + OutputMessagePool& operator=(const OutputMessagePool&) = delete; + + static OutputMessagePool& getInstance() { + static OutputMessagePool instance; + return instance; + } + + void sendAll(); + void scheduleSendAll(); + + static OutputMessage_ptr getOutputMessage(); + + void addProtocolToAutosend(Protocol_ptr protocol); + void removeProtocolFromAutosend(const Protocol_ptr& protocol); +private: + OutputMessagePool() = default; + //NOTE: A vector is used here because this container is mostly read + //and relatively rarely modified (only when a client connects/disconnects) + std::vector bufferedProtocols; +}; + + +#endif diff --git a/app/SabrehavenServer/src/party.cpp b/app/SabrehavenServer/src/party.cpp new file mode 100644 index 0000000..d9b06ba --- /dev/null +++ b/app/SabrehavenServer/src/party.cpp @@ -0,0 +1,471 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "party.h" +#include "game.h" +#include "configmanager.h" +#include "events.h" + +extern Game g_game; +extern ConfigManager g_config; +extern Events* g_events; + +Party::Party(Player* leader) : leader(leader) +{ + leader->setParty(this); +} + +void Party::disband() +{ + if (!g_events->eventPartyOnDisband(this)) { + return; + } + + + Player* currentLeader = leader; + leader = nullptr; + + currentLeader->setParty(nullptr); + currentLeader->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(currentLeader); + currentLeader->sendCreatureSkull(currentLeader); + currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); + + for (Player* invitee : inviteList) { + invitee->removePartyInvitation(this); + currentLeader->sendCreatureShield(invitee); + } + inviteList.clear(); + + for (Player* member : memberList) { + member->setParty(nullptr); + member->sendClosePrivate(CHANNEL_PARTY); + member->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); + } + + for (Player* member : memberList) { + g_game.updatePlayerShield(member); + + for (Player* otherMember : memberList) { + otherMember->sendCreatureSkull(member); + } + + member->sendCreatureSkull(currentLeader); + currentLeader->sendCreatureSkull(member); + } + + memberList.clear(); + delete this; +} + +bool Party::leaveParty(Player* player) +{ + if (!player) { + return false; + } + + if (player->getParty() != this && leader != player) { + return false; + } + + if (!g_events->eventPartyOnLeave(this, player)) { + return false; + } + + bool missingLeader = false; + if (leader == player) { + if (!memberList.empty()) { + if (memberList.size() == 1 && inviteList.empty()) { + missingLeader = true; + } else { + passPartyLeadership(memberList.front()); + } + } else { + missingLeader = true; + } + } + + //since we already passed the leadership, we remove the player from the list + auto it = std::find(memberList.begin(), memberList.end(), player); + if (it != memberList.end()) { + memberList.erase(it); + } + + player->setParty(nullptr); + player->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(player); + + for (Player* member : memberList) { + member->sendCreatureSkull(player); + player->sendPlayerPartyIcons(member); + } + + leader->sendCreatureSkull(player); + player->sendCreatureSkull(player); + player->sendPlayerPartyIcons(leader); + + player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party."); + + updateSharedExperience(); + updateVocationsList(); + + clearPlayerPoints(player); + + std::ostringstream ss; + ss << player->getName() << " has left the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); + + if (missingLeader || empty()) { + disband(); + } + + return true; +} + +bool Party::passPartyLeadership(Player* player) +{ + if (!player || leader == player || player->getParty() != this) { + return false; + } + + //Remove it before to broadcast the message correctly + auto it = std::find(memberList.begin(), memberList.end(), player); + if (it != memberList.end()) { + memberList.erase(it); + } + + std::ostringstream ss; + ss << player->getName() << " is now the leader of the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str(), true); + + Player* oldLeader = leader; + leader = player; + + memberList.insert(memberList.begin(), oldLeader); + + updateSharedExperience(); + + for (Player* member : memberList) { + member->sendCreatureShield(oldLeader); + member->sendCreatureShield(leader); + } + + for (Player* invitee : inviteList) { + invitee->sendCreatureShield(oldLeader); + invitee->sendCreatureShield(leader); + } + + leader->sendCreatureShield(oldLeader); + leader->sendCreatureShield(leader); + + player->sendTextMessage(MESSAGE_INFO_DESCR, "You are now the leader of the party."); + return true; +} + +bool Party::joinParty(Player& player) +{ + if (!g_events->eventPartyOnJoin(this, &player)) { + return false; + } + + auto it = std::find(inviteList.begin(), inviteList.end(), &player); + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + std::ostringstream ss; + ss << player.getName() << " has joined the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); + + player.setParty(this); + + g_game.updatePlayerShield(&player); + + for (Player* member : memberList) { + member->sendCreatureSkull(&player); + player.sendPlayerPartyIcons(member); + } + + player.sendCreatureSkull(&player); + leader->sendCreatureSkull(&player); + player.sendPlayerPartyIcons(leader); + + memberList.push_back(&player); + + player.removePartyInvitation(this); + updateSharedExperience(); + updateVocationsList(); + + const std::string& leaderName = leader->getName(); + ss.str(std::string()); + ss << "You have joined " << leaderName << "'" << (leaderName.back() == 's' ? "" : "s") << + " party. Open the party channel to communicate with your companions."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return true; +} + +bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) +{ + auto it = std::find(inviteList.begin(), inviteList.end(), &player); + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + leader->sendCreatureShield(&player); + player.sendCreatureShield(leader); + + if (removeFromPlayer) { + player.removePartyInvitation(this); + } + + if (empty()) { + disband(); + } + + return true; +} + +void Party::revokeInvitation(Player& player) +{ + std::ostringstream ss; + ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + ss.str(std::string()); + ss << "Invitation for " << player.getName() << " has been revoked."; + leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + removeInvite(player); +} + +bool Party::invitePlayer(Player& player) +{ + if (isPlayerInvited(&player)) { + return false; + } + + std::ostringstream ss; + ss << player.getName() << " has been invited."; + + if (memberList.empty() && inviteList.empty()) { + ss << " Open the party channel to communicate with your members. Type !share to enable/disable party experience share."; + g_game.updatePlayerShield(leader); + leader->sendCreatureSkull(leader); + } + + leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + inviteList.push_back(&player); + + leader->sendCreatureShield(&player); + player.sendCreatureShield(leader); + + player.addPartyInvitation(this); + + ss.str(std::string()); + ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return true; +} + +bool Party::isPlayerInvited(const Player* player) const +{ + return std::find(inviteList.begin(), inviteList.end(), player) != inviteList.end(); +} + +void Party::updateAllPartyIcons() +{ + for (Player* member : memberList) { + for (Player* otherMember : memberList) { + member->sendCreatureShield(otherMember); + } + + member->sendCreatureShield(leader); + leader->sendCreatureShield(member); + } + leader->sendCreatureShield(leader); +} + +void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations /*= false*/) +{ + for (Player* member : memberList) { + member->sendTextMessage(msgClass, msg); + } + + leader->sendTextMessage(msgClass, msg); + + if (sendToInvitations) { + for (Player* invitee : inviteList) { + invitee->sendTextMessage(msgClass, msg); + } + } +} + +void Party::broadcastPartyLoot(const std::string& loot) +{ + leader->sendTextMessage(MESSAGE_INFO_DESCR, loot); + + for (Player* member : memberList) { + member->sendTextMessage(MESSAGE_INFO_DESCR, loot); + } +} + +void Party::updateSharedExperience() +{ + if (sharedExpActive) { + bool result = canEnableSharedExperience(); + if (result != sharedExpEnabled) { + sharedExpEnabled = result; + updateAllPartyIcons(); + } + } +} + +void Party::updateVocationsList() +{ + std::set vocationIds; + + uint32_t vocationId = leader->getVocation()->getFromVocation(); + if (vocationId != VOCATION_NONE) { + vocationIds.insert(vocationId); + } + + for (const Player* member : memberList) { + vocationId = member->getVocation()->getFromVocation(); + if (vocationId != VOCATION_NONE) { + vocationIds.insert(vocationId); + } + } + + size_t size = vocationIds.size(); + if (size > 1) { + extraExpRate = static_cast(size * (10 + (size - 1) * 5)) / 100.f; + } else { + extraExpRate = 0.20f; + } +} + +bool Party::setSharedExperience(Player* player, bool sharedExpActive) +{ + if (!player || leader != player) { + return false; + } + + if (this->sharedExpActive == sharedExpActive) { + return true; + } + + this->sharedExpActive = sharedExpActive; + + if (sharedExpActive) { + this->sharedExpEnabled = canEnableSharedExperience(); + + if (this->sharedExpEnabled) { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience is now active."); + } else { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive."); + } + } else { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated."); + } + + updateAllPartyIcons(); + return true; +} + +void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/) +{ + uint64_t shareExperience = experience; + g_events->eventPartyOnShareExperience(this, shareExperience); + + for (Player* member : memberList) { + member->onGainSharedExperience(shareExperience, source); + } + leader->onGainSharedExperience(shareExperience, source); +} + +bool Party::canUseSharedExperience(const Player* player) const +{ + if (memberList.empty()) { + return false; + } + + uint32_t highestLevel = leader->getLevel(); + for (Player* member : memberList) { + if (member->getLevel() > highestLevel) { + highestLevel = member->getLevel(); + } + } + + uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); + if (player->getLevel() < minLevel) { + return false; + } + + if (!Position::areInRange<30, 30, 1>(leader->getPosition(), player->getPosition())) { + return false; + } + + return true; +} + +bool Party::canEnableSharedExperience() +{ + if (!canUseSharedExperience(leader)) { + return false; + } + + for (Player* member : memberList) { + if (!canUseSharedExperience(member)) { + return false; + } + } + return true; +} + +void Party::updatePlayerTicks(Player* player, uint32_t points) +{ + if (points != 0 && !player->hasFlag(PlayerFlag_NotGainInFight)) { + ticksMap[player->getID()] = OTSYS_TIME(); + updateSharedExperience(); + } +} + +void Party::clearPlayerPoints(Player* player) +{ + auto it = ticksMap.find(player->getID()); + if (it != ticksMap.end()) { + ticksMap.erase(it); + updateSharedExperience(); + } +} + +bool Party::canOpenCorpse(uint32_t ownerId) const +{ + if (Player* player = g_game.getPlayerByID(ownerId)) { + return leader->getID() == ownerId || player->getParty() == this; + } + return false; +} diff --git a/app/SabrehavenServer/src/party.h b/app/SabrehavenServer/src/party.h new file mode 100644 index 0000000..96fce94 --- /dev/null +++ b/app/SabrehavenServer/src/party.h @@ -0,0 +1,102 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 +#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 + +#include "player.h" +#include "monsters.h" + +class Player; +class Party; + +typedef std::vector PlayerVector; + +class Party +{ + public: + explicit Party(Player* leader); + + Player* getLeader() const { + return leader; + } + PlayerVector& getMembers() { + return memberList; + } + const PlayerVector& getInvitees() const { + return inviteList; + } + size_t getMemberCount() const { + return memberList.size(); + } + size_t getInvitationCount() const { + return inviteList.size(); + } + + void disband(); + bool invitePlayer(Player& player); + bool joinParty(Player& player); + void revokeInvitation(Player& player); + bool passPartyLeadership(Player* player); + bool leaveParty(Player* player); + + bool removeInvite(Player& player, bool removeFromPlayer = true); + + bool isPlayerInvited(const Player* player) const; + void updateAllPartyIcons(); + void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false); + void broadcastPartyLoot(const std::string& loot); + bool empty() const { + return memberList.empty() && inviteList.empty(); + } + bool canOpenCorpse(uint32_t ownerId) const; + + void shareExperience(uint64_t experience, Creature* source/* = nullptr*/); + bool setSharedExperience(Player* player, bool sharedExpActive); + bool isSharedExperienceActive() const { + return sharedExpActive; + } + bool isSharedExperienceEnabled() const { + return sharedExpEnabled; + } + bool canUseSharedExperience(const Player* player) const; + void updateSharedExperience(); + + void updateVocationsList(); + + void updatePlayerTicks(Player* player, uint32_t points); + void clearPlayerPoints(Player* player); + + protected: + bool canEnableSharedExperience(); + + std::map ticksMap; + + PlayerVector memberList; + PlayerVector inviteList; + + Player* leader; + + float extraExpRate = 0.20f; + + bool sharedExpActive = false; + bool sharedExpEnabled = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/player.cpp b/app/SabrehavenServer/src/player.cpp new file mode 100644 index 0000000..0d7a9c9 --- /dev/null +++ b/app/SabrehavenServer/src/player.cpp @@ -0,0 +1,4042 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "bed.h" +#include "chat.h" +#include "combat.h" +#include "configmanager.h" +#include "creatureevent.h" +#include "events.h" +#include "game.h" +#include "iologindata.h" +#include "monster.h" +#include "movement.h" +#include "scheduler.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Chat* g_chat; +extern Vocations g_vocations; +extern MoveEvents* g_moveEvents; +extern CreatureEvents* g_creatureEvents; +extern Events* g_events; + +MuteCountMap Player::muteCountMap; + +uint32_t Player::playerAutoID = 0x10000000; + +Player::Player(ProtocolGame_ptr p) : + Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) +{ +} + +Player::~Player() +{ + for (Item* item : inventory) { + if (item) { + item->setParent(nullptr); + item->decrementReferenceCounter(); + } + } + + for (const auto& it : depotLockerMap) { + it.second->decrementReferenceCounter(); + } + + setWriteItem(nullptr); + setEditHouse(nullptr); +} + +bool Player::setVocation(uint16_t vocId) +{ + Vocation* voc = g_vocations.getVocation(vocId); + if (!voc) { + return false; + } + vocation = voc; + + Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + if (condition) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); + condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); + condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); + condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + } + return true; +} + +bool Player::isPushable() const +{ + if (hasFlag(PlayerFlag_CannotBePushed)) { + return false; + } + return Creature::isPushable(); +} + +std::string Player::getDescription(int32_t lookDistance) const +{ + std::ostringstream s; + + if (lookDistance == -1) { + s << "yourself."; + + if (group->access) { + s << " You are " << group->name << '.'; + } else if (vocation->getId() != VOCATION_NONE) { + s << " You are " << vocation->getVocDescription() << '.'; + } else { + s << " You have no vocation."; + } + } else { + s << name; + if (!group->access) { + s << " (Level " << level << ')'; + } + s << '.'; + + if (sex == PLAYERSEX_FEMALE) { + s << " She"; + } else { + s << " He"; + } + + if (group->access) { + s << " is " << group->name << '.'; + } else if (vocation->getId() != VOCATION_NONE) { + s << " is " << vocation->getVocDescription() << '.'; + } else { + s << " has no vocation."; + } + } + + if (guild && guildRank) { + if (lookDistance == -1) { + s << " You are "; + } else if (sex == PLAYERSEX_FEMALE) { + s << " She is "; + } else { + s << " He is "; + } + + s << guildRank->name << " of the " << guild->getName(); + if (!guildNick.empty()) { + s << " (" << guildNick << ')'; + } + + s << "."; + } + + return s.str(); +} + +Item* Player::getInventoryItem(slots_t slot) const +{ + if (slot < CONST_SLOT_FIRST || slot > CONST_SLOT_LAST) { + return nullptr; + } + return inventory[slot]; +} + +void Player::addConditionSuppressions(uint32_t conditions) +{ + conditionSuppressions |= conditions; +} + +void Player::removeConditionSuppressions(uint32_t conditions) +{ + conditionSuppressions &= ~conditions; +} + +Item* Player::getWeapon() const +{ + Item* item = inventory[CONST_SLOT_LEFT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + return item; + } + + item = inventory[CONST_SLOT_RIGHT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + return item; + } + + return nullptr; +} + +Item* Player::getAmmunition() const +{ + return inventory[CONST_SLOT_AMMO]; +} + +int32_t Player::getArmor() const +{ + int32_t armor = 0; // base armor + + static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; + for (slots_t slot : armorSlots) { + Item* inventoryItem = inventory[slot]; + if (inventoryItem) { + armor += inventoryItem->getArmor(); + } + } + + if (armor > 1) { + armor = rand() % (armor >> 1) + (armor >> 1); + } + + return armor; +} + +void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const +{ + shield = nullptr; + weapon = nullptr; + + for (uint32_t slot = CONST_SLOT_RIGHT; slot <= CONST_SLOT_LEFT; slot++) { + Item* item = inventory[slot]; + if (!item) { + continue; + } + + switch (item->getWeaponType()) { + case WEAPON_NONE: + break; + + case WEAPON_SHIELD: { + if (!shield || item->getDefense() > shield->getDefense()) { + shield = item; + } + break; + } + + default: { // weapons that are not shields + weapon = item; + break; + } + } + } +} + +int32_t Player::getDefense() +{ + int32_t totalDefense = 5; + int32_t defenseSkill = getSkillLevel(SKILL_FIST); + + const Item* weapon; + const Item* shield; + getShieldAndWeapon(shield, weapon); + + if (weapon) { + totalDefense = weapon->getDefense() + 1; + + switch (weapon->getWeaponType()) { + case WEAPON_AXE: + defenseSkill = SKILL_AXE; + break; + case WEAPON_SWORD: + defenseSkill = SKILL_SWORD; + break; + case WEAPON_CLUB: + defenseSkill = SKILL_CLUB; + break; + case WEAPON_DISTANCE: + case WEAPON_AMMO: + defenseSkill = SKILL_DISTANCE; + break; + default: + break; + } + } + + if (shield) { + totalDefense = shield->getDefense() + 1; + defenseSkill = getSkillLevel(SKILL_SHIELD); + } + + fightMode_t attackMode = getFightMode(); + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; +} + +fightMode_t Player::getFightMode() const +{ + return fightMode; +} + +uint16_t Player::getClientIcons() const +{ + uint16_t icons = 0; + for (Condition* condition : conditions) { + if (!isSuppress(condition->getType())) { + icons |= condition->getIcons(); + } + } + + // Game client debugs with 10 or more icons + // so let's prevent that from happening. + std::bitset<20> icon_bitset(static_cast(icons)); + for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) { + if (icon_bitset[pos]) { + icon_bitset.reset(pos); + --bits_set; + } + } + return icon_bitset.to_ulong(); +} + +void Player::updateInventoryWeight() +{ + if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return; + } + + inventoryWeight = 0; + for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + const Item* item = inventory[i]; + if (item) { + inventoryWeight += item->getWeight(); + } + } +} + +void Player::addSkillAdvance(skills_t skill, uint64_t count) +{ + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + //player has reached max skill + return; + } + + g_events->eventPlayerOnGainSkillTries(this, skill, count); + if (count == 0) { + return; + } + + bool sendUpdateSkills = false; + while ((skills[skill].tries + count) >= nextReqTries) { + count -= nextReqTries - skills[skill].tries; + skills[skill].level++; + skills[skill].tries = 0; + skills[skill].percent = 0; + + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); + + sendUpdateSkills = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + count = 0; + break; + } + } + + skills[skill].tries += count; + + uint32_t newPercent; + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); + } else { + newPercent = 0; + } + + if (skills[skill].percent != newPercent) { + skills[skill].percent = newPercent; + sendUpdateSkills = true; + } + + if (sendUpdateSkills) { + sendSkills(); + } +} + +void Player::setVarStats(stats_t stat, int32_t modifier) +{ + varStats[stat] += modifier; + + switch (stat) { + case STAT_MAXHITPOINTS: { + if (getHealth() > getMaxHealth()) { + Creature::changeHealth(getMaxHealth() - getHealth()); + } else { + g_game.addCreatureHealth(this); + } + break; + } + + case STAT_MAXMANAPOINTS: { + if (getMana() > getMaxMana()) { + changeMana(getMaxMana() - getMana()); + } + break; + } + + default: { + break; + } + } +} + +int32_t Player::getDefaultStats(stats_t stat) const +{ + switch (stat) { + case STAT_MAXHITPOINTS: return healthMax; + case STAT_MAXMANAPOINTS: return manaMax; + case STAT_MAGICPOINTS: return getBaseMagicLevel(); + default: return 0; + } +} + +void Player::addContainer(uint8_t cid, Container* container) +{ + if (cid > 0xF) { + return; + } + + auto it = openContainers.find(cid); + if (it != openContainers.end()) { + OpenContainer& openContainer = it->second; + openContainer.container = container; + openContainer.index = 0; + } else { + OpenContainer openContainer; + openContainer.container = container; + openContainer.index = 0; + openContainers[cid] = openContainer; + } +} + +void Player::closeContainer(uint8_t cid) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return; + } + + openContainers.erase(it); +} + +void Player::setContainerIndex(uint8_t cid, uint16_t index) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return; + } + it->second.index = index; +} + +Container* Player::getContainerByID(uint8_t cid) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return nullptr; + } + return it->second.container; +} + +int8_t Player::getContainerID(const Container* container) const +{ + for (const auto& it : openContainers) { + if (it.second.container == container) { + return it.first; + } + } + return -1; +} + +uint16_t Player::getContainerIndex(uint8_t cid) const +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return 0; + } + return it->second.index; +} + +bool Player::canOpenCorpse(uint32_t ownerId) const +{ + return getID() == ownerId || (party && party->canOpenCorpse(ownerId)); +} + +uint16_t Player::getLookCorpse() const +{ + if (sex == PLAYERSEX_FEMALE) { + return ITEM_FEMALE_CORPSE; + } else { + return ITEM_MALE_CORPSE; + } +} + +void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) +{ + if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { + if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { + outfits.emplace_back( + value >> 16, + value & 0xFF + ); + return; + } + else { + std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl; + return; + } + } + + if (value != -1) { + int32_t oldValue; + getStorageValue(key, oldValue); + + storageMap[key] = value; + + if (!isLogin && g_game.getClientVersion() >= CLIENT_VERSION_790) { + auto currentFrameTime = g_dispatcher.getDispatcherCycle(); + if (lastQuestlogUpdate != currentFrameTime && g_game.quests.isQuestStorage(key, value, oldValue)) { + lastQuestlogUpdate = currentFrameTime; + sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated."); + } + } + } + else { + storageMap.erase(key); + } +} + +bool Player::getStorageValue(const uint32_t key, int32_t& value) const +{ + auto it = storageMap.find(key); + if (it == storageMap.end()) { + value = 0; + return false; + } + + value = it->second; + return true; +} + +bool Player::canSee(const Position& pos) const +{ + if (!client) { + return false; + } + return client->canSee(pos); +} + +bool Player::canSeeCreature(const Creature* creature) const +{ + if (creature == this) { + return true; + } + + if (creature->isInGhostMode() && !group->access) { + return false; + } + + if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) { + return false; + } + + return true; +} + +void Player::onReceiveMail(uint32_t townId) const +{ + if (isNearDepotBox(townId)) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived."); + } +} + +bool Player::isNearDepotBox(uint32_t townId) const +{ + const Position& pos = getPosition(); + for (int32_t cx = -1; cx <= 1; ++cx) { + for (int32_t cy = -1; cy <= 1; ++cy) { + Tile* tile = g_game.map.getTile(pos.x + cx, pos.y + cy, pos.z); + if (!tile) { + continue; + } + + if (DepotLocker* depotLocker = tile->getDepotLocker()) { + if (depotLocker->getDepotId() == townId) { + return true; + } + } + } + } + return false; +} + +DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate) +{ + auto it = depotLockerMap.find(depotId); + if (it != depotLockerMap.end()) { + return it->second; + } + + if (autoCreate) { + DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); + depotLocker->setDepotId(depotId); + Item* depotItem = Item::CreateItem(ITEM_DEPOT); + if (depotItem) { + depotLocker->internalAddThing(depotItem); + } + depotLockerMap[depotId] = depotLocker; + return depotLocker; + } + + return nullptr; +} + +void Player::sendCancelMessage(ReturnValue message) const +{ + sendCancelMessage(getReturnMessage(message)); +} + +void Player::sendStats() +{ + if (client) { + client->sendStats(); + } +} + +void Player::sendPing() +{ + int64_t timeNow = OTSYS_TIME(); + + bool hasLostConnection = false; + if ((timeNow - lastPing) >= 5000) { + lastPing = timeNow; + if (client) { + client->sendPing(); + } else { + hasLostConnection = true; + } + } + + int64_t noPongTime = timeNow - lastPong; + if ((hasLostConnection || noPongTime >= 7000) && attackedCreature && attackedCreature->getPlayer()) { + setAttackedCreature(nullptr); + } + + if (noPongTime >= 60000 && canLogout()) { + if (g_creatureEvents->playerLogout(this)) { + if (client) { + client->logout(true, true); + } else { + g_game.removeCreature(this, true); + } + } + } +} + +Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen) +{ + windowTextId = this->windowTextId; + maxWriteLen = this->maxWriteLen; + return writeItem; +} + +void Player::setWriteItem(Item* item, uint16_t maxWriteLen /*= 0*/) +{ + windowTextId++; + + if (writeItem) { + writeItem->decrementReferenceCounter(); + } + + if (item) { + writeItem = item; + this->maxWriteLen = maxWriteLen; + writeItem->incrementReferenceCounter(); + } else { + writeItem = nullptr; + this->maxWriteLen = 0; + } +} + +House* Player::getEditHouse(uint32_t& windowTextId, uint32_t& listId) +{ + windowTextId = this->windowTextId; + listId = this->editListId; + return editHouse; +} + +void Player::setEditHouse(House* house, uint32_t listId /*= 0*/) +{ + windowTextId++; + editHouse = house; + editListId = listId; +} + +void Player::sendHouseWindow(House* house, uint32_t listId) const +{ + if (!client) { + return; + } + + std::string text; + if (house->getAccessList(listId, text)) { + client->sendHouseWindow(windowTextId, text); + } +} + +//container +void Player::sendAddContainerItem(const Container* container, const Item* item) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + if (openContainer.index >= container->capacity()) { + item = container->getItemByIndex(openContainer.index - 1); + } + client->sendAddContainerItem(it.first, item); + } +} + +void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + if (slot < openContainer.index) { + continue; + } + + uint16_t pageEnd = openContainer.index + container->capacity(); + if (slot >= pageEnd) { + continue; + } + + client->sendUpdateContainerItem(it.first, slot, newItem); + } +} + +void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) +{ + if (!client) { + return; + } + + for (auto& it : openContainers) { + OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + uint16_t& firstIndex = openContainer.index; + if (firstIndex > 0 && firstIndex >= container->size() - 1) { + firstIndex -= container->capacity(); + sendContainer(it.first, container, false, firstIndex); + } + + client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex)); + } +} + +void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) +{ + Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); + + if (oldItem != newItem) { + onRemoveTileItem(tile, pos, oldType, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + if (tradeItem && oldItem == tradeItem) { + g_game.internalCloseTrade(this); + } + } +} + +void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) +{ + Creature::onRemoveTileItem(tile, pos, iType, item); + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (isLogin && creature == this) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item) { + item->startDecaying(); + g_moveEvents->onPlayerEquip(this, item, static_cast(slot), false); + } + } + + for (Condition* condition : storedConditionList) { + addCondition(condition); + } + storedConditionList.clear(); + + BedItem* bed = g_game.getBedBySleeper(guid); + if (bed) { + bed->wakeUp(this); + } + + std::cout << name << " has logged in." << std::endl; + + if (guild) { + guild->addMember(this); + } + + int32_t offlineTime; + if (getLastLogout() != 0) { + // Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds). + offlineTime = std::min(time(nullptr) - getLastLogout(), 86400 * 21); + } else { + offlineTime = 0; + } + + for (Condition* condition : getMuteConditions()) { + condition->setTicks(condition->getTicks() - (offlineTime * 1000)); + if (condition->getTicks() <= 0) { + removeCondition(condition); + } + } + + g_game.checkPlayersRecord(); + IOLoginData::updateOnlineStatus(guid, true); + } +} + +void Player::onAttackedCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); + } +} + +void Player::onFollowCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); + } +} + +void Player::onChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + + sendIcons(); +} + +void Player::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } else if (zone == ZONE_NOPVP) { + if (attackedCreature->getPlayer()) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + } else if (zone == ZONE_NORMAL) { + //attackedCreature can leave a pvp zone if not pzlocked + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attackedCreature->getPlayer()) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + } +} + +void Player::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (creature == this) { + if (isLogout) { + loginPosition = getPosition(); + } + + lastLogout = time(nullptr); + + if (eventWalk != 0) { + setFollowCreature(nullptr); + } + + if (tradePartner) { + g_game.internalCloseTrade(this); + } + + clearPartyInvitations(); + + if (party) { + party->leaveParty(this); + } + + g_chat->removeUserFromAllChannels(*this); + + std::cout << getName() << " has logged out." << std::endl; + + if (guild) { + guild->removeMember(this); + } + + IOLoginData::updateOnlineStatus(guid, false); + + bool saved = false; + for (uint32_t tries = 0; tries < 3; ++tries) { + if (IOLoginData::savePlayer(this)) { + saved = true; + break; + } + } + + if (!saved) { + std::cout << "Error while saving player: " << getName() << std::endl; + } + } +} + +void Player::onWalk(Direction& dir) +{ + Creature::onWalk(dir); + setNextActionTask(nullptr); + // TODO: Find out if really nothing brokes. This is for allow players to heal and walk or walk and open backpacks + //setNextAction(OTSYS_TIME() + getStepDuration(dir)); +} + +void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) { + isUpdatingPath = false; + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + } + + if (creature != this) { + return; + } + + if (tradeState != TRADE_TRANSFER) { + //check if we should close trade + if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + + if (tradePartner && !Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + } + + if (party) { + party->updateSharedExperience(); + } + + if (teleport || oldPos.z != newPos.z) { + int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY); + if (ticks > 0) { + if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) { + addCondition(condition); + } + } + } +} + +//container +void Player::onAddContainerItem(const Item* item) +{ + checkTradeState(item); +} + +void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem) +{ + if (oldItem != newItem) { + onRemoveContainerItem(container, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveContainerItem(const Container* container, const Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCloseContainer(const Container* container) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + if (it.second.container == container) { + client->sendCloseContainer(it.first); + } + } +} + +void Player::onSendContainer(const Container* container) +{ + if (!client) { + return; + } + + bool hasParent = container->hasParent(); + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container == container) { + client->sendContainer(it.first, container, hasParent, openContainer.index); + } + } +} + +//inventory +void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem) +{ + if (oldItem != newItem) { + onRemoveInventoryItem(oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveInventoryItem(Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::checkTradeState(const Item* item) +{ + if (!tradeItem || tradeState == TRADE_TRANSFER) { + return; + } + + if (tradeItem == item) { + g_game.internalCloseTrade(this); + } else { + const Container* container = dynamic_cast(item->getParent()); + while (container) { + if (container == tradeItem) { + g_game.internalCloseTrade(this); + break; + } + + container = dynamic_cast(container->getParent()); + } + } +} + +void Player::setNextWalkActionTask(SchedulerTask* task) +{ + if (walkTaskEvent != 0) { + g_scheduler.stopEvent(walkTaskEvent); + walkTaskEvent = 0; + } + + delete walkTask; + walkTask = task; +} + +void Player::setNextWalkTask(SchedulerTask* task) +{ + if (nextStepEvent != 0) { + g_scheduler.stopEvent(nextStepEvent); + nextStepEvent = 0; + } + + if (task) { + nextStepEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +void Player::setNextActionTask(SchedulerTask* task) +{ + if (actionTaskEvent != 0) { + g_scheduler.stopEvent(actionTaskEvent); + actionTaskEvent = 0; + } + + if (task) { + actionTaskEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +uint32_t Player::getNextActionTime() const +{ + return std::max(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME()); +} + +void Player::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + sendPing(); + + MessageBufferTicks += interval; + if (MessageBufferTicks >= 1500) { + MessageBufferTicks = 0; + addMessageBuffer(); + } + + lastWalkingTime += interval; + if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) { + idleTime += interval; + const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES); + if ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) { + if (!isFakePlayer) { + kickPlayer(true); + } + } else if (client && idleTime == 60000 * kickAfterMinutes) { + std::ostringstream ss; + ss << "You have been idle for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if you are still idle then."; + client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str())); + } + } + + if (isFakePlayer && idleTime > uniform_random(60000, 120000)) { + uint32_t r = uniform_random(0, 1); + Direction dir = DIRECTION_NORTH; + if (r == 0) { + dir = DIRECTION_SOUTH; + } + + g_game.internalCreatureTurn(this, dir); + resetIdleTime(); + } + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + checkSkullTicks(); + } + + addOfflineTrainingTime(interval); +} + +uint32_t Player::isMuted() const +{ + if (hasFlag(PlayerFlag_CannotBeMuted)) { + return 0; + } + + int32_t muteTicks = 0; + for (Condition* condition : conditions) { + if (condition->getType() == CONDITION_MUTED && condition->getTicks() > muteTicks) { + muteTicks = condition->getTicks(); + } + } + return static_cast(muteTicks) / 1000; +} + +void Player::addMessageBuffer() +{ + if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) { + --MessageBufferCount; + } +} + +void Player::removeMessageBuffer() +{ + if (hasFlag(PlayerFlag_CannotBeMuted)) { + return; + } + + const int32_t maxMessageBuffer = g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER); + if (maxMessageBuffer != 0 && MessageBufferCount <= maxMessageBuffer + 1) { + if (++MessageBufferCount > maxMessageBuffer) { + uint32_t muteCount = 1; + auto it = muteCountMap.find(guid); + if (it != muteCountMap.end()) { + muteCount = it->second; + } + + uint32_t muteTime = 5 * muteCount * muteCount; + muteCountMap[guid] = muteCount + 1; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0); + addCondition(condition); + + std::ostringstream ss; + ss << "You are muted for " << muteTime << " seconds."; + sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + } + } +} + +void Player::drainHealth(Creature* attacker, int32_t damage) +{ + Creature::drainHealth(attacker, damage); + sendStats(); +} + +void Player::drainMana(Creature* attacker, int32_t manaLoss) +{ + onAttacked(); + changeMana(-manaLoss); + + if (attacker) { + addDamagePoints(attacker, manaLoss); + } + + sendStats(); +} + +void Player::addManaSpent(uint64_t amount) +{ + if (hasFlag(PlayerFlag_NotGainMana)) { + return; + } + + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + if (currReqMana >= nextReqMana) { + //player has reached max magic level + return; + } + + g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, amount); + if (amount == 0) { + return; + } + + bool sendUpdateStats = false; + while ((manaSpent + amount) >= nextReqMana) { + amount -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); + + sendUpdateStats = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + if (currReqMana >= nextReqMana) { + return; + } + } + + manaSpent += amount; + + uint8_t oldPercent = magLevelPercent; + if (nextReqMana > currReqMana) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + if (oldPercent != magLevelPercent) { + sendUpdateStats = true; + } + + if (sendUpdateStats) { + sendStats(); + } +} + +void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = false*/) +{ + uint64_t currLevelExp = Player::getExpForLevel(level); + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + uint64_t rawExp = exp; + if (currLevelExp >= nextLevelExp) { + //player has reached max level + levelPercent = 0; + sendStats(); + return; + } + + g_events->eventPlayerOnGainExperience(this, source, exp, rawExp); + if (exp == 0) { + return; + } + + experience += exp; + + if (sendText) { + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp)); + } + + uint32_t prevLevel = level; + while (experience >= nextLevelExp) { + ++level; + healthMax += vocation->getHPGain(); + health += vocation->getHPGain(); + manaMax += vocation->getManaGain(); + mana += vocation->getManaGain(); + capacity += vocation->getCapGain(); + + currLevelExp = nextLevelExp; + nextLevelExp = Player::getExpForLevel(level + 1); + if (currLevelExp >= nextLevelExp) { + //player has reached max level + break; + } + } + + if (prevLevel != level) { + updateBaseSpeed(); + setBaseSpeed(getBaseSpeed()); + + g_game.changeSpeed(this, 0); + g_game.addCreatureHealth(this); + + if (party) { + party->updateSharedExperience(); + } + + g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level); + + std::ostringstream ss; + ss << "You advanced from Level " << prevLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + sendStats(); +} + +void Player::removeExperience(uint64_t exp) +{ + if (experience == 0 || exp == 0) { + return; + } + + experience = std::max(0, experience - exp); + + uint32_t oldLevel = level; + uint64_t currLevelExp = Player::getExpForLevel(level); + + while (level > 1 && experience < currLevelExp) { + --level; + healthMax = std::max(150, std::max(0, healthMax - vocation->getHPGain())); + manaMax = std::max(0, manaMax - vocation->getManaGain()); + capacity = std::max(400, std::max(0, capacity - vocation->getCapGain())); + currLevelExp = Player::getExpForLevel(level); + } + + if (oldLevel != level) { + health = healthMax; + mana = manaMax; + + updateBaseSpeed(); + setBaseSpeed(getBaseSpeed()); + + g_game.changeSpeed(this, 0); + g_game.addCreatureHealth(this); + + if (party) { + party->updateSharedExperience(); + } + + std::ostringstream ss; + ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + sendStats(); +} + +uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) +{ + if (nextLevelCount == 0) { + return 0; + } + + uint8_t result = (count * 100) / nextLevelCount; + if (result > 100) { + return 0; + } + return result; +} + +uint16_t Player::getDropLootPercent() +{ + return 10; +} + +void Player::onBlockHit() +{ + if (shieldBlockCount > 0) { + --shieldBlockCount; + + if (hasShield()) { + addSkillAdvance(SKILL_SHIELD, 1); + } + } +} + +void Player::onAttackedCreatureBlockHit(BlockType_t blockType) +{ + lastAttackBlockType = blockType; + + switch (blockType) { + case BLOCK_NONE: { + addAttackSkillPoint = true; + bloodHitCount = 30; + shieldBlockCount = 30; + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + //need to draw blood every 30 hits + if (bloodHitCount > 0) { + addAttackSkillPoint = true; + --bloodHitCount; + } else { + addAttackSkillPoint = false; + } + break; + } + + default: { + addAttackSkillPoint = false; + break; + } + } +} + +bool Player::hasShield() const +{ + Item* item = inventory[CONST_SLOT_LEFT]; + if (item && item->getWeaponType() == WEAPON_SHIELD) { + return true; + } + + item = inventory[CONST_SLOT_RIGHT]; + if (item && item->getWeaponType() == WEAPON_SHIELD) { + return true; + } + return false; +} + +BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); + + if (attacker) { + sendCreatureSquare(attacker, SQ_COLOR_BLACK); + } + + if (blockType != BLOCK_NONE) { + return blockType; + } + + if (damage > 0) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + if (!isItemAbilityEnabled(static_cast(slot))) { + continue; + } + + Item* item = inventory[slot]; + if (!item) { + continue; + } + + const ItemType& it = Item::items[item->getID()]; + if (it.abilities) { + const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + if (absorbPercent != 0) { + damage -= std::round(damage * (absorbPercent / 100.)); + + uint16_t charges = item->getCharges() - 1; + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges); + } else { + g_game.internalRemoveItem(item); + } + } + + if (field) { + const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; + if (fieldAbsorbPercent != 0) { + damage -= std::round(damage * (fieldAbsorbPercent / 100.)); + + uint16_t charges = item->getCharges(); + if (charges != 0) { + if (charges - 1 == 0) { + g_game.internalRemoveItem(item); + } else { + g_game.transformItem(item, item->getID(), charges - 1); + } + } + } + } + } + } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + + return blockType; +} + +uint32_t Player::getIP() const +{ + if (client) { + return client->getIP(); + } + + return 0; +} + +void Player::dropLoot(Container* corpse, Creature*) +{ + if (corpse && lootDrop) { + Skulls_t playerSkull = getSkull(); + if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) { + g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1); + } else { + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (item) { + if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) { + g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); + sendInventoryItem(static_cast(i), nullptr); + } + } + } + } + } + + if (g_game.getClientVersion() >= CLIENT_VERSION_790) { + if (!inventory[CONST_SLOT_BACKPACK]) { + Item* bagItem = Item::CreateItem(ITEM_BAG, 1); + if (bagItem) { + g_game.internalPlayerAddItem(this, bagItem, false, CONST_SLOT_BACKPACK); + } + } + } +} + +void Player::death(Creature* lastHitCreature) +{ + if (isFakePlayer) { + loginPosition = g_game.map.towns.getTown(10)->getTemplePosition(); // Isle of solitude + } + else { + loginPosition = town->getTemplePosition(); + } + + if (skillLoss) { + //Magic level loss + uint64_t sumMana = 0; + uint64_t lostMana = 0; + + //sum up all the mana + for (uint32_t i = 1; i <= magLevel; ++i) { + sumMana += vocation->getReqMana(i); + } + + sumMana += manaSpent; + + double deathLossPercent = getLostPercent(); + + lostMana = static_cast(sumMana * deathLossPercent); + + while (lostMana > manaSpent && magLevel > 0) { + lostMana -= manaSpent; + manaSpent = vocation->getReqMana(magLevel); + magLevel--; + } + + manaSpent -= lostMana; + + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + if (nextReqMana > vocation->getReqMana(magLevel)) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + //Skill loss + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + uint64_t sumSkillTries = 0; + for (uint16_t c = 11; c <= skills[i].level; ++c) { //sum up all required tries for all skill levels + sumSkillTries += vocation->getReqSkillTries(i, c); + } + + sumSkillTries += skills[i].tries; + + uint32_t lostSkillTries = static_cast(sumSkillTries * deathLossPercent); + while (lostSkillTries > skills[i].tries) { + lostSkillTries -= skills[i].tries; + + if (skills[i].level <= 10) { + skills[i].level = 10; + skills[i].tries = 0; + lostSkillTries = 0; + break; + } + + skills[i].tries = vocation->getReqSkillTries(i, skills[i].level); + skills[i].level--; + } + + skills[i].tries = std::max(0, skills[i].tries - lostSkillTries); + skills[i].percent = Player::getPercentLevel(skills[i].tries, vocation->getReqSkillTries(i, skills[i].level)); + } + + //Level loss + uint64_t expLoss = static_cast(experience * deathLossPercent); + g_events->eventPlayerOnLoseExperience(this, expLoss); + + if (expLoss != 0) { + uint32_t oldLevel = level; + + experience -= expLoss; + + while (level > 1 && experience < Player::getExpForLevel(level)) { + --level; + healthMax = std::max(0, healthMax - vocation->getHPGain()); + manaMax = std::max(0, manaMax - vocation->getManaGain()); + capacity = std::max(0, capacity - vocation->getCapGain()); + } + + if (oldLevel != level) { + std::ostringstream ss; + ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint64_t currLevelExp = Player::getExpForLevel(level); + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + } + + std::bitset<6> bitset(blessings); + if (bitset[5]) { + if (Player::lastHitIsPlayer(lastHitCreature)) { + bitset.reset(5); + blessings = bitset.to_ulong(); + } else { + blessings = 32; + } + } else { + blessings = 0; + } + + sendStats(); + sendSkills(); + + health = healthMax; + mana = manaMax; + + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; + } else { + ++it; + } + } + + // Teleport newbies to newbie island + if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) { + if (getVocationId() != VOCATION_NONE && level <= static_cast(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) { + Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN)); + if (newbieTown) { + // Restart stats + level = 1; + experience = 0; + levelPercent = 0; + capacity = 400; + health = 150; + healthMax = 150; + mana = 0; + manaMax = 0; + magLevel = 0; + magLevelPercent = 0; + manaSpent = 0; + staminaMinutes = 3360; + setVocation(0); + + // Restart skills + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + skills[i].level = 10; + skills[i].tries = 0; + skills[i].percent = 0; + } + + // Restart town + setTown(newbieTown); + loginPosition = getTemplePosition(); + + // Restart first items + lastLoginSaved = 0; + lastLogout = 0; + + // Restart storages + storageMap.clear(); + outfits.clear(); + + // Restart items + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++) + { + Item* item = inventory[slot]; + if (item) { + g_game.internalRemoveItem(item, item->getItemCount()); + } + } + } else { + std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl; + } + } + } + } else { + setSkillLoss(true); + + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; + } + else { + ++it; + } + } + + health = healthMax; + g_game.internalTeleport(this, getTemplePosition(), true); + g_game.addCreatureHealth(this); + onThink(EVENT_CREATURE_THINK_INTERVAL); + onIdleStatus(); + sendStats(); + } +} + +bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) { + return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + setDropLoot(true); + return false; +} + +Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) +{ + Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); + if (corpse && corpse->getContainer()) { + std::ostringstream ss; + if (lastHitCreature) { + ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature->getNameDescription() << '.'; + } else { + ss << "You recognize " << getNameDescription() << '.'; + } + + corpse->setSpecialDescription(ss.str()); + } + return corpse; +} + +void Player::addInFightTicks(bool pzlock /*= false*/) +{ + if (hasFlag(PlayerFlag_NotGainInFight)) { + return; + } + + if (pzlock) { + pzLocked = true; + } + + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0); + addCondition(condition); +} + +void Player::removeList() +{ + g_game.removePlayer(this); + + for (const auto& it : g_game.getPlayers()) { + it.second->notifyStatusChange(this, VIPSTATUS_OFFLINE); + } +} + +void Player::addList() +{ + for (const auto& it : g_game.getPlayers()) { + it.second->notifyStatusChange(this, VIPSTATUS_ONLINE); + } + + g_game.addPlayer(this); +} + +void Player::kickPlayer(bool displayEffect) +{ + g_creatureEvents->playerLogout(this); + if (client) { + client->logout(displayEffect, true); + } else { + g_game.removeCreature(this); + } +} + +void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status) +{ + if (!client) { + return; + } + + auto it = VIPList.find(loginPlayer->guid); + if (it == VIPList.end()) { + return; + } + + client->sendUpdatedVIPStatus(loginPlayer->guid, status); + + if (status == VIPSTATUS_ONLINE) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged in.")); + } else if (status == VIPSTATUS_OFFLINE) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged out.")); + } +} + +bool Player::removeVIP(uint32_t vipGuid) +{ + if (VIPList.erase(vipGuid) == 0) { + return false; + } + + IOLoginData::removeVIPEntry(accountNumber, vipGuid); + return true; +} + +bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status) +{ + if (guid == vipGuid) { + sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself."); + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies."); + return false; + } + + auto result = VIPList.insert(vipGuid); + if (!result.second) { + sendTextMessage(MESSAGE_STATUS_SMALL, "This player is already in your list."); + return false; + } + + IOLoginData::addVIPEntry(accountNumber, vipGuid); + if (client) { + client->sendVIP(vipGuid, vipName, status); + } + return true; +} + +bool Player::addVIPInternal(uint32_t vipGuid) +{ + if (guid == vipGuid) { + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + return false; + } + + return VIPList.insert(vipGuid).second; +} + +//close container and its child containers +void Player::autoCloseContainers(const Container* container) +{ + std::vector closeList; + for (const auto& it : openContainers) { + Container* tmpContainer = it.second.container; + while (tmpContainer) { + if (tmpContainer->isRemoved() || tmpContainer == container) { + closeList.push_back(it.first); + break; + } + + tmpContainer = dynamic_cast(tmpContainer->getParent()); + } + } + + for (uint32_t containerId : closeList) { + closeContainer(containerId); + if (client) { + client->sendCloseContainer(containerId); + } + } +} + +bool Player::hasCapacity(const Item* item, uint32_t count) const +{ + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return false; + } + + if (hasFlag(PlayerFlag_HasInfiniteCapacity) || item->getTopParent() == this) { + return true; + } + + uint32_t itemWeight = item->getContainer() != nullptr ? item->getWeight() : item->getBaseWeight(); + if (item->isStackable()) { + itemWeight *= count; + } + return itemWeight <= getFreeCapacity(); +} + +ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature*) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + if (childIsOwner) { + //a child container is querying the player, just check if enough capacity + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + if (skipLimit || hasCapacity(item, count)) { + return RETURNVALUE_NOERROR; + } + return RETURNVALUE_NOTENOUGHCAPACITY; + } + + if (!item->isPickupable()) { + return RETURNVALUE_CANNOTPICKUP; + } + + ReturnValue ret = RETURNVALUE_NOERROR; + + const int32_t& slotPosition = item->getSlotPosition(); + if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || + (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || + (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else if (slotPosition & SLOTP_TWO_HAND) { + ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; + } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { + ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; + } + + switch (index) { + case CONST_SLOT_HEAD: { + if (slotPosition & SLOTP_HEAD) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_NECKLACE: { + if (slotPosition & SLOTP_NECKLACE) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_BACKPACK: { + if (slotPosition & SLOTP_BACKPACK) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_ARMOR: { + if (slotPosition & SLOTP_ARMOR) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_RIGHT: { + if (slotPosition & SLOTP_RIGHT) { + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else if (inventory[CONST_SLOT_LEFT]) { + const Item* leftItem = inventory[CONST_SLOT_LEFT]; + WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType(); + + if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RETURNVALUE_DROPTWOHANDEDITEM; + } else if (item == leftItem && count == item->getItemCount()) { + ret = RETURNVALUE_NOERROR; + } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RETURNVALUE_CANONLYUSEONESHIELD; + } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || + leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_NOERROR; + } else { + ret = RETURNVALUE_CANONLYUSEONEWEAPON; + } + } else { + ret = RETURNVALUE_NOERROR; + } + } + break; + } + + case CONST_SLOT_LEFT: { + if (slotPosition & SLOTP_LEFT) { + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else if (inventory[CONST_SLOT_RIGHT]) { + const Item* rightItem = inventory[CONST_SLOT_RIGHT]; + WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); + + if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RETURNVALUE_DROPTWOHANDEDITEM; + } else if (item == rightItem && count == item->getItemCount()) { + ret = RETURNVALUE_NOERROR; + } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RETURNVALUE_CANONLYUSEONESHIELD; + } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || + rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_NOERROR; + } else { + ret = RETURNVALUE_CANONLYUSEONEWEAPON; + } + } else { + ret = RETURNVALUE_NOERROR; + } + } + break; + } + + case CONST_SLOT_LEGS: { + if (slotPosition & SLOTP_LEGS) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_FEET: { + if (slotPosition & SLOTP_FEET) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_RING: { + if (slotPosition & SLOTP_RING) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_AMMO: { + ret = RETURNVALUE_NOERROR; + break; + } + + case CONST_SLOT_WHEREEVER: + case -1: + ret = RETURNVALUE_NOTENOUGHROOM; + break; + + default: + ret = RETURNVALUE_NOTPOSSIBLE; + break; + } + + if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { + //need an exchange with source? + const Item* inventoryItem = getInventoryItem(static_cast(index)); + if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { + return RETURNVALUE_NEEDEXCHANGE; + } + + //check if enough capacity + if (!hasCapacity(item, count)) { + return RETURNVALUE_NOTENOUGHCAPACITY; + } + + if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { + return RETURNVALUE_CANNOTBEDRESSED; + } + } + return ret; +} + +ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + maxQueryCount = 0; + return RETURNVALUE_NOTPOSSIBLE; + } + + if (index == INDEX_WHEREEVER) { + uint32_t n = 0; + for (int32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + if (inventoryItem) { + if (Container* subContainer = inventoryItem->getContainer()) { + uint32_t queryCount = 0; + subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); + n += queryCount; + + //iterate through all items, including sub-containers (deep search) + for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) { + if (Container* tmpContainer = (*it)->getContainer()) { + queryCount = 0; + tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); + n += queryCount; + } + } + } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) { + uint32_t remainder = (100 - inventoryItem->getItemCount()); + + if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n += remainder; + } + } + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + if (item->isStackable()) { + n += 100; + } else { + ++n; + } + } + } + + maxQueryCount = n; + } else { + const Item* destItem = nullptr; + + const Thing* destThing = getThing(index); + if (destThing) { + destItem = destThing->getItem(); + } + + if (destItem) { + if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { + maxQueryCount = 100 - destItem->getItemCount(); + } + else { + maxQueryCount = 0; + } + } + else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot + if (item->isStackable()) { + maxQueryCount = 100; + } + else { + maxQueryCount = 1; + } + + return RETURNVALUE_NOERROR; + } + } + + if (maxQueryCount < count) { + return RETURNVALUE_NOTENOUGHROOM; + } else { + return RETURNVALUE_NOERROR; + } +} + +ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + + return RETURNVALUE_NOERROR; +} + +Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) +{ + if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { + *destItem = nullptr; + + const Item* item = thing.getItem(); + if (item == nullptr) { + return this; + } + + bool autoStack = !((flags & FLAG_IGNOREAUTOSTACK) == FLAG_IGNOREAUTOSTACK); + bool isStackable = item->isStackable(); + + std::vector containers; + + for (uint32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + if (inventoryItem) { + if (inventoryItem == tradeItem) { + continue; + } + + if (inventoryItem == item) { + continue; + } + + if (autoStack && isStackable) { + //try find an already existing item to stack with + if (queryAdd(slotIndex, *item, item->getItemCount(), 0) == RETURNVALUE_NOERROR) { + if (inventoryItem->equals(item) && inventoryItem->getItemCount() < 100) { + index = slotIndex; + *destItem = inventoryItem; + return this; + } + } + + if (Container* subContainer = inventoryItem->getContainer()) { + containers.push_back(subContainer); + } + } else if (Container* subContainer = inventoryItem->getContainer()) { + containers.push_back(subContainer); + } + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + index = slotIndex; + *destItem = nullptr; + return this; + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* tmpContainer = containers[i++]; + if (!autoStack || !isStackable) { + //we need to find first empty container as fast as we can for non-stackable items + uint32_t n = tmpContainer->capacity() - tmpContainer->size(); + while (n) { + if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + index = tmpContainer->capacity() - n; + *destItem = nullptr; + return tmpContainer; + } + + n--; + } + + if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { + for (Item* tmpContainerItem : tmpContainer->getItemList()) { + if (Container* subContainer = tmpContainerItem->getContainer()) { + containers.push_back(subContainer); + } + } + } + + continue; + } + + uint32_t n = 0; + + for (Item* tmpItem : tmpContainer->getItemList()) { + if (tmpItem == tradeItem) { + continue; + } + + if (tmpItem == item) { + continue; + } + + //try find an already existing item to stack with + if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) { + index = n; + *destItem = tmpItem; + return tmpContainer; + } + + if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { + if (Container* subContainer = tmpItem->getContainer()) { + containers.push_back(subContainer); + } + } + + n++; + } + + if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + index = n; + *destItem = nullptr; + return tmpContainer; + } + } + + return this; + } + + Thing* destThing = getThing(index); + if (destThing) { + *destItem = destThing->getItem(); + } + + Cylinder* subCylinder = dynamic_cast(destThing); + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + return subCylinder; + } else { + return this; + } +} + +void Player::addThing(int32_t index, Thing* thing) +{ + if (index < CONST_SLOT_FIRST || index > CONST_SLOT_LAST) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + inventory[index] = item; + + //send to client + sendInventoryItem(static_cast(index), item); +} + +void Player::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setID(itemId); + item->setSubType(count); + + //send to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(item, item); +} + +void Player::replaceThing(uint32_t index, Thing* thing) +{ + if (index > CONST_SLOT_LAST) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* oldItem = getInventoryItem(static_cast(index)); + if (!oldItem) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + //send to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(oldItem, item); + + item->setParent(this); + + inventory[index] = item; +} + +void Player::removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (item->isStackable()) { + if (count == item->getItemCount()) { + //send change to client + sendInventoryItem(static_cast(index), nullptr); + + //event methods + onRemoveInventoryItem(item); + + item->setParent(nullptr); + inventory[index] = nullptr; + } else { + uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); + item->setItemCount(newCount); + + //send change to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(item, item); + } + } else { + //send change to client + sendInventoryItem(static_cast(index), nullptr); + + //event methods + onRemoveInventoryItem(item); + + item->setParent(nullptr); + inventory[index] = nullptr; + } +} + +int32_t Player::getThingIndex(const Thing* thing) const +{ + for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + if (inventory[i] == thing) { + return i; + } + } + return -1; +} + +size_t Player::getFirstIndex() const +{ + return CONST_SLOT_FIRST; +} + +size_t Player::getLastIndex() const +{ + return CONST_SLOT_LAST + 1; +} + +uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (item->getID() == itemId) { + count += Item::countByType(item, subType); + } + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + count += Item::countByType(*it, subType); + } + } + } + } + return count; +} + +// 7.8 mechanics to count runes by charges requires different logic to be implemented +uint32_t Player::getRuneCount(uint16_t itemId) const +{ + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (item->getID() == itemId) { + count += item->getCharges(); + } + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + count += (*it)->getCharges(); + } + } + } + } + return count; +} + +bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const +{ + if (amount == 0) { + return true; + } + + std::vector itemList; + + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (!ignoreEquipped && item->getID() == itemId) { + uint32_t itemCount = Item::countByType(item, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(item); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + else if (Container* container = item->getContainer()) { + if (container->getID() == itemId) { + uint32_t itemCount = Item::countByType(item, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(item); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + Item* containerItem = *it; + if (containerItem->getID() == itemId) { + uint32_t itemCount = Item::countByType(containerItem, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(containerItem); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + } + } + } + return false; +} + +std::map& Player::getAllItemTypeCount(std::map& countMap) const +{ + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + countMap[item->getID()] += Item::countByType(item, -1); + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + countMap[(*it)->getID()] += Item::countByType(*it, -1); + } + } + } + return countMap; +} + +Thing* Player::getThing(size_t index) const +{ + if (index >= CONST_SLOT_FIRST && index <= CONST_SLOT_LAST) { + return inventory[index]; + } + return nullptr; +} + +void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); + } + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + onSendContainer(container); + } + } else if (const Creature* creature = thing->getCreature()) { + if (creature == this) { + //check containers + std::vector containers; + + for (const auto& it : openContainers) { + Container* container = it.second.container; + if (!Position::areInRange<1, 1, 0>(container->getPosition(), getPosition())) { + containers.push_back(container); + } + } + + for (const Container* container : containers) { + autoCloseContainers(container); + } + } + } +} + +void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); + } + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { + autoCloseContainers(container); + } else if (container->getTopParent() == this) { + onSendContainer(container); + } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { + if (const DepotLocker* depotLocker = dynamic_cast(topContainer)) { + bool isOwner = false; + + for (const auto& it : depotLockerMap) { + if (it.second == depotLocker) { + isOwner = true; + onSendContainer(container); + } + } + + if (!isOwner) { + autoCloseContainers(container); + } + } else { + onSendContainer(container); + } + } else { + autoCloseContainers(container); + } + } + } +} + +void Player::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Player::internalAddThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + if (!item) { + return; + } + + //index == 0 means we should equip this item at the most appropiate slot (no action required here) + if (index > 0 && index < 11) { + if (inventory[index]) { + return; + } + + inventory[index] = item; + item->setParent(this); + } +} + +uint32_t Player::checkPlayerKilling() +{ + time_t today = std::time(nullptr); + int32_t lastDay = 0; + int32_t lastWeek = 0; + int32_t lastMonth = 0; + int64_t egibleMurders = 0; + + time_t dayTimestamp = today - (24 * 60 * 60); + time_t weekTimestamp = today - (7 * 24 * 60 * 60); + time_t monthTimestamp = today - (30 * 24 * 60 * 60); + + for (time_t currentMurderTimestamp : murderTimeStamps) { + if (currentMurderTimestamp > dayTimestamp) { + lastDay++; + } + + if (currentMurderTimestamp > weekTimestamp) { + lastWeek++; + } + + egibleMurders = lastMonth + 1; + + if (currentMurderTimestamp <= monthTimestamp) { + egibleMurders = lastMonth; + } + + lastMonth = egibleMurders; + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) { + return 2; // banishment! + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) { + return 1; // red skull! + } + + return 0; +} + +bool Player::setFollowCreature(Creature* creature) +{ + if (!Creature::setFollowCreature(creature)) { + setFollowCreature(nullptr); + setAttackedCreature(nullptr); + + sendCancelMessage(RETURNVALUE_THEREISNOWAY); + sendCancelTarget(); + stopWalk(); + return false; + } + return true; +} + +bool Player::setAttackedCreature(Creature* creature) +{ + if (!Creature::setAttackedCreature(creature)) { + sendCancelTarget(); + return false; + } + + if (chaseMode == CHASEMODE_FOLLOW && creature) { + if (followCreature != creature) { + //chase opponent + setFollowCreature(creature); + } + } else if (followCreature) { + setFollowCreature(nullptr); + } + + if (creature) { + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + return true; +} + +void Player::goToFollowCreature() +{ + if (!walkTask) { + if ((OTSYS_TIME() - lastFailedFollow) < 2000) { + return; + } + + Creature::goToFollowCreature(); + + if (followCreature && !hasFollowPath) { + lastFailedFollow = OTSYS_TIME(); + } + } +} + +void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + fpp.fullPathSearch = true; +} + +void Player::doAttacking(uint32_t) +{ + if (lastAttack == 0) { + lastAttack = OTSYS_TIME() - getAttackSpeed() - 1; + } + + if (hasCondition(CONDITION_PACIFIED)) { + return; + } + + if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { + if (Combat::attack(this, attackedCreature)) { + earliestAttackTime = OTSYS_TIME() + 2000; + lastAttack = OTSYS_TIME(); + } + } +} + +uint64_t Player::getGainedExperience(Creature* attacker) const +{ + if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) { + Player* attackerPlayer = attacker->getPlayer(); + if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { + return std::max(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75)); + } + } + return 0; +} + +void Player::onFollowCreature(const Creature* creature) +{ + if (!creature) { + stopWalk(); + } +} + +void Player::setChaseMode(chaseMode_t mode) +{ + chaseMode_t prevChaseMode = chaseMode; + chaseMode = mode; + + if (prevChaseMode != chaseMode) { + if (chaseMode == CHASEMODE_FOLLOW) { + if (!followCreature && attackedCreature) { + //chase opponent + setFollowCreature(attackedCreature); + } + } else if (attackedCreature) { + setFollowCreature(nullptr); + cancelNextWalk = true; + } + } +} + +void Player::onWalkAborted() +{ + setNextWalkActionTask(nullptr); + sendCancelWalk(); +} + +void Player::onWalkComplete() +{ + if (walkTask) { + walkTaskEvent = g_scheduler.addEvent(walkTask); + walkTask = nullptr; + } +} + +void Player::stopWalk() +{ + cancelNextWalk = true; +} + +LightInfo Player::getCreatureLight() const +{ + if (internalLight.level > itemsLight.level) { + return internalLight; + } + return itemsLight; +} + +void Player::updateItemsLight(bool internal /*=false*/) +{ + LightInfo maxLight; + LightInfo curLight; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (item) { + item->getLight(curLight); + + if (curLight.level > maxLight.level) { + maxLight = curLight; + } + } + } + + if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) { + itemsLight = maxLight; + + if (!internal) { + g_game.changeLight(this); + } + } +} + +void Player::onAddCondition(ConditionType_t type) +{ + Creature::onAddCondition(type); + sendIcons(); +} + +void Player::onAddCombatCondition(ConditionType_t type) +{ + switch (type) { + case CONDITION_POISON: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); + break; + + case CONDITION_DROWN: + sendTextMessage(MESSAGE_STATUS_SMALL, "You are drowning."); + break; + + case CONDITION_PARALYZE: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); + break; + + case CONDITION_DRUNK: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); + break; + + default: + break; + } +} + +void Player::onEndCondition(ConditionType_t type) +{ + Creature::onEndCondition(type); + + if (type == CONDITION_INFIGHT) { + onIdleStatus(); + pzLocked = false; + clearAttacked(); + + if (getSkull() != SKULL_RED) { + setSkull(SKULL_NONE); + } + } + + sendIcons(); +} + +void Player::onCombatRemoveCondition(Condition* condition) +{ + //Creature::onCombatRemoveCondition(condition); + if (condition->getId() > 0) { + //Means the condition is from an item, id == slot + if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + Item* item = getInventoryItem(static_cast(condition->getId())); + if (item) { + //25% chance to destroy the item + if (25 >= uniform_random(1, 100)) { + g_game.internalRemoveItem(item); + } + } + } + } else { + if (!canDoAction()) { + const uint32_t delay = getNextActionTime(); + const int32_t ticks = delay - (delay % EVENT_CREATURE_THINK_INTERVAL); + if (ticks < 0) { + removeCondition(condition); + } else { + condition->setTicks(ticks); + } + } else { + removeCondition(condition); + } + } +} + +void Player::onAttackedCreature(Creature* target) +{ + Creature::onAttackedCreature(target); + + if (target->getZone() == ZONE_PVP) { + return; + } + + if (target == this) { + addInFightTicks(); + return; + } + + if (hasFlag(PlayerFlag_NotGainInFight)) { + return; + } + + Player* targetPlayer = target->getPlayer(); + if (targetPlayer) { + if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + pzLocked = true; + sendIcons(); + } + + if (!isPartner(targetPlayer)) { + if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + addAttacked(targetPlayer); + targetPlayer->sendCreatureSkull(this); + } else { + if (!targetPlayer->hasAttacked(this)) { + if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { + addAttacked(targetPlayer); + if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { + setSkull(SKULL_WHITE); + } + } + + if (getSkull() == SKULL_NONE) { + targetPlayer->sendCreatureSkull(this); + } + } + } + } + } + + addInFightTicks(); +} + +void Player::onAttacked() +{ + Creature::onAttacked(); + + addInFightTicks(); +} + +void Player::onIdleStatus() +{ + Creature::onIdleStatus(); + + if (party) { + party->clearPlayerPoints(this); + } +} + +void Player::onPlacedCreature() +{ + //scripting event - onLogin + if (!g_creatureEvents->playerLogin(this)) { + kickPlayer(true); + } +} + +void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + Creature::onAttackedCreatureDrainHealth(target, points); + + if (target) { + if (party && !Combat::isPlayerCombat(target)) { + Monster* tmpMonster = target->getMonster(); + if (tmpMonster && tmpMonster->isHostile()) { + //We have fulfilled a requirement for shared experience + party->updatePlayerTicks(this, points); + } + } + } +} + +void Player::onTargetCreatureGainHealth(Creature* target, int32_t points) +{ + if (target && party) { + Player* tmpPlayer = nullptr; + + if (target->getPlayer()) { + tmpPlayer = target->getPlayer(); + } else if (Creature* targetMaster = target->getMaster()) { + if (Player* targetMasterPlayer = targetMaster->getPlayer()) { + tmpPlayer = targetMasterPlayer; + } + } + + if (isPartner(tmpPlayer)) { + party->updatePlayerTicks(this, points); + } + } +} + +bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) +{ + bool unjustified = false; + + if (hasFlag(PlayerFlag_NotGenerateLoot)) { + target->setDropLoot(false); + } + + Creature::onKilledCreature(target, lastHit); + + if (Player* targetPlayer = target->getPlayer()) { + if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { + targetPlayer->setDropLoot(false); + targetPlayer->setSkillLoss(false); + } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { + if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { + if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { + unjustified = true; + addUnjustifiedDead(targetPlayer); + } + } + } + + if (lastHit && hasCondition(CONDITION_INFIGHT)) { + pzLocked = true; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); + addCondition(condition); + } + } + + return unjustified; +} + +void Player::gainExperience(uint64_t gainExp, Creature* source) +{ + if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0 || staminaMinutes == 0) { + return; + } + + addExperience(source, gainExp, true); +} + +void Player::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (hasFlag(PlayerFlag_NotGainExperience)) { + return; + } + + if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + party->shareExperience(gainExp, target); + //We will get a share of the experience through the sharing mechanism + return; + } + + Creature::onGainExperience(gainExp, target); + gainExperience(gainExp, target); +} + +void Player::onGainSharedExperience(uint64_t gainExp, Creature* source) +{ + gainExperience(gainExp, source); +} + +bool Player::isImmune(CombatType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + return Creature::isImmune(type); +} + +bool Player::isImmune(ConditionType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + return Creature::isImmune(type); +} + +bool Player::isAttackable() const +{ + return !hasFlag(PlayerFlag_CannotBeAttacked); +} + +bool Player::lastHitIsPlayer(Creature* lastHitCreature) +{ + if (!lastHitCreature) { + return false; + } + + if (lastHitCreature->getPlayer()) { + return true; + } + + Creature* lastHitMaster = lastHitCreature->getMaster(); + return lastHitMaster && lastHitMaster->getPlayer(); +} + +void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + Creature::changeHealth(healthChange, sendHealthChange); + sendStats(); +} + +void Player::changeMana(int32_t manaChange) +{ + if (!hasFlag(PlayerFlag_HasInfiniteMana)) { + if (manaChange > 0) { + mana += std::min(manaChange, getMaxMana() - mana); + } + else { + mana = std::max(0, mana + manaChange); + } + } + + sendStats(); +} + +void Player::changeSoul(int32_t soulChange) +{ + if (soulChange > 0) { + soul += std::min(soulChange, vocation->getSoulMax() - soul); + } else { + soul = std::max(0, soul + soulChange); + } + + sendStats(); +} + +bool Player::canWear(uint32_t lookType, uint8_t addons) const +{ + if (group->access) { + return true; + } + + const Outfit* outfit = Outfits::getInstance().getOutfitByLookType(sex, lookType); + if (!outfit) { + return false; + } + + if (outfit->premium && !isPremium()) { + return false; + } + + if (outfit->unlocked && addons == 0) { + return true; + } + + for (const OutfitEntry& outfitEntry : outfits) { + if (outfitEntry.lookType != lookType) { + continue; + } + return (outfitEntry.addons & addons) == addons; + } + return false; +} + +bool Player::canLogout() +{ + if (isConnecting) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + return true; + } + + return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); +} + +void Player::genReservedStorageRange() +{ + //generate outfits range + uint32_t base_key = PSTRG_OUTFITS_RANGE_START; + for (const OutfitEntry& entry : outfits) { + storageMap[++base_key] = (entry.lookType << 16) | entry.addons; + } +} + +void Player::addOutfit(uint16_t lookType, uint8_t addons) +{ + for (OutfitEntry& outfitEntry : outfits) { + if (outfitEntry.lookType == lookType) { + outfitEntry.addons |= addons; + return; + } + } + outfits.emplace_back(lookType, addons); +} + +bool Player::removeOutfit(uint16_t lookType) +{ + for (auto it = outfits.begin(), end = outfits.end(); it != end; ++it) { + OutfitEntry& entry = *it; + if (entry.lookType == lookType) { + outfits.erase(it); + return true; + } + } + return false; +} + +bool Player::removeOutfitAddon(uint16_t lookType, uint8_t addons) +{ + for (OutfitEntry& outfitEntry : outfits) { + if (outfitEntry.lookType == lookType) { + outfitEntry.addons &= ~addons; + return true; + } + } + return false; +} + +bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const +{ + if (group->access) { + addons = 3; + return true; + } + + if (outfit.premium && !isPremium()) { + return false; + } + + for (const OutfitEntry& outfitEntry : outfits) { + if (outfitEntry.lookType != outfit.lookType) { + continue; + } + + addons = outfitEntry.addons; + return true; + } + + if (!outfit.unlocked) { + return false; + } + + addons = 0; + return true; +} + +void Player::setSex(PlayerSex_t newSex) +{ + sex = newSex; +} + +Skulls_t Player::getSkull() const +{ + if (hasFlag(PlayerFlag_NotGainInFight)) { + return SKULL_NONE; + } + return skull; +} + +Skulls_t Player::getSkullClient(const Creature* creature) const +{ + if (!creature || g_game.getWorldType() != WORLD_TYPE_PVP) { + return SKULL_NONE; + } + + const Player* player = creature->getPlayer(); + if (!player || player->getSkull() != SKULL_NONE) { + return Creature::getSkullClient(creature); + } + + if (isInWar(player)) { + return SKULL_GREEN; + } + + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } + + if (isPartner(player)) { + return SKULL_GREEN; + } + return Creature::getSkullClient(creature); +} + + +bool Player::hasAttacked(const Player* attacked) const +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) { + return false; + } + + return attackedSet.find(attacked->id) != attackedSet.end(); +} + +void Player::addAttacked(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) { + return; + } + + attackedSet.insert(attacked->id); +} + +void Player::removeAttacked(const Player* attacked) +{ + if (!attacked || attacked == this) { + return; + } + + auto it = attackedSet.find(attacked->guid); + if (it != attackedSet.end()) { + attackedSet.erase(it); + } +} + +void Player::clearAttacked() +{ + attackedSet.clear(); +} + +void Player::addUnjustifiedDead(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + return; + } + + // current unjustified kill! + murderTimeStamps.push_back(std::time(nullptr)); + + sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified."); + + if (playerKillerEnd == 0) { + // white skull time, it only sets on first kill! + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME); + } + + uint32_t murderResult = checkPlayerKilling(); + if (murderResult >= 1) { + // red skull player + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME); + setSkull(SKULL_RED); + + if (murderResult == 2) { + // banishment for too many unjustified kills + Database* db = Database::getInstance(); + + std::ostringstream ss; + ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ("; + ss << getAccount() << ", "; + ss << db->escapeString("Too many unjustified kills") << ", "; + ss << std::time(nullptr) << ", "; + ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", "; + ss << "1);"; + + db->executeQuery(ss.str()); + + g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS); + g_game.removeCreature(this); + disconnect(); + } + } +} + +void Player::checkSkullTicks() +{ + time_t today = std::time(nullptr); + + if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) { + setSkull(SKULL_NONE); + } +} + +bool Player::isPromoted() const +{ + uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId()); + return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation; +} + +double Player::getLostPercent() const +{ + int32_t blessingCount = std::bitset<5>(blessings).count(); + + int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT); + if (deathLosePercent != -1) { + if (isPromoted()) { + deathLosePercent -= 3; + } + + deathLosePercent -= blessingCount; + return std::max(0, deathLosePercent) / 100.; + } + + double lossPercent; + if (level >= 25) { + double tmpLevel = level + (levelPercent / 100.); + lossPercent = static_cast((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; + } else { + lossPercent = 10; + } + + if (isPromoted()) { + lossPercent *= 0.7; + } + + return lossPercent * pow(0.92, blessingCount) / 100; +} + +void Player::learnInstantSpell(const std::string& spellName) +{ + if (!hasLearnedInstantSpell(spellName)) { + learnedInstantSpellList.push_front(spellName); + } +} + +void Player::forgetInstantSpell(const std::string& spellName) +{ + learnedInstantSpellList.remove(spellName); +} + +bool Player::hasLearnedInstantSpell(const std::string& spellName) const +{ + if (hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + for (const auto& learnedSpellName : learnedInstantSpellList) { + if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) { + return true; + } + } + return false; +} + +uint32_t Player::getWarId(const Player* targetPlayer) const +{ + if (!targetPlayer || !guild) { + return false; + } + + const Guild* targetPlayerGuild = targetPlayer->getGuild(); + if (!targetPlayerGuild) { + return false; + } + + const auto targetGuild = guild->getId(); + const auto killerGuild = targetPlayerGuild->getId(); + + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `guild_wars` WHERE `status` IN (1, 4) AND ((`guild1` = " << killerGuild << " AND `guild2` = " << targetGuild << ") OR (`guild1` = " << targetGuild << " AND `guild2` = " << killerGuild << "))"; + DBResult_ptr result = db->storeQuery(query.str()); + + if (!result) { + return 0; + } + + return result->getNumber("id"); +} + +bool Player::isInWar(const Player* player) const +{ + if (!player || !guild) { + return false; + } + + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + return false; + } + + return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId()); +} + +bool Player::isInWarList(uint32_t guildId) const +{ + return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end(); +} + +bool Player::isPremium() const +{ + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { + return true; + } + + return premiumDays > 0; +} + +void Player::setPremiumDays(int32_t v) +{ + premiumDays = v; +} + +PartyShields_t Player::getPartyShield(const Player* player) const +{ + if (!player) { + return SHIELD_NONE; + } + + if (party) { + if (party->getLeader() == player) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_YELLOW_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_YELLOW_NOSHAREDEXP; + } + + return SHIELD_YELLOW_NOSHAREDEXP_BLINK; + } + + return SHIELD_YELLOW; + } + + if (player->party == party) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_BLUE_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_BLUE_NOSHAREDEXP; + } + + return SHIELD_BLUE_NOSHAREDEXP_BLINK; + } + + return SHIELD_BLUE; + } + + if (isInviting(player)) { + return SHIELD_WHITEBLUE; + } + } + + if (player->isInviting(this)) { + return SHIELD_WHITEYELLOW; + } + + return SHIELD_NONE; +} + +bool Player::isInviting(const Player* player) const +{ + if (!player || !party || party->getLeader() != this) { + return false; + } + return party->isPlayerInvited(player); +} + +bool Player::isPartner(const Player* player) const +{ + if (!player || !party || player == this) { + return false; + } + return party == player->party; +} + +bool Player::isGuildMate(const Player* player) const +{ + if (!player || !guild) { + return false; + } + return guild == player->guild; +} + +void Player::sendPlayerPartyIcons(Player* player) +{ + sendCreatureShield(player); + sendCreatureSkull(player); +} + +bool Player::addPartyInvitation(Party* party) +{ + auto it = std::find(invitePartyList.begin(), invitePartyList.end(), party); + if (it != invitePartyList.end()) { + return false; + } + + invitePartyList.push_front(party); + return true; +} + +void Player::removePartyInvitation(Party* party) +{ + invitePartyList.remove(party); +} + +void Player::clearPartyInvitations() +{ + for (Party* invitingParty : invitePartyList) { + invitingParty->removeInvite(*this, false); + } + invitePartyList.clear(); +} + +void Player::sendClosePrivate(uint16_t channelId) +{ + if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { + g_chat->removeUserFromChannel(*this, channelId); + } + + if (client) { + client->sendClosePrivate(channelId); + } +} + +bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) +{ + if (tries == 0 || skill == SKILL_LEVEL) { + return false; + } + + bool sendUpdate = false; + uint32_t oldSkillValue, newSkillValue; + long double oldPercentToNextLevel, newPercentToNextLevel; + + if (skill == SKILL_MAGLEVEL) { + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + return false; + } + + oldSkillValue = magLevel; + oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; + + g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, tries); + uint32_t currMagLevel = magLevel; + + while ((manaSpent + tries) >= nextReqMana) { + tries -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); + + sendUpdate = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + tries = 0; + break; + } + } + + manaSpent += tries; + + if (magLevel != currMagLevel) { + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint8_t newPercent; + if (nextReqMana > currReqMana) { + newPercent = Player::getPercentLevel(manaSpent, nextReqMana); + newPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; + } + else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (newPercent != magLevelPercent) { + magLevelPercent = newPercent; + sendUpdate = true; + } + + newSkillValue = magLevel; + } + else { + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + return false; + } + + oldSkillValue = skills[skill].level; + oldPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; + + g_events->eventPlayerOnGainSkillTries(this, skill, tries); + uint32_t currSkillLevel = skills[skill].level; + + while ((skills[skill].tries + tries) >= nextReqTries) { + tries -= nextReqTries - skills[skill].tries; + + skills[skill].level++; + skills[skill].tries = 0; + skills[skill].percent = 0; + + g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); + + sendUpdate = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + + if (currReqTries >= nextReqTries) { + tries = 0; + break; + } + } + + skills[skill].tries += tries; + + if (currSkillLevel != skills[skill].level) { + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint8_t newPercent; + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); + newPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; + } + else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (skills[skill].percent != newPercent) { + skills[skill].percent = newPercent; + sendUpdate = true; + } + + newSkillValue = skills[skill].level; + } + + if (sendUpdate) { + sendSkills(); + } + + std::ostringstream ss; + ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ')'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + return sendUpdate; +} + +uint64_t Player::getMoney() const +{ + std::vector containers; + uint64_t moneyCount = 0; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + const Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } else { + moneyCount += item->getWorth(); + } + } + + size_t i = 0; + while (i < containers.size()) { + const Container* container = containers[i++]; + for (const Item* item : container->getItemList()) { + const Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } else { + moneyCount += item->getWorth(); + } + } + } + return moneyCount; +} + +size_t Player::getMaxVIPEntries() const +{ + if (group->maxVipEntries != 0) { + return group->maxVipEntries; + } else if (isPremium()) { + return 100; + } + return 20; +} + +size_t Player::getMaxDepotItems() const +{ + if (group->maxDepotItems != 0) { + return group->maxDepotItems; + } else if (isPremium()) { + return 2000; + } + + return 1000; +} + +std::forward_list Player::getMuteConditions() const +{ + std::forward_list muteConditions; + for (Condition* condition : conditions) { + if (condition->getTicks() <= 0) { + continue; + } + + ConditionType_t type = condition->getType(); + if (type != CONDITION_MUTED && type != CONDITION_CHANNELMUTEDTICKS && type != CONDITION_YELLTICKS) { + continue; + } + + muteConditions.push_front(condition); + } + return muteConditions; +} + +void Player::setGuild(Guild* guild) +{ + if (guild == this->guild) { + return; + } + + Guild* oldGuild = this->guild; + + this->guildNick.clear(); + this->guild = nullptr; + this->guildRank = nullptr; + + if (guild) { + const GuildRank* rank = guild->getRankByLevel(1); + if (!rank) { + return; + } + + this->guild = guild; + this->guildRank = rank; + guild->addMember(this); + } + + if (oldGuild) { + oldGuild->removeMember(this); + } +} diff --git a/app/SabrehavenServer/src/player.h b/app/SabrehavenServer/src/player.h new file mode 100644 index 0000000..08f4002 --- /dev/null +++ b/app/SabrehavenServer/src/player.h @@ -0,0 +1,1176 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F +#define FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F + +#include "creature.h" +#include "container.h" +#include "cylinder.h" +#include "outfit.h" +#include "enums.h" +#include "vocation.h" +#include "protocolgame.h" +#include "ioguild.h" +#include "party.h" +#include "depotlocker.h" +#include "guild.h" +#include "groups.h" +#include "town.h" + +class BehaviourDatabase; +class House; +class NetworkMessage; +class Weapon; +class ProtocolGame; +class Npc; +class Party; +class SchedulerTask; +class Bed; +class Guild; + +enum skillsid_t { + SKILLVALUE_LEVEL = 0, + SKILLVALUE_TRIES = 1, + SKILLVALUE_PERCENT = 2, +}; + +enum chaseMode_t : uint8_t { + CHASEMODE_STANDSTILL = 0, + CHASEMODE_FOLLOW = 1, +}; + +enum tradestate_t : uint8_t { + TRADE_NONE, + TRADE_INITIATED, + TRADE_ACCEPT, + TRADE_ACKNOWLEDGE, + TRADE_TRANSFER, +}; + +struct VIPEntry { + VIPEntry(uint32_t guid, std::string name) : + guid(guid), name(std::move(name)) {} + + uint32_t guid; + std::string name; +}; + +struct OpenContainer { + Container* container; + uint16_t index; +}; + +struct OutfitEntry { + constexpr OutfitEntry(uint16_t lookType, uint8_t addons) : lookType(lookType), addons(addons) {} + + uint16_t lookType; + uint8_t addons; +}; + +struct Skill { + uint64_t tries = 0; + uint16_t level = 10; + uint8_t percent = 0; +}; + +typedef std::map MuteCountMap; + +static constexpr int32_t PLAYER_MAX_SPEED = 1500; +static constexpr int32_t PLAYER_MIN_SPEED = 0; + +class Player final : public Creature, public Cylinder +{ + public: + explicit Player(ProtocolGame_ptr p); + ~Player(); + + // non-copyable + Player(const Player&) = delete; + Player& operator=(const Player&) = delete; + + Player* getPlayer() final { + return this; + } + const Player* getPlayer() const final { + return this; + } + + void setID() final { + if (id == 0) { + id = playerAutoID++; + } + } + + static MuteCountMap muteCountMap; + + const std::string& getName() const final { + return name; + } + void setName(std::string name) { + this->name = std::move(name); + } + const std::string& getNameDescription() const final { + return name; + } + std::string getDescription(int32_t lookDistance) const final; + + void setGUID(uint32_t guid) { + this->guid = guid; + } + uint32_t getGUID() const { + return guid; + } + bool canSeeInvisibility() const final { + return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; + } + + void removeList() final; + void addList() final; + void kickPlayer(bool displayEffect); + + static uint64_t getExpForLevel(int32_t lv) { + lv--; + return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL; + } + + uint16_t getStaminaMinutes() const { + return staminaMinutes; + } + + bool isFakePlayer = false; + + bool addOfflineTrainingTries(skills_t skill, uint64_t tries); + + void addOfflineTrainingTime(int32_t addTime) { + offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); + } + void removeOfflineTrainingTime(int32_t removeTime) { + offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); + } + int32_t getOfflineTrainingTime() const { + return offlineTrainingTime; + } + + int32_t getOfflineTrainingSkill() const { + return offlineTrainingSkill; + } + void setOfflineTrainingSkill(int32_t skill) { + offlineTrainingSkill = skill; + } + + uint64_t getBankBalance() const { + return bankBalance; + } + void setBankBalance(uint64_t balance) { + bankBalance = balance; + } + + Guild* getGuild() const { + return guild; + } + void setGuild(Guild* guild); + + const GuildRank* getGuildRank() const { + return guildRank; + } + void setGuildRank(const GuildRank* newGuildRank) { + guildRank = newGuildRank; + } + + bool isGuildMate(const Player* player) const; + + const std::string& getGuildNick() const { + return guildNick; + } + void setGuildNick(std::string nick) { + guildNick = nick; + } + + uint32_t getWarId(const Player* player) const; + bool isInWar(const Player* player) const; + bool isInWarList(uint32_t guild_id) const; + + uint16_t getClientIcons() const; + + const GuildWarList& getGuildWarList() const { + return guildWarList; + } + + Vocation* getVocation() const { + return vocation; + } + + OperatingSystem_t getOperatingSystem() const { + return operatingSystem; + } + void setOperatingSystem(OperatingSystem_t clientos) { + operatingSystem = clientos; + } + + uint16_t getProtocolVersion() const { + if (!client) { + return 0; + } + + return client->getVersion(); + } + + bool hasSecureMode() const { + return secureMode; + } + + void setParty(Party* party) { + this->party = party; + } + Party* getParty() const { + return party; + } + PartyShields_t getPartyShield(const Player* player) const; + bool isInviting(const Player* player) const; + bool isPartner(const Player* player) const; + void sendPlayerPartyIcons(Player* player); + bool addPartyInvitation(Party* party); + void removePartyInvitation(Party* party); + void clearPartyInvitations(); + + uint64_t getSpentMana() const { + return manaSpent; + } + + bool hasFlag(PlayerFlags value) const { + return (group->flags & value) != 0; + } + + BedItem* getBedItem() { + return bedItem; + } + void setBedItem(BedItem* b) { + bedItem = b; + } + + void addBlessing(uint8_t blessing) { + blessings |= blessing; + } + void removeBlessing(uint8_t blessing) { + blessings &= ~blessing; + } + bool hasBlessing(uint8_t value) const { + return (blessings & (static_cast(1) << value)) != 0; + } + + bool isOffline() const { + return (getID() == 0); + } + void disconnect() { + if (client) { + client->disconnect(); + } + } + uint32_t getIP() const; + + void addContainer(uint8_t cid, Container* container); + void closeContainer(uint8_t cid); + void setContainerIndex(uint8_t cid, uint16_t index); + + Container* getContainerByID(uint8_t cid); + int8_t getContainerID(const Container* container) const; + uint16_t getContainerIndex(uint8_t cid) const; + + bool canOpenCorpse(uint32_t ownerId) const; + + void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); + bool getStorageValue(const uint32_t key, int32_t& value) const; + void genReservedStorageRange(); + + void setGroup(Group* newGroup) { + group = newGroup; + } + Group* getGroup() const { + return group; + } + + void resetIdleTime() { + idleTime = 0; + resetLastWalkingTime(); + } + + int32_t getIdleTime() const { + return idleTime; + } + + void resetLastWalkingTime() { + lastWalkingTime = 0; + } + + int32_t getLastWalkingTime() const { + return lastWalkingTime; + } + + bool isInGhostMode() const { + return ghostMode; + } + void switchGhostMode() { + ghostMode = !ghostMode; + } + + uint32_t getAccount() const { + return accountNumber; + } + AccountType_t getAccountType() const { + return accountType; + } + uint32_t getLevel() const { + return level; + } + uint8_t getLevelPercent() const { + return levelPercent; + } + uint32_t getMagicLevel() const { + return std::max(0, magLevel + varStats[STAT_MAGICPOINTS]); + } + uint32_t getBaseMagicLevel() const { + return magLevel; + } + uint8_t getMagicLevelPercent() const { + return magLevelPercent; + } + uint8_t getSoul() const { + return soul; + } + bool isAccessPlayer() const { + return group->access; + } + bool isPremium() const; + void setPremiumDays(int32_t v); + + bool setVocation(uint16_t vocId); + uint16_t getVocationId() const { + return vocation->getId(); + } + uint16_t getVocationFlagId() const { + return vocation->getFlagId(); + } + + PlayerSex_t getSex() const { + return sex; + } + void setSex(PlayerSex_t); + uint64_t getExperience() const { + return experience; + } + + time_t getLastLoginSaved() const { + return lastLoginSaved; + } + + time_t getLastLogout() const { + return lastLogout; + } + + const Position& getLoginPosition() const { + return loginPosition; + } + const Position& getTemplePosition() const { + return town->getTemplePosition(); + } + Town* getTown() const { + return town; + } + void setTown(Town* town) { + this->town = town; + } + + bool isPushable() const final; + uint32_t isMuted() const; + void addMessageBuffer(); + void removeMessageBuffer(); + + bool removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped = false) const; + + uint32_t getCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); + } + return capacity; + } + + uint32_t getFreeCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); + } else { + return std::max(0, capacity - inventoryWeight); + } + } + + int32_t getMaxHealth() const final { + return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); + } + + uint32_t getMana() const { + return mana; + } + + uint32_t getMaxMana() const { + return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); + } + + Item* getInventoryItem(slots_t slot) const; + + bool isItemAbilityEnabled(slots_t slot) const { + return inventoryAbilities[slot]; + } + void setItemAbility(slots_t slot, bool enabled) { + inventoryAbilities[slot] = enabled; + } + + void setVarSkill(skills_t skill, int32_t modifier) { + varSkills[skill] += modifier; + } + + void setVarStats(stats_t stat, int32_t modifier); + int32_t getDefaultStats(stats_t stat) const; + + void addConditionSuppressions(uint32_t conditions); + void removeConditionSuppressions(uint32_t conditions); + + DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate); + void onReceiveMail(uint32_t townId) const; + bool isNearDepotBox(uint32_t townId) const; + + bool canSee(const Position& pos) const final; + bool canSeeCreature(const Creature* creature) const final; + + RaceType_t getRace() const final { + return RACE_BLOOD; + } + + uint64_t getMoney() const; + + //safe-trade functions + void setTradeState(tradestate_t state) { + tradeState = state; + } + tradestate_t getTradeState() const { + return tradeState; + } + Item* getTradeItem() { + return tradeItem; + } + + //V.I.P. functions + void notifyStatusChange(Player* player, VipStatus_t status); + bool removeVIP(uint32_t vipGuid); + bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); + bool addVIPInternal(uint32_t vipGuid); + + //follow functions + bool setFollowCreature(Creature* creature) final; + void goToFollowCreature() final; + + //follow events + void onFollowCreature(const Creature* creature) final; + + //walk events + void onWalk(Direction& dir) final; + void onWalkAborted() final; + void onWalkComplete() final; + + void stopWalk(); + + void setChaseMode(chaseMode_t mode); + void setFightMode(fightMode_t mode) { + fightMode = mode; + } + void setSecureMode(bool mode) { + secureMode = mode; + } + + //combat functions + bool setAttackedCreature(Creature* creature) final; + bool isImmune(CombatType_t type) const final; + bool isImmune(ConditionType_t type) const final; + bool hasShield() const; + bool isAttackable() const final; + static bool lastHitIsPlayer(Creature* lastHitCreature); + + void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; + void changeMana(int32_t manaChange); + void changeSoul(int32_t soulChange); + + bool isPzLocked() const { + return pzLocked; + } + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false) final; + void doAttacking(uint32_t interval) final; + bool hasExtraSwing() final { + return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); + } + + uint16_t getSkillLevel(uint8_t skill) const { + return std::max(0, skills[skill].level + varSkills[skill]); + } + uint16_t getBaseSkill(uint8_t skill) const { + return skills[skill].level; + } + uint8_t getSkillPercent(uint8_t skill) const { + return skills[skill].percent; + } + + bool getAddAttackSkill() const { + return addAttackSkillPoint; + } + BlockType_t getLastAttackBlockType() const { + return lastAttackBlockType; + } + + Item* getWeapon() const; + Item* getAmmunition() const; + void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; + + void drainHealth(Creature* attacker, int32_t damage) final; + void drainMana(Creature* attacker, int32_t manaLoss); + void addManaSpent(uint64_t amount); + void addSkillAdvance(skills_t skill, uint64_t count); + + int32_t getArmor() const final; + int32_t getDefense() final; + fightMode_t getFightMode() const; + + void addInFightTicks(bool pzlock = false); + + uint64_t getGainedExperience(Creature* attacker) const final; + + //combat event functions + void onAddCondition(ConditionType_t type) final; + void onAddCombatCondition(ConditionType_t type) final; + void onEndCondition(ConditionType_t type) final; + void onCombatRemoveCondition(Condition* condition) final; + void onAttackedCreature(Creature* target) final; + void onAttacked() final; + void onAttackedCreatureDrainHealth(Creature* target, int32_t points) final; + void onTargetCreatureGainHealth(Creature* target, int32_t points) final; + bool onKilledCreature(Creature* target, bool lastHit = true) final; + void onGainExperience(uint64_t gainExp, Creature* target) final; + void onGainSharedExperience(uint64_t gainExp, Creature* source); + void onAttackedCreatureBlockHit(BlockType_t blockType) final; + void onBlockHit() final; + void onChangeZone(ZoneType_t zone) final; + void onAttackedCreatureChangeZone(ZoneType_t zone) final; + void onIdleStatus() final; + void onPlacedCreature() final; + + LightInfo getCreatureLight() const override; + + Skulls_t getSkull() const final; + Skulls_t getSkullClient(const Creature* creature) const final; + time_t getPlayerKillerEnd() const { return playerKillerEnd; } + void setPlayerKillerEnd(time_t ticks) { playerKillerEnd = ticks; } + + bool hasAttacked(const Player* attacked) const; + void addAttacked(const Player* attacked); + void removeAttacked(const Player* attacked); + void clearAttacked(); + void addUnjustifiedDead(const Player* attacked); + void sendCreatureSkull(const Creature* creature) const { + if (client) { + client->sendCreatureSkull(creature); + } + } + void checkSkullTicks(); + + bool canWear(uint32_t lookType, uint8_t addons) const; + void addOutfit(uint16_t lookType, uint8_t addons); + bool removeOutfit(uint16_t lookType); + bool removeOutfitAddon(uint16_t lookType, uint8_t addons); + bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; + + + bool canLogout(); + + size_t getMaxVIPEntries() const; + size_t getMaxDepotItems() const; + + //tile + //send methods + void sendAddTileItem(const Tile* tile, const Position& pos, const Item* item) { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendAddTileItem(pos, item, stackpos); + } + } + } + void sendUpdateTileItem(const Tile* tile, const Position& pos, const Item* item) { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendUpdateTileItem(pos, stackpos, item); + } + } + } + void sendRemoveTileThing(const Position& pos, int32_t stackpos) { + if (stackpos != -1 && client) { + client->sendRemoveTileThing(pos, stackpos); + } + } + void sendUpdateTile(const Tile* tile, const Position& pos) { + if (client) { + client->sendUpdateTile(tile, pos); + } + } + + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { + if (client) { + client->sendChannelMessage(author, text, type, channel); + } + } + void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { + if (client) { + client->sendAddCreature(creature, pos, creature->getTile()->getStackposOfCreature(this, creature), isLogin); + } + } + void sendCreatureMove(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) { + if (client) { + client->sendMoveCreature(creature, newPos, newStackPos, oldPos, oldStackPos, teleport); + } + } + void sendCreatureTurn(const Creature* creature) { + if (client && canSeeCreature(creature)) { + int32_t stackpos = creature->getTile()->getStackposOfCreature(this, creature); + if (stackpos != -1) { + client->sendCreatureTurn(creature, stackpos); + } + } + } + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr) { + if (client) { + client->sendCreatureSay(creature, type, text, pos); + } + } + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) { + if (client) { + client->sendPrivateMessage(speaker, type, text); + } + } + void sendCreatureSquare(const Creature* creature, SquareColor_t color) { + if (client) { + client->sendCreatureSquare(creature, color); + } + } + void sendCreatureChangeOutfit(const Creature* creature, const Outfit_t& outfit) { + if (client) { + client->sendCreatureOutfit(creature, outfit); + } + } + void sendCreatureChangeVisible(const Creature* creature, bool visible) { + if (!client) { + return; + } + + if (creature->getPlayer()) { + if (visible) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + static Outfit_t outfit; + client->sendCreatureOutfit(creature, outfit); + } + } else if (canSeeInvisibility()) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + int32_t stackpos = creature->getTile()->getStackposOfCreature(this, creature); + if (stackpos == -1) { + return; + } + + if (visible) { + client->sendAddCreature(creature, creature->getPosition(), stackpos, false); + } else { + client->sendRemoveTileThing(creature->getPosition(), stackpos); + } + } + } + void sendCreatureLight(const Creature* creature) { + if (client) { + client->sendCreatureLight(creature); + } + } + void sendCreatureShield(const Creature* creature) { + if (client) { + client->sendCreatureShield(creature); + } + } + + //container + void sendAddContainerItem(const Container* container, const Item* item); + void sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem); + void sendRemoveContainerItem(const Container* container, uint16_t slot); + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) { + if (client) { + client->sendContainer(cid, container, hasParent, firstIndex); + } + } + + //inventory + void sendInventoryItem(slots_t slot, const Item* item) { + if (client) { + client->sendInventoryItem(slot, item); + } + } + + //event methods + void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) final; + void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) final; + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) final; + + void onAttackedCreatureDisappear(bool isLogout) final; + void onFollowCreatureDisappear(bool isLogout) final; + + //container + void onAddContainerItem(const Item* item); + void onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem); + void onRemoveContainerItem(const Container* container, const Item* item); + + void onCloseContainer(const Container* container); + void onSendContainer(const Container* container); + void autoCloseContainers(const Container* container); + + //inventory + void onUpdateInventoryItem(Item* oldItem, Item* newItem); + void onRemoveInventoryItem(Item* item); + + void sendCancelMessage(const std::string& msg) const { + if (client) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, msg)); + } + } + void sendCancelMessage(ReturnValue message) const; + void sendCancelTarget() const { + if (client) { + client->sendCancelTarget(); + } + } + void sendCancelWalk() const { + if (client) { + client->sendCancelWalk(); + } + } + void sendChangeSpeed(const Creature* creature, uint32_t newSpeed) const { + if (client) { + client->sendChangeSpeed(creature, newSpeed); + } + } + void sendCreatureHealth(const Creature* creature) const { + if (client) { + client->sendCreatureHealth(creature); + } + } + void sendDistanceShoot(const Position& from, const Position& to, unsigned char type) const { + if (client) { + client->sendDistanceShoot(from, to, type); + } + } + void sendHouseWindow(House* house, uint32_t listId) const; + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendCreatePrivateChannel(channelId, channelName); + } + } + void sendClosePrivate(uint16_t channelId); + void sendIcons() const { + if (client) { + client->sendIcons(getClientIcons()); + } + } + void sendMagicEffect(const Position& pos, uint8_t type) const { + if (client) { + client->sendMagicEffect(pos, type); + } + } + void sendPing(); + void sendPingBack() const { + if (client) { + client->sendPingBack(); + } + } + void sendStats(); + void sendSkills() const { + if (client) { + client->sendSkills(); + } + } + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) const { + if (client) { + client->sendAnimatedText(pos, color, text); + } + } + void sendTextMessage(MessageClasses mclass, const std::string& message) const { + if (client) { + client->sendTextMessage(TextMessage(mclass, message)); + } + } + void sendTextMessage(const TextMessage& message) const { + if (client) { + client->sendTextMessage(message); + } + } + void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { + if (client) { + client->sendTextWindow(windowTextId, item, maxlen, canWrite); + } + } + void sendTextWindow(uint32_t itemId, const std::string& text) const { + if (client) { + client->sendTextWindow(windowTextId, itemId, text); + } + } + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) const { + if (client) { + client->sendToChannel(creature, type, text, channelId); + } + } + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const { + if (client) { + client->sendTradeItemRequest(traderName, item, ack); + } + } + void sendTradeClose() const { + if (client) { + client->sendCloseTrade(); + } + } + void sendWorldLight(const LightInfo& lightInfo) { + if (client) { + client->sendWorldLight(lightInfo); + } + } + void sendChannelsDialog() { + if (client) { + client->sendChannelsDialog(); + } + } + void sendOpenPrivateChannel(const std::string& receiver) { + if (client) { + client->sendOpenPrivateChannel(receiver); + } + } + void sendQuestLog() { + if (client) { + client->sendQuestLog(); + } + } + void sendQuestLine(const Quest* quest) { + if (client) { + client->sendQuestLine(quest); + } + } + void sendOutfitWindow() { + if (client) { + client->sendOutfitWindow(); + } + } + void sendCloseContainer(uint8_t cid) { + if (client) { + client->sendCloseContainer(cid); + } + } + void sendRemoveRuleViolationReport(const std::string& name) const { + if (client) { + client->sendRemoveRuleViolationReport(name); + } + } + void sendRuleViolationCancel(const std::string& name) const { + if (client) { + client->sendRuleViolationCancel(name); + } + } + void sendLockRuleViolationReport() const { + if (client) { + client->sendLockRuleViolation(); + } + } + void sendRuleViolationsChannel(uint16_t channelId) const { + if (client) { + client->sendRuleViolationsChannel(channelId); + } + } + + void sendChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendChannel(channelId, channelName); + } + } + void sendFightModes() { + if (client) { + client->sendFightModes(); + } + } + void sendNetworkMessage(const NetworkMessage& message) { + if (client) { + client->writeToOutputBuffer(message); + } + } + + void receivePing() { + lastPong = OTSYS_TIME(); + } + + void onThink(uint32_t interval) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + void setNextAction(int64_t time) { + if (time > nextAction) { + nextAction = time; + } + } + bool canDoAction() const { + return nextAction <= OTSYS_TIME(); + } + uint32_t getNextActionTime() const; + + Item* getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen); + void setWriteItem(Item* item, uint16_t maxWriteLen = 0); + + House* getEditHouse(uint32_t& windowTextId, uint32_t& listId); + void setEditHouse(House* house, uint32_t listId = 0); + + void learnInstantSpell(const std::string& spellName); + void forgetInstantSpell(const std::string& spellName); + bool hasLearnedInstantSpell(const std::string& spellName) const; + + protected: + std::forward_list getMuteConditions() const; + + void checkTradeState(const Item* item); + bool hasCapacity(const Item* item, uint32_t count) const; + + void gainExperience(uint64_t gainExp, Creature* source); + void addExperience(Creature* source, uint64_t exp, bool sendText = false); + void removeExperience(uint64_t exp); + + void updateInventoryWeight(); + + void setNextWalkActionTask(SchedulerTask* task); + void setNextWalkTask(SchedulerTask* task); + void setNextActionTask(SchedulerTask* task); + + void dropLoot(Container* corpse, Creature*) final; + void death(Creature* lastHitCreature) final; + bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing*) final {} + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + uint32_t getRuneCount(uint16_t itemId) const; + std::map& getAllItemTypeCount(std::map& countMap) const final; + Thing* getThing(size_t index) const final; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + + uint32_t checkPlayerKilling(); + + std::unordered_set attackedSet; + std::unordered_set VIPList; + + std::map openContainers; + std::map depotLockerMap; + std::map storageMap; + + std::vector outfits; + GuildWarList guildWarList; + + std::forward_list invitePartyList; + std::forward_list learnedInstantSpellList; + std::forward_list storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow + + std::list murderTimeStamps; + + std::string name; + std::string guildNick; + + Skill skills[SKILL_LAST + 1]; + LightInfo itemsLight; + Position loginPosition; + Position lastWalkthroughPosition; + + time_t lastLoginSaved = 0; + time_t lastLogout = 0; + time_t playerKillerEnd = 0; + + uint64_t experience = 0; + uint64_t manaSpent = 0; + uint64_t bankBalance = 0; + uint64_t lastQuestlogUpdate = 0; + int64_t lastAttack = 0; + int64_t lastFailedFollow = 0; + int64_t lastPing; + int64_t lastPong; + int64_t nextAction = 0; + int64_t earliestAttackTime = 0; + + BedItem* bedItem = nullptr; + Guild* guild = nullptr; + const GuildRank* guildRank = nullptr; + Group* group = nullptr; + Item* tradeItem = nullptr; + Item* inventory[CONST_SLOT_LAST + 1] = {}; + Item* writeItem = nullptr; + House* editHouse = nullptr; + Party* party = nullptr; + Player* tradePartner = nullptr; + ProtocolGame_ptr client; + SchedulerTask* walkTask = nullptr; + Town* town = nullptr; + Vocation* vocation = nullptr; + + uint32_t inventoryWeight = 0; + uint32_t capacity = 40000; + uint32_t damageImmunities = 0; + uint32_t conditionImmunities = 0; + uint32_t conditionSuppressions = 0; + uint32_t level = 1; + uint32_t magLevel = 0; + uint32_t actionTaskEvent = 0; + uint32_t nextStepEvent = 0; + uint32_t walkTaskEvent = 0; + uint32_t MessageBufferTicks = 0; + uint32_t lastIP = 0; + uint32_t accountNumber = 0; + uint32_t guid = 0; + uint32_t windowTextId = 0; + uint32_t editListId = 0; + uint32_t mana = 0; + uint32_t manaMax = 0; + int32_t varSkills[SKILL_LAST + 1] = {}; + int32_t varStats[STAT_LAST + 1] = {}; + int32_t MessageBufferCount = 0; + int32_t premiumDays = 0; + int32_t bloodHitCount = 0; + int32_t shieldBlockCount = 0; + int32_t offlineTrainingSkill = -1; + int32_t offlineTrainingTime = 0; + int32_t idleTime = 0; + int32_t lastWalkingTime = 0; + + uint16_t staminaMinutes = 3360; + uint16_t maxWriteLen = 0; + + uint8_t soul = 0; + uint8_t blessings = 0; + uint8_t levelPercent = 0; + uint8_t magLevelPercent = 0; + + PlayerSex_t sex = PLAYERSEX_FEMALE; + OperatingSystem_t operatingSystem = CLIENTOS_NONE; + BlockType_t lastAttackBlockType = BLOCK_NONE; + tradestate_t tradeState = TRADE_NONE; + chaseMode_t chaseMode = CHASEMODE_STANDSTILL; + fightMode_t fightMode = FIGHTMODE_ATTACK; + AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + + bool secureMode = false; + bool ghostMode = false; + bool pzLocked = false; + bool isConnecting = false; + bool addAttackSkillPoint = false; + bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; + + static uint32_t playerAutoID; + + void updateItemsLight(bool internal = false); + int32_t getStepSpeed() const final { + return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); + } + void updateBaseSpeed() { + if (!hasFlag(PlayerFlag_SetMaxSpeed)) { + baseSpeed = vocation->getBaseSpeed() + (level - 1); + } else { + baseSpeed = PLAYER_MAX_SPEED; + } + } + + bool isPromoted() const; + + uint32_t getAttackSpeed() const { + return vocation->getAttackSpeed(); + } + + static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); + static uint16_t getDropLootPercent(); + double getLostPercent() const; + uint64_t getLostExperience() const final { + return skillLoss ? static_cast(experience * getLostPercent()) : 0; + } + uint32_t getDamageImmunities() const final { + return damageImmunities; + } + uint32_t getConditionImmunities() const final { + return conditionImmunities; + } + uint32_t getConditionSuppressions() const final { + return conditionSuppressions; + } + uint16_t getLookCorpse() const final; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; + + friend class Game; + friend class Npc; + friend class LuaScriptInterface; + friend class Map; + friend class Actions; + friend class IOLoginData; + friend class ProtocolGame; + friend class BehaviourDatabase; + friend class ConjureSpell; +}; + +#endif diff --git a/app/SabrehavenServer/src/position.cpp b/app/SabrehavenServer/src/position.cpp new file mode 100644 index 0000000..a972899 --- /dev/null +++ b/app/SabrehavenServer/src/position.cpp @@ -0,0 +1,73 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "position.h" + +std::ostream& operator<<(std::ostream& os, const Position& pos) +{ + os << "( " << std::setw(5) << std::setfill('0') << pos.x; + os << " / " << std::setw(5) << std::setfill('0') << pos.y; + os << " / " << std::setw(3) << std::setfill('0') << pos.getZ(); + os << " )"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Direction& dir) +{ + switch (dir) { + case DIRECTION_NORTH: + os << "North"; + break; + + case DIRECTION_EAST: + os << "East"; + break; + + case DIRECTION_WEST: + os << "West"; + break; + + case DIRECTION_SOUTH: + os << "South"; + break; + + case DIRECTION_SOUTHWEST: + os << "South-West"; + break; + + case DIRECTION_SOUTHEAST: + os << "South-East"; + break; + + case DIRECTION_NORTHWEST: + os << "North-West"; + break; + + case DIRECTION_NORTHEAST: + os << "North-East"; + break; + + default: + break; + } + + return os; +} diff --git a/app/SabrehavenServer/src/position.h b/app/SabrehavenServer/src/position.h new file mode 100644 index 0000000..abd83ad --- /dev/null +++ b/app/SabrehavenServer/src/position.h @@ -0,0 +1,134 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 +#define FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 + +enum Direction : uint8_t { + DIRECTION_NORTH = 0, + DIRECTION_EAST = 1, + DIRECTION_SOUTH = 2, + DIRECTION_WEST = 3, + + DIRECTION_DIAGONAL_MASK = 4, + DIRECTION_SOUTHWEST = DIRECTION_DIAGONAL_MASK | 0, + DIRECTION_SOUTHEAST = DIRECTION_DIAGONAL_MASK | 1, + DIRECTION_NORTHWEST = DIRECTION_DIAGONAL_MASK | 2, + DIRECTION_NORTHEAST = DIRECTION_DIAGONAL_MASK | 3, + + DIRECTION_LAST = DIRECTION_NORTHEAST, + DIRECTION_NONE = 8, +}; + +struct Position +{ + constexpr Position() = default; + constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {} + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; + } + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay && Position::getDistanceZ(p1, p2) <= deltaz; + } + + inline static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { + return p1.getX() - p2.getX(); + } + inline static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { + return p1.getY() - p2.getY(); + } + inline static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { + return p1.getZ() - p2.getZ(); + } + + inline static int32_t getDistanceX(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetX(p1, p2)); + } + inline static int32_t getDistanceY(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetY(p1, p2)); + } + inline static int16_t getDistanceZ(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetZ(p1, p2)); + } + + uint16_t x = 0; + uint16_t y = 0; + uint8_t z = 0; + + bool operator<(const Position& p) const { + if (z < p.z) { + return true; + } + + if (z > p.z) { + return false; + } + + if (y < p.y) { + return true; + } + + if (y > p.y) { + return false; + } + + if (x < p.x) { + return true; + } + + if (x > p.x) { + return false; + } + + return false; + } + + bool operator>(const Position& p) const { + return ! (*this < p); + } + + bool operator==(const Position& p) const { + return p.x == x && p.y == y && p.z == z; + } + + bool operator!=(const Position& p) const { + return p.x != x || p.y != y || p.z != z; + } + + Position operator+(const Position& p1) const { + return Position(x + p1.x, y + p1.y, z + p1.z); + } + + Position operator-(const Position& p1) const { + return Position(x - p1.x, y - p1.y, z - p1.z); + } + + inline int_fast32_t getX() const { return x; } + inline int_fast32_t getY() const { return y; } + inline int_fast16_t getZ() const { return z; } +}; + +std::ostream& operator<<(std::ostream&, const Position&); +std::ostream& operator<<(std::ostream&, const Direction&); + +#endif diff --git a/app/SabrehavenServer/src/protocol.cpp b/app/SabrehavenServer/src/protocol.cpp new file mode 100644 index 0000000..2686f1a --- /dev/null +++ b/app/SabrehavenServer/src/protocol.cpp @@ -0,0 +1,155 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocol.h" +#include "outputmessage.h" +#include "rsa.h" + +extern RSA g_RSA; + +void Protocol::onSendMessage(const OutputMessage_ptr& msg) const +{ + if (!rawMessages) { + msg->writeMessageLength(); + + if (encryptionEnabled) { + XTEA_encrypt(*msg); + msg->addCryptoHeader(); + } + } +} + +void Protocol::onRecvMessage(NetworkMessage& msg) +{ + if (encryptionEnabled && !XTEA_decrypt(msg)) { + return; + } + + parsePacket(msg); +} + +OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) +{ + //dispatcher thread + if (!outputBuffer) { + outputBuffer = OutputMessagePool::getOutputMessage(); + } + else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { + send(outputBuffer); + outputBuffer = OutputMessagePool::getOutputMessage(); + } + return outputBuffer; +} + +void Protocol::XTEA_encrypt(OutputMessage& msg) const +{ + const uint32_t delta = 0x61C88647; + + // The message must be a multiple of 8 + size_t paddingBytes = msg.getLength() % 8; + if (paddingBytes != 0) { + msg.addPaddingBytes(8 - paddingBytes); + } + + uint8_t* buffer = msg.getOutputBuffer(); + const size_t messageLength = msg.getLength(); + size_t readPos = 0; + const uint32_t k[] = { key[0], key[1], key[2], key[3] }; + while (readPos < messageLength) { + uint32_t v0; + memcpy(&v0, buffer + readPos, 4); + uint32_t v1; + memcpy(&v1, buffer + readPos + 4, 4); + + uint32_t sum = 0; + + for (int32_t i = 32; --i >= 0;) { + v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + sum -= delta; + v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]); + } + + memcpy(buffer + readPos, &v0, 4); + readPos += 4; + memcpy(buffer + readPos, &v1, 4); + readPos += 4; + } +} + +bool Protocol::XTEA_decrypt(NetworkMessage& msg) const +{ + if (((msg.getLength() - 2) & 7) != 0) { + return false; + } + + const uint32_t delta = 0x61C88647; + + uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); + const size_t messageLength = (msg.getLength() - 2); + size_t readPos = 0; + const uint32_t k[] = { key[0], key[1], key[2], key[3] }; + while (readPos < messageLength) { + uint32_t v0; + memcpy(&v0, buffer + readPos, 4); + uint32_t v1; + memcpy(&v1, buffer + readPos + 4, 4); + + uint32_t sum = 0xC6EF3720; + + for (int32_t i = 32; --i >= 0;) { + v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]); + sum += delta; + v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + } + + memcpy(buffer + readPos, &v0, 4); + readPos += 4; + memcpy(buffer + readPos, &v1, 4); + readPos += 4; + } + + int innerLength = msg.get(); + if (innerLength > msg.getLength() - 4) { + return false; + } + + msg.setLength(innerLength); + return true; +} + +bool Protocol::RSA_decrypt(NetworkMessage& msg) +{ + if ((msg.getLength() - msg.getBufferPosition()) != 128) { + return false; + } + + g_RSA.decrypt(reinterpret_cast(msg.getBuffer()) + msg.getBufferPosition()); //does not break strict aliasing + return msg.getByte() == 0; +} + +uint32_t Protocol::getIP() const +{ + if (auto connection = getConnection()) { + return connection->getIP(); + } + + return 0; +} diff --git a/app/SabrehavenServer/src/protocol.h b/app/SabrehavenServer/src/protocol.h new file mode 100644 index 0000000..43ad54d --- /dev/null +++ b/app/SabrehavenServer/src/protocol.h @@ -0,0 +1,97 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2016 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 +#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 + +#include "connection.h" + +class Protocol : public std::enable_shared_from_this +{ +public: + explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {} + virtual ~Protocol() = default; + + // non-copyable + Protocol(const Protocol&) = delete; + Protocol& operator=(const Protocol&) = delete; + + virtual void parsePacket(NetworkMessage&) {} + + virtual void onSendMessage(const OutputMessage_ptr& msg) const; + void onRecvMessage(NetworkMessage& msg); + virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; + virtual void onConnect() {} + + bool isConnectionExpired() const { + return connection.expired(); + } + + Connection_ptr getConnection() const { + return connection.lock(); + } + + uint32_t getIP() const; + + //Use this function for autosend messages only + OutputMessage_ptr getOutputBuffer(int32_t size); + + OutputMessage_ptr& getCurrentBuffer() { + return outputBuffer; + } + + void send(OutputMessage_ptr msg) const { + if (auto connection = getConnection()) { + connection->send(msg); + } + } + +protected: + void disconnect() const { + if (auto connection = getConnection()) { + connection->close(); + } + } + void enableXTEAEncryption() { + encryptionEnabled = true; + } + void setXTEAKey(const uint32_t* key) { + memcpy(this->key, key, sizeof(*key) * 4); + } + + void XTEA_encrypt(OutputMessage& msg) const; + bool XTEA_decrypt(NetworkMessage& msg) const; + static bool RSA_decrypt(NetworkMessage& msg); + + void setRawMessages(bool value) { + rawMessages = value; + } + + virtual void release() {} + friend class Connection; + + OutputMessage_ptr outputBuffer; +private: + const ConnectionWeak_ptr connection; + uint32_t key[4]; + bool encryptionEnabled; + bool rawMessages; +}; + +#endif diff --git a/app/SabrehavenServer/src/protocolgame.cpp b/app/SabrehavenServer/src/protocolgame.cpp new file mode 100644 index 0000000..ce46f0d --- /dev/null +++ b/app/SabrehavenServer/src/protocolgame.cpp @@ -0,0 +1,2145 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "protocolgame.h" + +#include "outputmessage.h" + +#include "player.h" + +#include "configmanager.h" +#include "actions.h" +#include "game.h" +#include "iologindata.h" +#include "waitlist.h" +#include "ban.h" +#include "scheduler.h" + +extern ConfigManager g_config; +extern Actions actions; +extern CreatureEvents* g_creatureEvents; +extern Chat* g_chat; + +void ProtocolGame::release() +{ + //dispatcher thread + if (player && player->client == shared_from_this()) { + player->client.reset(); + player->decrementReferenceCounter(); + player = nullptr; + } + + OutputMessagePool::getInstance().removeProtocolFromAutosend(shared_from_this()); + Protocol::release(); +} + +void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem, bool isFake) +{ + //dispatcher thread + Player* foundPlayer = g_game.getPlayerByName(name); + if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + if (!isFake) { + player = new Player(getThis()); + } + else + { + player = new Player(nullptr); + } + player->setName(name); + player->incrementReferenceCounter(); + player->setID(); + + if (!IOLoginData::preloadPlayer(player, name)) { + disconnectClient("Your character could not be loaded."); + return; + } + + if (IOBan::isPlayerNamelocked(player->getGUID())) { + disconnectClient("Your character has been namelocked."); + return; + } + + if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient("The game is just going down.\nPlease try again later."); + return; + } + + if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient("Server is currently closed.\nPlease try again later."); + return; + } + + if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) { + disconnectClient("You may only login with one character\nof your account at the same time."); + return; + } + + if (!player->hasFlag(PlayerFlag_CannotBeBanned)) { + BanInfo banInfo; + if (IOBan::isAccountBanned(accountId, banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + if (banInfo.expiresAt > 0) { + ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + } else { + ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + } + + disconnectClient(ss.str()); + return; + } + } + + if (!WaitingList::getInstance()->clientLogin(player)) { + uint32_t currentSlot = WaitingList::getInstance()->getClientSlot(player); + uint32_t retryTime = WaitingList::getTime(currentSlot); + std::ostringstream ss; + + ss << "Too many players online.\nYou are at place " + << currentSlot << " on the waiting list."; + + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x16); + output->addString(ss.str()); + output->addByte(retryTime); + send(output); + disconnect(); + return; + } + + if (!IOLoginData::loadPlayerByName(player, name)) { + disconnectClient("Your character could not be loaded."); + return; + } + + player->setOperatingSystem(operatingSystem); + + if (!g_game.placeCreature(player, player->getLoginPosition())) { + if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) { + disconnectClient("Temple position is wrong. Contact the administrator."); + return; + } + } + + if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { + player->registerCreatureEvent("ExtendedOpcode"); + } + + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); + acceptPackets = true; + } else { + if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) { + //Already trying to connect + disconnectClient("You are already logged in."); + return; + } + + if (foundPlayer->client) { + foundPlayer->disconnect(); + foundPlayer->isConnecting = true; + + eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getID(), operatingSystem))); + } else { + connect(foundPlayer->getID(), operatingSystem); + } + } + + OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this()); +} + +void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) +{ + eventConnect = 0; + + Player* foundPlayer = g_game.getPlayerByID(playerId); + if (!foundPlayer || foundPlayer->client) { + disconnectClient("You are already logged in."); + return; + } + + if (isConnectionExpired()) { + //ProtocolGame::release() has been called at this point and the Connection object + //no longer exists, so we return to prevent leakage of the Player. + return; + } + + player = foundPlayer; + player->incrementReferenceCounter(); + + g_chat->removeUserFromAllChannels(*player); + player->setOperatingSystem(operatingSystem); + player->isConnecting = false; + + player->client = getThis(); + sendAddCreature(player, player->getPosition(), 0, false); + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); + acceptPackets = true; +} + +void ProtocolGame::logout(bool displayEffect, bool forced) +{ + //dispatcher thread + if (!player) { + return; + } + + if (!player->isRemoved()) { + if (!forced) { + if (!player->isAccessPlayer()) { + if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE); + return; + } + + if (player->hasCondition(CONDITION_INFIGHT)) { + player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT); + return; + } + } + + //scripting event - onLogout + if (!g_creatureEvents->playerLogout(player)) { + //Let the script handle the error message + return; + } + } + + if (displayEffect && player->getHealth() > 0) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + } + + disconnect(); + + g_game.removeCreature(player); +} + +void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + disconnect(); + return; + } + + OperatingSystem_t operatingSystem = static_cast(msg.get()); + version = msg.get(); + + if (!Protocol::RSA_decrypt(msg)) { + disconnect(); + return; + } + + uint32_t key[4]; + key[0] = msg.get(); + key[1] = msg.get(); + key[2] = msg.get(); + key[3] = msg.get(); + enableXTEAEncryption(); + setXTEAKey(key); + + if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { + NetworkMessage opcodeMessage; + opcodeMessage.addByte(0x32); + opcodeMessage.addByte(0x00); + opcodeMessage.add(0x00); + writeToOutputBuffer(opcodeMessage); + } + + msg.skipBytes(1); // gamemaster flag + + uint32_t accountNumber = msg.get(); + std::string characterName = msg.getString(); + std::string password = msg.getString(); + + /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + //sendUpdateRequest(); + disconnectClient("Use Tibia 7.72 to login!"); + return; + }*/ + + if (g_game.getGameState() == GAME_STATE_STARTUP) { + disconnectClient("Gameworld is starting up. Please wait."); + return; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient("Gameworld is under maintenance. Please re-connect in a while."); + return; + } + + BanInfo banInfo; + if (IOBan::isIpBanned(getIP(), banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient(ss.str()); + return; + } + + uint32_t accountId = IOLoginData::gameworldAuthentication(accountNumber, password, characterName); + if (accountId == 0) { + disconnectClient("Account number or password is not correct."); + return; + } + + Account account; + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Account number or password is not correct."); + return; + } + + //Update premium days + Game::updatePremium(account); + + if (characterName == "King Tibianus") { + std::ostringstream query; + Database* db = Database::getInstance(); + query << "SELECT `name`, `account_id` FROM `players` WHERE `fake_player` = 1 group by `account_id` limit 197"; + DBResult_ptr result; + if ((result = db->storeQuery(query.str()))) { + do { + g_scheduler.addEvent(createSchedulerTask(uniform_random(1000, 1000 * 60 * 60), std::bind(&ProtocolGame::login, getThis(), result->getString("name"), result->getNumber("account_id"), operatingSystem, true))); + } while (result->next()); + } + } + else { + g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem, false))); + } +} + +void ProtocolGame::onConnect() +{ + + +} + +void ProtocolGame::disconnectClient(const std::string& message) const +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x14); + output->addString(message); + send(output); + disconnect(); +} + +void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg) +{ + auto out = getOutputBuffer(msg.getLength()); + out->append(msg); +} + +void ProtocolGame::parsePacket(NetworkMessage& msg) +{ + if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getLength() <= 0) { + return; + } + + uint8_t recvbyte = msg.getByte(); + + if (!player) { + if (recvbyte == 0x0F) { + disconnect(); + } + + return; + } + + //a dead player can not performs actions + if (player->isRemoved() || player->getHealth() <= 0) { + if (recvbyte == 0x0F) { + disconnect(); + return; + } + + if (recvbyte != 0x14) { + return; + } + } + + switch (recvbyte) { + case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break; + case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break; + case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break; + case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode + case 0x64: parseAutoWalk(msg); break; + case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break; + case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break; + case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break; + case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break; + case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break; + case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break; + case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break; + case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break; + case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break; + case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break; + case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; + case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; + case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; + case 0x78: parseThrow(msg); break; + case 0x7D: parseRequestTrade(msg); break; + case 0x7E: parseLookInTrade(msg); break; + case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break; + case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break; + case 0x82: parseUseItem(msg); break; + case 0x83: parseUseItemEx(msg); break; + case 0x84: parseUseWithCreature(msg); break; + case 0x85: parseRotateItem(msg); break; + case 0x87: parseCloseContainer(msg); break; + case 0x88: parseUpArrowContainer(msg); break; + case 0x89: parseTextWindow(msg); break; + case 0x8A: parseHouseWindow(msg); break; + case 0x8C: parseLookAt(msg); break; + case 0x8D: parseLookInBattleList(msg); break; + case 0x96: parseSay(msg); break; + case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; + case 0x98: parseOpenChannel(msg); break; + case 0x99: parseCloseChannel(msg); break; + case 0x9A: parseOpenPrivateChannel(msg); break; + case 0x9B: parseProcessRuleViolationReport(msg); break; + case 0x9C: parseCloseRuleViolationReport(msg); break; + case 0x9D: addGameTask(&Game::playerCancelRuleViolationReport, player->getID()); break; + case 0xA0: parseFightModes(msg); break; + case 0xA1: parseAttack(msg); break; + case 0xA2: parseFollow(msg); break; + case 0xA3: parseInviteToParty(msg); break; + case 0xA4: parseJoinParty(msg); break; + case 0xA5: parseRevokePartyInvite(msg); break; + case 0xA6: parsePassPartyLeadership(msg); break; + case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break; + case 0xA8: parseEnableSharedPartyExperience(msg); break; + case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; + case 0xAB: parseChannelInvite(msg); break; + case 0xAC: parseChannelExclude(msg); break; + case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; + case 0xC9: /* update tile */ break; + case 0xCA: parseUpdateContainer(msg); break; + case 0xCC: parseSeekInContainer(msg); break; + case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; + case 0xD3: parseSetOutfit(msg); break; + case 0xDC: parseAddVip(msg); break; + case 0xDD: parseRemoveVip(msg); break; + case 0xE6: parseBugReport(msg); break; + case 0xE7: /* violation window */ break; + case 0xE8: parseDebugAssert(msg); break; + case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; + case 0xF1: parseQuestLine(msg); break; + default: + std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; + break; + } + + if (msg.isOverrun()) { + disconnect(); + } +} + +void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) +{ + int32_t count; + Item* ground = tile->getGround(); + if (ground) { + msg.addItem(ground); + count = 1; + } + else { + count = 0; + } + + const TileItemVector* items = tile->getItemList(); + if (items) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + msg.addItem(*it); + + count++; + if (count == 9 && tile->getPosition() == player->getPosition()) { + break; + } + else if (count == 10) { + return; + } + } + } + + const CreatureVector* creatures = tile->getCreatures(); + if (creatures) { + bool playerAdded = false; + for (const Creature* creature : boost::adaptors::reverse(*creatures)) { + if (!player->canSeeCreature(creature)) { + continue; + } + + if (tile->getPosition() == player->getPosition() && count == 9 && !playerAdded) { + creature = player; + } + + if (creature->getID() == player->getID()) { + playerAdded = true; + } + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, known, removedKnown); + + if (++count == 10) { + return; + } + } + } + + if (items) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + msg.addItem(*it); + + if (++count == 10) { + return; + } + } + } +} + +void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg) +{ + int32_t skip = -1; + int32_t startz, endz, zstep; + + if (z > 7) { + startz = z - 2; + endz = std::min(MAP_MAX_LAYERS - 1, z + 2); + zstep = 1; + } else { + startz = 7; + endz = 0; + zstep = -1; + } + + for (int32_t nz = startz; nz != endz + zstep; nz += zstep) { + GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip); + } + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } +} + +void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, int32_t offset, int32_t& skip) +{ + for (int32_t nx = 0; nx < width; nx++) { + for (int32_t ny = 0; ny < height; ny++) { + Tile* tile = g_game.map.getTile(x + nx + offset, y + ny + offset, z); + if (tile) { + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + + skip = 0; + GetTileDescription(tile, msg); + } else if (skip == 0xFE) { + msg.addByte(0xFF); + msg.addByte(0xFF); + skip = -1; + } else { + ++skip; + } + } + } +} + +void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown) +{ + auto result = knownCreatureSet.insert(id); + if (!result.second) { + known = true; + return; + } + + known = false; + + if (knownCreatureSet.size() > 150) { + // Look for a creature to remove + for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) { + Creature* creature = g_game.getCreatureByID(*it); + if (!canSee(creature)) { + removedKnown = *it; + knownCreatureSet.erase(it); + return; + } + } + + // Bad situation. Let's just remove anyone. + auto it = knownCreatureSet.begin(); + if (*it == id) { + ++it; + } + + removedKnown = *it; + knownCreatureSet.erase(it); + } else { + removedKnown = 0; + } +} + +bool ProtocolGame::canSee(const Creature* c) const +{ + if (!c || !player || c->isRemoved()) { + return false; + } + + if (!player->canSeeCreature(c)) { + return false; + } + + return canSee(c->getPosition()); +} + +bool ProtocolGame::canSee(const Position& pos) const +{ + return canSee(pos.x, pos.y, pos.z); +} + +bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const +{ + if (!player) { + return false; + } + + const Position& myPos = player->getPosition(); + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (std::abs(myPos.getZ() - z) > 2) { + return false; + } + } + + //negative offset means that the action taken place is on a lower floor than ourself + int32_t offsetz = myPos.getZ() - z; + if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) && + (y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) { + return true; + } + return false; +} + +// Parse methods +void ProtocolGame::parseChannelInvite(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerChannelInvite, player->getID(), name); +} + +void ProtocolGame::parseChannelExclude(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerChannelExclude, player->getID(), name); +} + +void ProtocolGame::parseOpenChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.get(); + addGameTask(&Game::playerOpenChannel, player->getID(), channelId); +} + +void ProtocolGame::parseCloseChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.get(); + addGameTask(&Game::playerCloseChannel, player->getID(), channelId); +} + +void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) +{ + const std::string receiver = msg.getString(); + addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver); +} + +void ProtocolGame::parseAutoWalk(NetworkMessage& msg) +{ + uint8_t numdirs = msg.getByte(); + if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 4)) { + return; + } + + msg.skipBytes(numdirs); + + std::forward_list path; + for (uint8_t i = 0; i < numdirs; ++i) { + uint8_t rawdir = msg.getPreviousByte(); + switch (rawdir) { + case 1: path.push_front(DIRECTION_EAST); break; + case 2: path.push_front(DIRECTION_NORTHEAST); break; + case 3: path.push_front(DIRECTION_NORTH); break; + case 4: path.push_front(DIRECTION_NORTHWEST); break; + case 5: path.push_front(DIRECTION_WEST); break; + case 6: path.push_front(DIRECTION_SOUTHWEST); break; + case 7: path.push_front(DIRECTION_SOUTH); break; + case 8: path.push_front(DIRECTION_SOUTHEAST); break; + default: break; + } + } + + if (path.empty()) { + return; + } + + addGameTask(&Game::playerAutoWalk, player->getID(), path); +} + +void ProtocolGame::parseSetOutfit(NetworkMessage& msg) +{ + Outfit_t newOutfit; + newOutfit.lookType = msg.get(); + newOutfit.lookHead = msg.getByte(); + newOutfit.lookBody = msg.getByte(); + newOutfit.lookLegs = msg.getByte(); + newOutfit.lookFeet = msg.getByte(); + newOutfit.lookAddons = msg.getByte(); + addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); +} + +void ProtocolGame::parseUseItem(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + uint8_t index = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId); +} + +void ProtocolGame::parseUseItemEx(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t fromSpriteId = msg.get(); + uint8_t fromStackPos = msg.getByte(); + Position toPos = msg.getPosition(); + uint16_t toSpriteId = msg.get(); + uint8_t toStackPos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); +} + +void ProtocolGame::parseUseWithCreature(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t fromStackPos = msg.getByte(); + uint32_t creatureId = msg.get(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId); +} + +void ProtocolGame::parseCloseContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerCloseContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerMoveUpContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpdateContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerUpdateContainer, player->getID(), cid); +} + +void ProtocolGame::parseThrow(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t fromStackpos = msg.getByte(); + Position toPos = msg.getPosition(); + uint8_t count = msg.getByte(); + + if (toPos != fromPos) { + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count); + } +} + +void ProtocolGame::parseLookAt(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + msg.skipBytes(2); // spriteId + uint8_t stackpos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, stackpos); +} + +void ProtocolGame::parseLookInBattleList(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId); +} + +void ProtocolGame::parseSay(NetworkMessage& msg) +{ + std::string receiver; + uint16_t channelId; + + SpeakClasses type = static_cast(msg.getByte()); + switch (type) { + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: + receiver = msg.getString(); + channelId = 0; + break; + + case TALKTYPE_CHANNEL_Y: + case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: + channelId = msg.get(); + break; + + default: + channelId = 0; + break; + } + + const std::string text = msg.getString(); + if (text.length() > 255 || text.find('\n') != std::string::npos) { + return; + } + + addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text); +} + +void ProtocolGame::parseFightModes(NetworkMessage& msg) +{ + uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive + uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fightning, 1 - chase opponent + uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked + // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 + + chaseMode_t chaseMode; + if (rawChaseMode == 1) { + chaseMode = CHASEMODE_FOLLOW; + } else { + chaseMode = CHASEMODE_STANDSTILL; + } + + fightMode_t fightMode; + if (rawFightMode == 1) { + fightMode = FIGHTMODE_ATTACK; + } else if (rawFightMode == 2) { + fightMode = FIGHTMODE_BALANCED; + } else { + fightMode = FIGHTMODE_DEFENSE; + } + + addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, chaseMode, rawSecureMode != 0); +} + +void ProtocolGame::parseAttack(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseFollow(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseProcessRuleViolationReport(NetworkMessage& msg) +{ + const std::string reporter = msg.getString(); + addGameTask(&Game::playerProcessRuleViolationReport, player->getID(), reporter); +} + +void ProtocolGame::parseCloseRuleViolationReport(NetworkMessage& msg) +{ + const std::string reporter = msg.getString(); + addGameTask(&Game::playerCloseRuleViolationReport, player->getID(), reporter); +} + +void ProtocolGame::parseTextWindow(NetworkMessage& msg) +{ + uint32_t windowTextId = msg.get(); + const std::string newText = msg.getString(); + addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText); +} + +void ProtocolGame::parseHouseWindow(NetworkMessage& msg) +{ + uint8_t doorId = msg.getByte(); + uint32_t id = msg.get(); + const std::string text = msg.getString(); + addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); +} + +void ProtocolGame::parseRequestTrade(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + uint32_t playerId = msg.get(); + addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId); +} + +void ProtocolGame::parseLookInTrade(NetworkMessage& msg) +{ + bool counterOffer = (msg.getByte() == 0x01); + uint8_t index = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index); +} + +void ProtocolGame::parseAddVip(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerRequestAddVip, player->getID(), name); +} + +void ProtocolGame::parseRemoveVip(NetworkMessage& msg) +{ + uint32_t guid = msg.get(); + addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); +} + +void ProtocolGame::parseRotateItem(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); +} + +void ProtocolGame::parseBugReport(NetworkMessage& msg) +{ + std::string bug = msg.getString(); + addGameTask(&Game::playerReportBug, player->getID(), bug); +} + +void ProtocolGame::parseDebugAssert(NetworkMessage& msg) +{ + if (debugAssertSent) { + return; + } + + debugAssertSent = true; + + std::string assertLine = msg.getString(); + std::string date = msg.getString(); + std::string description = msg.getString(); + std::string comment = msg.getString(); + addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment); +} + +void ProtocolGame::parseInviteToParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerInviteToParty, player->getID(), targetId); +} + +void ProtocolGame::parseJoinParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerJoinParty, player->getID(), targetId); +} + +void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId); +} + +void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); +} + +void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg) +{ + bool sharedExpActive = msg.getByte() == 1; + addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive); +} + +void ProtocolGame::parseQuestLine(NetworkMessage& msg) +{ + uint16_t questId = msg.get(); + addGameTask(&Game::playerShowQuestLine, player->getID(), questId); +} + +void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) +{ + uint8_t containerId = msg.getByte(); + uint16_t index = msg.get(); + addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index); +} + +// Send methods +void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) +{ + NetworkMessage msg; + msg.addByte(0xAD); + msg.addString(receiver); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x8E); + msg.add(creature->getID()); + AddOutfit(msg, outfit); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureLight(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + AddCreatureLight(msg, creature); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendWorldLight(const LightInfo& lightInfo) +{ + NetworkMessage msg; + AddWorldLight(msg, lightInfo); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureShield(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x91); + msg.add(creature->getID()); + msg.addByte(player->getPartyShield(creature->getPlayer())); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSkull(const Creature* creature) +{ + if (g_game.getWorldType() != WORLD_TYPE_PVP) { + return; + } + + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x90); + msg.add(creature->getID()); + msg.addByte(player->getSkullClient(creature)); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x86); + msg.add(creature->getID()); + msg.addByte(color); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveRuleViolationReport(const std::string& name) +{ + NetworkMessage msg; + msg.addByte(0xAF); + msg.addString(name); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendLockRuleViolation() +{ + NetworkMessage msg; + msg.addByte(0xB1); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRuleViolationCancel(const std::string& name) +{ + NetworkMessage msg; + msg.addByte(0xB0); + msg.addString(name); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRuleViolationsChannel(uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xAE); + msg.add(channelId); + auto it = g_game.getRuleViolationReports().begin(); + for (; it != g_game.getRuleViolationReports().end(); ++it) { + const RuleViolation& rvr = it->second; + if (rvr.pending) { + Player* reporter = g_game.getPlayerByID(rvr.reporterId); + if (reporter) { + msg.addByte(0xAA); + msg.add(0); + msg.addString(reporter->getName()); + msg.addByte(TALKTYPE_RVR_CHANNEL); + msg.add(0); + msg.addString(rvr.text); + } + } + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendStats() +{ + NetworkMessage msg; + AddPlayerStats(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextMessage(const TextMessage& message) +{ + NetworkMessage msg; + msg.addByte(0xB4); + msg.addByte(message.type); + msg.addString(message.text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x84); + msg.addPosition(pos); + msg.addByte(color); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendClosePrivate(uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xB3); + msg.add(channelId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.addByte(0xB2); + msg.add(channelId); + msg.addString(channelName); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelsDialog() +{ + NetworkMessage msg; + msg.addByte(0xAB); + + const ChannelList& list = g_chat->getChannelList(*player); + msg.addByte(list.size()); + for (ChatChannel* channel : list) { + msg.add(channel->getId()); + msg.addString(channel->getName()); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.addByte(0xAC); + + msg.add(channelId); + msg.addString(channelName); + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) +{ + NetworkMessage msg; + msg.addByte(0xAA); + msg.add(0x00); + msg.addString(author); + msg.add(0x00); + msg.addByte(type); + msg.add(channel); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendIcons(uint16_t icons) +{ + NetworkMessage msg; + msg.addByte(0xA2); + msg.add(icons); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) +{ + NetworkMessage msg; + msg.addByte(0x6E); + + msg.addByte(cid); + + msg.addItem(container); + msg.addString(container->getName()); + + msg.addByte(container->capacity()); + + msg.addByte(hasParent ? 0x01 : 0x00); + + uint32_t containerSize = container->size(); + if (firstIndex < containerSize) { + uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), std::numeric_limits::max()); + + msg.addByte(itemsToSend); + for (auto it = container->getItemList().begin() + firstIndex, end = it + itemsToSend; it != end; ++it) { + msg.addItem(*it); + } + } else { + msg.addByte(0x00); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLog() +{ + NetworkMessage msg; + msg.addByte(0xF0); + msg.add(g_game.quests.getQuestsCount(player)); + + for (const Quest& quest : g_game.quests.getQuests()) { + if (quest.isStarted(player)) { + msg.add(quest.getID()); + msg.addString(quest.getName()); + msg.addByte(quest.isCompleted(player)); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLine(const Quest* quest) +{ + NetworkMessage msg; + msg.addByte(0xF1); + msg.add(quest->getID()); + msg.addByte(quest->getMissionsCount(player)); + + for (const Mission& mission : quest->getMissions()) { + if (mission.isStarted(player)) { + msg.addString(mission.getName(player)); + msg.addString(mission.getDescription(player)); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) +{ + NetworkMessage msg; + + if (ack) { + msg.addByte(0x7D); + } else { + msg.addByte(0x7E); + } + + msg.addString(traderName); + + if (const Container* tradeContainer = item->getContainer()) { + std::list listContainer {tradeContainer}; + std::list itemList {tradeContainer}; + while (!listContainer.empty()) { + const Container* container = listContainer.front(); + listContainer.pop_front(); + + for (Item* containerItem : container->getItemList()) { + Container* tmpContainer = containerItem->getContainer(); + if (tmpContainer) { + listContainer.push_back(tmpContainer); + } + itemList.push_back(containerItem); + } + } + + msg.addByte(itemList.size()); + for (const Item* listItem : itemList) { + msg.addItem(listItem); + } + } else { + msg.addByte(0x01); + msg.addItem(item); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseTrade() +{ + NetworkMessage msg; + msg.addByte(0x7F); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseContainer(uint8_t cid) +{ + NetworkMessage msg; + msg.addByte(0x6F); + msg.addByte(cid); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6B); + msg.addPosition(creature->getPosition()); + msg.addByte(stackPos); + msg.add(0x63); + msg.add(creature->getID()); + msg.addByte(creature->getDirection()); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/) +{ + NetworkMessage msg; + msg.addByte(0xAA); + + static uint32_t statementId = 0; + msg.add(++statementId); + + msg.addString(creature->getName()); + + //Add level only for players + if (const Player* speaker = creature->getPlayer()) { + msg.add(speaker->getLevel()); + } + else { + msg.add(0x00); + } + + msg.addByte(type); + if (pos) { + msg.addPosition(*pos); + } + else { + msg.addPosition(creature->getPosition()); + } + + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xAA); + + static uint32_t statementId = 0; + msg.add(++statementId); + if (!creature) { + msg.add(0x00); + } + else if (type == TALKTYPE_CHANNEL_R2) { + msg.add(0x00); + type = TALKTYPE_CHANNEL_R1; + } + else { + msg.addString(creature->getName()); + //Add level only for players + if (const Player* speaker = creature->getPlayer()) { + msg.add(speaker->getLevel()); + } + else { + msg.add(0x00); + } + } + + msg.addByte(type); + msg.add(channelId); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0xAA); + static uint32_t statementId = 0; + msg.add(++statementId); + if (speaker) { + msg.addString(speaker->getName()); + msg.add(speaker->getLevel()); + } + else { + msg.add(0x00); + } + msg.addByte(type); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelTarget() +{ + NetworkMessage msg; + msg.addByte(0xA3); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed) +{ + NetworkMessage msg; + msg.addByte(0x8F); + msg.add(creature->getID()); + msg.add(speed); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelWalk() +{ + NetworkMessage msg; + msg.addByte(0xB5); + msg.addByte(player->getDirection()); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSkills() +{ + NetworkMessage msg; + AddPlayerSkills(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPing() +{ + if (!player) { + return; + } + + NetworkMessage msg; + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(0x1D); + } else { + // classic clients ping + msg.addByte(0x1E); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPingBack() +{ + NetworkMessage msg; + msg.addByte(0x1E); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type) +{ + NetworkMessage msg; + msg.addByte(0x85); + msg.addPosition(from); + msg.addPosition(to); + msg.addByte(type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x83); + msg.addPosition(pos); + msg.addByte(type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureHealth(const Creature* creature) +{ + NetworkMessage msg; + msg.addByte(0x8C); + msg.add(creature->getID()); + + if (creature->isHealthHidden()) { + msg.addByte(0x00); + } + else { + msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + } + + writeToOutputBuffer(msg); +} + +//tile +void ProtocolGame::sendMapDescription(const Position& pos) +{ + NetworkMessage msg; + msg.addByte(0x64); + msg.addPosition(player->getPosition()); + GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6A); + msg.addPosition(pos); + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6B); + msg.addPosition(pos); + msg.addByte(stackpos); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveTileThing(const Position& pos, uint32_t stackpos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + RemoveTileThing(msg, pos, stackpos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x69); + msg.addPosition(pos); + + if (tile) { + GetTileDescription(tile, msg); + msg.addByte(0x00); + msg.addByte(0xFF); + } else { + msg.addByte(0x01); + msg.addByte(0xFF); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendFightModes() +{ + NetworkMessage msg; + msg.addByte(0xA7); + msg.addByte(player->fightMode); + msg.addByte(player->chaseMode); + msg.addByte(player->secureMode); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin) +{ + if (!canSee(pos)) { + return; + } + + if (creature != player) { + if (stackpos != -1) { + NetworkMessage msg; + msg.addByte(0x6A); + msg.addPosition(pos); + + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, known, removedKnown); + writeToOutputBuffer(msg); + } + + if (isLogin) { + sendMagicEffect(pos, CONST_ME_TELEPORT); + } + return; + } + + NetworkMessage msg; + msg.addByte(0x0A); + + msg.add(player->getID()); + msg.addByte(0x32); // beat duration (50) + msg.addByte(0x00); + + // can report bugs? + if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { + msg.addByte(0x01); + } else { + msg.addByte(0x00); + } + + if (player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + msg.addByte(0x0B); + for (uint8_t i = 0; i < 32; i++) { + msg.addByte(0xFF); + } + } + + writeToOutputBuffer(msg); + + sendMapDescription(pos); + + if (isLogin) { + sendMagicEffect(pos, CONST_ME_TELEPORT); + } + + for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + sendInventoryItem(static_cast(i), player->getInventoryItem(static_cast(i))); + } + + sendStats(); + sendSkills(); + + //gameworld light-settings + sendWorldLight(g_game.getWorldLightInfo()); + + //player light level + sendCreatureLight(creature); + + //player vip + sendVIPEntries(); + + player->sendIcons(); +} + +void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) +{ + if (creature == player) { + if (oldStackPos >= 10) { + sendMapDescription(newPos); + } else if (teleport) { + NetworkMessage msg; + RemoveTileThing(msg, oldPos, oldStackPos); + writeToOutputBuffer(msg); + sendMapDescription(newPos); + } else { + NetworkMessage msg; + if (oldPos.z == 7 && newPos.z >= 8) { + RemoveTileThing(msg, oldPos, oldStackPos); + } else { + msg.addByte(0x6D); + msg.addPosition(oldPos); + msg.addByte(oldStackPos); + msg.addPosition(newPos); + } + + if (newPos.z > oldPos.z) { + MoveDownCreature(msg, creature, newPos, oldPos); + } else if (newPos.z < oldPos.z) { + MoveUpCreature(msg, creature, newPos, oldPos); + } + + if (oldPos.y > newPos.y) { // north, for old x + msg.addByte(0x65); + GetMapDescription(oldPos.x - 8, newPos.y - 6, newPos.z, 18, 1, msg); + } else if (oldPos.y < newPos.y) { // south, for old x + msg.addByte(0x67); + GetMapDescription(oldPos.x - 8, newPos.y + 7, newPos.z, 18, 1, msg); + } + + if (oldPos.x < newPos.x) { // east, [with new y] + msg.addByte(0x66); + GetMapDescription(newPos.x + 9, newPos.y - 6, newPos.z, 1, 14, msg); + } else if (oldPos.x > newPos.x) { // west, [with new y] + msg.addByte(0x68); + GetMapDescription(newPos.x - 8, newPos.y - 6, newPos.z, 1, 14, msg); + } + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos) && canSee(creature->getPosition())) { + if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) { + sendRemoveTileThing(oldPos, oldStackPos); + sendAddCreature(creature, newPos, newStackPos, false); + } else { + NetworkMessage msg; + msg.addByte(0x6D); + msg.addPosition(oldPos); + msg.addByte(oldStackPos); + msg.addPosition(creature->getPosition()); + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos)) { + sendRemoveTileThing(oldPos, oldStackPos); + } else if (canSee(creature->getPosition())) { + sendAddCreature(creature, newPos, newStackPos, false); + } +} + +void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) +{ + NetworkMessage msg; + if (item) { + msg.addByte(0x78); + msg.addByte(slot); + msg.addItem(item); + } else { + msg.addByte(0x79); + msg.addByte(slot); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddContainerItem(uint8_t cid, const Item* item) +{ + NetworkMessage msg; + msg.addByte(0x70); + msg.addByte(cid); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item) +{ + NetworkMessage msg; + msg.addByte(0x71); + msg.addByte(cid); + msg.addByte(static_cast(slot)); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot) +{ + NetworkMessage msg; + msg.addByte(0x72); + msg.addByte(cid); + msg.addByte(static_cast(slot)); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite) +{ + NetworkMessage msg; + msg.addByte(0x96); + msg.add(windowTextId); + msg.addItem(item, true); + + if (canWrite) { + msg.add(maxlen); + msg.addString(item->getText()); + } else { + const std::string& text = item->getText(); + msg.add(text.size()); + msg.addString(text); + } + + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + msg.addString(writer); + } else { + msg.add(0x00); + } + + if (g_game.getClientVersion() >= CLIENT_VERSION_790) { + time_t writtenDate = item->getDate(); + if (writtenDate != 0) { + msg.addString(formatDateShort(writtenDate)); + } + else { + msg.add(0x00); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x96); + msg.add(windowTextId); + msg.addItem(itemId, 1, true); + msg.add(text.size()); + msg.addString(text); + msg.add(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x97); + msg.addByte(0x00); + msg.add(windowTextId); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendOutfitWindow() +{ + NetworkMessage msg; + msg.addByte(0xC8); + + Outfit_t currentOutfit = player->getDefaultOutfit(); + AddOutfit(msg, currentOutfit); + + const ClientVersion_t clientVersion = g_game.getClientVersion(); + std::vector protocolOutfits; + if (player->isAccessPlayer()) { + static const std::string gamemasterOutfitName = "Gamemaster"; + protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0); + } + + const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); + protocolOutfits.reserve(outfits.size()); + for (const Outfit& outfit : outfits) { + uint8_t addons; + if (!player->getOutfitAddons(outfit, addons)) { + continue; + } + + protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); + if (CLIENT_VERSION_780 <= clientVersion && clientVersion <= CLIENT_VERSION_792) { + if (protocolOutfits.size() == 20) { // Game client doesn't allow more than 15 outfits in 780-792 + break; + } + } + } + + msg.addByte(protocolOutfits.size()); + for (const ProtocolOutfit& outfit : protocolOutfits) { + msg.add(outfit.lookType); + if (clientVersion > CLIENT_VERSION_781) { + msg.addString(outfit.name); + } + msg.addByte(outfit.addons); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) +{ + NetworkMessage msg; + msg.addByte(newStatus == VIPSTATUS_ONLINE ? 0xD3 : 0xD4); + msg.add(guid); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, VipStatus_t status) +{ + NetworkMessage msg; + msg.addByte(0xD2); + msg.add(guid); + msg.addString(name); + msg.addByte(status); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendVIPEntries() +{ + const std::forward_list& vipEntries = IOLoginData::getVIPEntries(player->getAccount()); + + for (const VIPEntry& entry : vipEntries) { + VipStatus_t vipStatus = VIPSTATUS_ONLINE; + + Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); + if (!vipPlayer || (vipPlayer->isInGhostMode() && !player->isAccessPlayer())) { + vipStatus = VIPSTATUS_OFFLINE; + } + + sendVIP(entry.guid, entry.name, vipStatus); + } +} + +////////////// Add common messages +void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) +{ + const Player* otherPlayer = creature->getPlayer(); + + if (known) { + msg.add(0x62); + msg.add(creature->getID()); + } else { + msg.add(0x61); + msg.add(remove); + msg.add(creature->getID()); + msg.addString(creature->getName()); + } + + if (creature->isHealthHidden()) { + msg.addByte(0x00); + } + else { + msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + } + + msg.addByte(creature->getDirection()); + + if (!creature->isInGhostMode() && !creature->isInvisible()) { + AddOutfit(msg, creature->getCurrentOutfit()); + } else { + static Outfit_t outfit; + AddOutfit(msg, outfit); + } + + LightInfo lightInfo = creature->getCreatureLight(); + msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); + msg.addByte(lightInfo.color); + + msg.add(creature->getStepSpeed()); + + msg.addByte(player->getSkullClient(creature)); + msg.addByte(player->getPartyShield(otherPlayer)); +} + +void ProtocolGame::AddPlayerStats(NetworkMessage& msg) +{ + msg.addByte(0xA0); + + msg.add(std::min(player->getHealth(), std::numeric_limits::max())); + msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); + + msg.add(player->getFreeCapacity()); + + msg.add(std::min(player->getExperience(), 0x7FFFFFFF)); + + msg.add(player->getLevel()); + msg.addByte(player->getLevelPercent()); + + msg.add(std::min(player->getMana(), std::numeric_limits::max())); + msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); + + msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); + msg.addByte(player->getMagicLevelPercent()); + + msg.addByte(player->getSoul()); + + msg.add(player->getStaminaMinutes()); +} + +void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) +{ + msg.addByte(0xA1); + + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + msg.addByte(std::min(player->getSkillLevel(i), std::numeric_limits::max())); + msg.addByte(player->getSkillPercent(i)); + } +} + +void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) +{ + msg.add(outfit.lookType); + + if (outfit.lookType != 0) { + msg.addByte(outfit.lookHead); + msg.addByte(outfit.lookBody); + msg.addByte(outfit.lookLegs); + msg.addByte(outfit.lookFeet); + msg.addByte(outfit.lookAddons); + } else { + msg.addItemId(outfit.lookTypeEx); + } +} + +void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo) +{ + msg.addByte(0x82); + msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.addByte(lightInfo.color); +} + +void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) +{ + LightInfo lightInfo = creature->getCreatureLight(); + + msg.addByte(0x8D); + msg.add(creature->getID()); + msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.addByte(lightInfo.color); +} + +//tile +void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos) +{ + if (stackpos >= 10) { + return; + } + + msg.addByte(0x6C); + msg.addPosition(pos); + msg.addByte(stackpos); +} + +void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +{ + if (creature != player) { + return; + } + + //floor change up + msg.addByte(0xBE); + + //going to surface + if (newPos.z == 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 5, 18, 14, 3, skip); //(floor 7 and 6 already set) + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 4, 18, 14, 4, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 3, 18, 14, 5, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 2, 18, 14, 6, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 1, 18, 14, 7, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 0, 18, 14, 8, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + //underground, going one floor up (still underground) + else if (newPos.z > 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, oldPos.getZ() - 3, 18, 14, 3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + + //moving up a floor up makes us out of sync + //west + msg.addByte(0x68); + GetMapDescription(oldPos.x - 8, oldPos.y - 5, newPos.z, 1, 14, msg); + + //north + msg.addByte(0x65); + GetMapDescription(oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 1, msg); +} + +void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +{ + if (creature != player) { + return; + } + + //floor change down + msg.addByte(0xBF); + + //going from surface to underground + if (newPos.z == 8) { + int32_t skip = -1; + + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 14, -1, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 1, 18, 14, -2, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + //going further down + else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + + //moving down a floor makes us out of sync + //east + msg.addByte(0x66); + GetMapDescription(oldPos.x + 9, oldPos.y - 7, newPos.z, 1, 14, msg); + + //south + msg.addByte(0x67); + GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); +} + +void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) +{ + uint8_t opcode = msg.getByte(); + const std::string& buffer = msg.getString(); + + // process additional opcodes via lua script event + addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer); +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/protocolgame.h b/app/SabrehavenServer/src/protocolgame.h new file mode 100644 index 0000000..03a2baa --- /dev/null +++ b/app/SabrehavenServer/src/protocolgame.h @@ -0,0 +1,280 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 +#define FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 + +#include "protocol.h" +#include "chat.h" +#include "creature.h" +#include "tasks.h" + +class NetworkMessage; +class Player; +class Game; +class House; +class Container; +class Tile; +class Connection; +class Quest; +class ProtocolGame; +typedef std::shared_ptr ProtocolGame_ptr; + +extern Game g_game; + +struct TextMessage +{ + MessageClasses type; + std::string text; + TextMessage() = default; + TextMessage(MessageClasses type, std::string text) : type(type), text(std::move(text)) {} +}; + +class ProtocolGame final : public Protocol +{ + public: + // static protocol information + enum { server_sends_first = true }; + enum { protocol_identifier = 0 }; // Not required as we send first + + static const char* protocol_name() { + return "gameworld protocol"; + } + + explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} + + void login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem, bool isFake); + void logout(bool displayEffect, bool forced); + + uint16_t getVersion() const { + return version; + } + + private: + ProtocolGame_ptr getThis() { + return std::static_pointer_cast(shared_from_this()); + } + void connect(uint32_t playerId, OperatingSystem_t operatingSystem); + void disconnectClient(const std::string& message) const; + void writeToOutputBuffer(const NetworkMessage& msg); + + void release() final; + + void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); + + bool canSee(int32_t x, int32_t y, int32_t z) const; + bool canSee(const Creature*) const; + bool canSee(const Position& pos) const; + + // we have all the parse methods + void parsePacket(NetworkMessage& msg) final; + void onRecvFirstMessage(NetworkMessage& msg) final; + void onConnect() override; + + //Parse methods + void parseAutoWalk(NetworkMessage& msg); + void parseSetOutfit(NetworkMessage& msg); + void parseSay(NetworkMessage& msg); + void parseLookAt(NetworkMessage& msg); + void parseLookInBattleList(NetworkMessage& msg); + void parseFightModes(NetworkMessage& msg); + void parseAttack(NetworkMessage& msg); + void parseFollow(NetworkMessage& msg); + + void parseProcessRuleViolationReport(NetworkMessage& msg); + void parseCloseRuleViolationReport(NetworkMessage& msg); + + void parseBugReport(NetworkMessage& msg); + void parseDebugAssert(NetworkMessage& msg); + + void parseThrow(NetworkMessage& msg); + void parseUseItemEx(NetworkMessage& msg); + void parseUseWithCreature(NetworkMessage& msg); + void parseUseItem(NetworkMessage& msg); + void parseCloseContainer(NetworkMessage& msg); + void parseUpArrowContainer(NetworkMessage& msg); + void parseUpdateContainer(NetworkMessage& msg); + void parseTextWindow(NetworkMessage& msg); + void parseHouseWindow(NetworkMessage& msg); + + void parseQuestLine(NetworkMessage& msg); + + void parseInviteToParty(NetworkMessage& msg); + void parseJoinParty(NetworkMessage& msg); + void parseRevokePartyInvite(NetworkMessage& msg); + void parsePassPartyLeadership(NetworkMessage& msg); + void parseEnableSharedPartyExperience(NetworkMessage& msg); + + void parseSeekInContainer(NetworkMessage& msg); + + //trade methods + void parseRequestTrade(NetworkMessage& msg); + void parseLookInTrade(NetworkMessage& msg); + + //VIP methods + void parseAddVip(NetworkMessage& msg); + void parseRemoveVip(NetworkMessage& msg); + + void parseRotateItem(NetworkMessage& msg); + + //Channel tabs + void parseChannelInvite(NetworkMessage& msg); + void parseChannelExclude(NetworkMessage& msg); + void parseOpenChannel(NetworkMessage& msg); + void parseOpenPrivateChannel(NetworkMessage& msg); + void parseCloseChannel(NetworkMessage& msg); + + //Send functions + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); + void sendClosePrivate(uint16_t channelId); + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); + void sendChannelsDialog(); + void sendChannel(uint16_t channelId, const std::string& channelName); + void sendOpenPrivateChannel(const std::string& receiver); + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); + void sendIcons(uint16_t icons); + + void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); + void sendMagicEffect(const Position& pos, uint8_t type); + void sendCreatureHealth(const Creature* creature); + void sendSkills(); + void sendPing(); + void sendPingBack(); + void sendCreatureTurn(const Creature* creature, uint32_t stackpos); + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr); + + void sendQuestLog(); + void sendQuestLine(const Quest* quest); + + void sendCancelWalk(); + void sendChangeSpeed(const Creature* creature, uint32_t speed); + void sendCancelTarget(); + void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); + void sendStats(); + void sendTextMessage(const TextMessage& message); + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text); + + void sendCreatureShield(const Creature* creature); + void sendCreatureSkull(const Creature* creature); + + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); + void sendCloseTrade(); + + void sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite); + void sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text); + void sendHouseWindow(uint32_t windowTextId, const std::string& text); + void sendOutfitWindow(); + + void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); + void sendVIP(uint32_t guid, const std::string& name, VipStatus_t status); + void sendVIPEntries(); + + void sendFightModes(); + + void sendCreatureLight(const Creature* creature); + void sendWorldLight(const LightInfo& lightInfo); + + void sendCreatureSquare(const Creature* creature, SquareColor_t color); + + //rule violations + void sendRemoveRuleViolationReport(const std::string& name); + void sendLockRuleViolation(); + void sendRuleViolationCancel(const std::string& name); + void sendRuleViolationsChannel(uint16_t channelId); + + //tiles + void sendMapDescription(const Position& pos); + + void sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos); + void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); + void sendRemoveTileThing(const Position& pos, uint32_t stackpos); + void sendUpdateTile(const Tile* tile, const Position& pos); + + void sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin); + void sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, + const Position& oldPos, int32_t oldStackPos, bool teleport); + + //containers + void sendAddContainerItem(uint8_t cid, const Item* item); + void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot); + + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); + void sendCloseContainer(uint8_t cid); + + //inventory + void sendInventoryItem(slots_t slot, const Item* item); + + //Help functions + + // translate a tile to clientreadable format + void GetTileDescription(const Tile* tile, NetworkMessage& msg); + + // translate a floor to clientreadable format + void GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, int32_t offset, int32_t& skip); + + // translate a map area to clientreadable format + void GetMapDescription(int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, NetworkMessage& msg); + + void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); + void AddPlayerStats(NetworkMessage& msg); + void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); + void AddPlayerSkills(NetworkMessage& msg); + void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo); + void AddCreatureLight(NetworkMessage& msg, const Creature* creature); + + //tiles + static void RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos); + + void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + + //otclient + void parseExtendedOpcode(NetworkMessage& msg); + + friend class Player; + + // Helpers so we don't need to bind every time + template + void addGameTask(Callable function, Args&&... args) { + g_dispatcher.addTask(createTask(std::bind(function, &g_game, std::forward(args)...))); + } + + template + void addGameTaskTimed(uint32_t delay, Callable function, Args&&... args) { + g_dispatcher.addTask(createTask(delay, std::bind(function, &g_game, std::forward(args)...))); + } + + std::unordered_set knownCreatureSet; + Player* player = nullptr; + + uint32_t eventConnect = 0; + uint32_t challengeTimestamp = 0; + uint16_t version; + + uint8_t challengeRandom = 0; + + bool debugAssertSent = false; + bool acceptPackets = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/protocollogin.cpp b/app/SabrehavenServer/src/protocollogin.cpp new file mode 100644 index 0000000..5a632e7 --- /dev/null +++ b/app/SabrehavenServer/src/protocollogin.cpp @@ -0,0 +1,211 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocollogin.h" + +#include "outputmessage.h" +#include "tasks.h" + +#include "configmanager.h" +#include "iologindata.h" +#include "ban.h" +#include "game.h" + +extern ConfigManager g_config; +extern Game g_game; + +void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version) +{ + auto output = OutputMessagePool::getOutputMessage(); + + output->addByte(version >= 1076 ? 0x0B : 0x0A); + output->addString(message); + send(output); + + disconnect(); +} + +void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version) +{ + Account account; + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Accountnumber or password is not correct.", version); + return; + } + + auto output = OutputMessagePool::getOutputMessage(); + + //Update premium days + Game::updatePremium(account); + + const std::string& motd = g_config.getString(ConfigManager::MOTD); + if (!motd.empty()) { + //Add MOTD + output->addByte(0x14); + + std::ostringstream ss; + ss << g_game.getMotdNum() << "\n" << motd; + output->addString(ss.str()); + } + + //Add char list + output->addByte(0x64); + + uint8_t size = std::min(std::numeric_limits::max(), account.characters.size()); + output->addByte(size); + for (uint8_t i = 0; i < size; i++) { + output->addString(account.characters[i]); + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->add(inet_addr(g_config.getString(ConfigManager::IP).c_str())); + output->add(g_config.getNumber(ConfigManager::GAME_PORT)); + } + + //Add premium days + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + output->add(0xFFFF); + } + else { + output->add(account.premiumDays); + } + + send(output); + + disconnect(); +} + + +void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + disconnect(); + return; + } + + msg.skipBytes(2); // client OS + + uint16_t version = msg.get(); + if (version >= 971) { + msg.skipBytes(17); + } + else { + msg.skipBytes(12); + } + + /* + * Skipped bytes: + * 4 bytes: protocolVersion + * 12 bytes: dat, spr, pic signatures (4 bytes each) + * 1 byte: 0 + */ + + if (version <= 760) { + std::ostringstream ss; + ss << "Only clients with protocol " << getClientVersionString(g_game.getClientVersion()) << " allowed!"; + disconnectClient(ss.str(), version); + return; + } + + if (!Protocol::RSA_decrypt(msg)) { + disconnect(); + return; + } + + uint32_t key[4]; + key[0] = msg.get(); + key[1] = msg.get(); + key[2] = msg.get(); + key[3] = msg.get(); + enableXTEAEncryption(); + setXTEAKey(key); + + if (!isProtocolAllowed(version)) { + std::ostringstream ss; + ss << "Only clients with protocol " << getClientVersionString(g_game.getClientVersion()) << " allowed!"; + disconnectClient(ss.str(), version); + return; + } + + if (g_game.getGameState() == GAME_STATE_STARTUP) { + disconnectClient("Gameworld is starting up. Please wait.", version); + return; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while.", version); + return; + } + + BanInfo banInfo; + auto connection = getConnection(); + if (!connection) { + return; + } + + if (IOBan::isIpBanned(connection->getIP(), banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient(ss.str(), version); + return; + } + + uint32_t accountNumber = msg.get(); + if (!accountNumber) { + disconnectClient("Invalid account number.", version); + return; + } + + uint32_t accountNumberOtClientShallow = msg.get(); + if (!accountNumber || accountNumberOtClientShallow != accountNumber) { + std::ostringstream ss; + ss << "Only clients with protocol " << getClientVersionString(g_game.getClientVersion()) << " allowed!"; + disconnectClient(ss.str(), version); + return; + } + + std::string password = msg.getString(); + if (password.empty()) { + disconnectClient("Invalid password.", version); + return; + } + + auto thisPtr = std::static_pointer_cast(shared_from_this()); + g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version))); +} + + +bool ProtocolLogin::isProtocolAllowed(uint16_t version) +{ + ClientVersion_t allowedClientVersion = g_game.getClientVersion(); + + if (allowedClientVersion == version) + { + return true; + } + else if (version == 781 && allowedClientVersion == CLIENT_VERSION_780) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/app/SabrehavenServer/src/protocollogin.h b/app/SabrehavenServer/src/protocollogin.h new file mode 100644 index 0000000..a95b1ec --- /dev/null +++ b/app/SabrehavenServer/src/protocollogin.h @@ -0,0 +1,50 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D +#define FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D + +#include "protocol.h" +#include "tools.h" + +class NetworkMessage; +class OutputMessage; + +class ProtocolLogin : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0x01}; + static const char* protocol_name() { + return "login protocol"; + } + + explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) override; + bool isProtocolAllowed(uint16_t version); + + private: + void disconnectClient(const std::string& message, uint16_t version); + + void getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version); +}; + +#endif diff --git a/app/SabrehavenServer/src/protocolstatus.cpp b/app/SabrehavenServer/src/protocolstatus.cpp new file mode 100644 index 0000000..be5ad60 --- /dev/null +++ b/app/SabrehavenServer/src/protocolstatus.cpp @@ -0,0 +1,253 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocolstatus.h" +#include "configmanager.h" +#include "game.h" +#include "outputmessage.h" + +extern ConfigManager g_config; +extern Game g_game; + +std::map ProtocolStatus::ipConnectMap; +const uint64_t ProtocolStatus::start = OTSYS_TIME(); + +enum RequestedInfo_t : uint16_t { + REQUEST_BASIC_SERVER_INFO = 1 << 0, + REQUEST_OWNER_SERVER_INFO = 1 << 1, + REQUEST_MISC_SERVER_INFO = 1 << 2, + REQUEST_PLAYERS_INFO = 1 << 3, + REQUEST_MAP_INFO = 1 << 4, + REQUEST_EXT_PLAYERS_INFO = 1 << 5, + REQUEST_PLAYER_STATUS_INFO = 1 << 6, + REQUEST_SERVER_SOFTWARE_INFO = 1 << 7, +}; + +void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg) +{ + uint32_t ip = getIP(); + if (ip != 0x0100007F) { + std::string ipStr = convertIPToString(ip); + if (ipStr != g_config.getString(ConfigManager::IP)) { + std::map::const_iterator it = ipConnectMap.find(ip); + if (it != ipConnectMap.end() && (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) { + disconnect(); + return; + } + } + } + + ipConnectMap[ip] = OTSYS_TIME(); + + switch (msg.getByte()) { + //XML info protocol + case 0xFF: { + if (msg.getString(4) == "info") { + g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendStatusString, + std::static_pointer_cast(shared_from_this())))); + return; + } + break; + } + + //Another ServerInfo protocol + case 0x01: { + uint16_t requestedInfo = msg.get(); // only a Byte is necessary, though we could add new info here + std::string characterName; + if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { + characterName = msg.getString(); + } + g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast(shared_from_this()), + requestedInfo, characterName))); + return; + } + + default: + break; + } + disconnect(); +} + +void ProtocolStatus::sendStatusString() +{ + auto output = OutputMessagePool::getOutputMessage(); + + setRawMessages(true); + + pugi::xml_document doc; + + pugi::xml_node decl = doc.prepend_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node tsqp = doc.append_child("tsqp"); + tsqp.append_attribute("version") = "1.0"; + + pugi::xml_node serverinfo = tsqp.append_child("serverinfo"); + uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; + serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str(); + serverinfo.append_attribute("ip") = g_config.getString(ConfigManager::IP).c_str(); + serverinfo.append_attribute("servername") = g_config.getString(ConfigManager::SERVER_NAME).c_str(); + serverinfo.append_attribute("port") = std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)).c_str(); + serverinfo.append_attribute("location") = g_config.getString(ConfigManager::LOCATION).c_str(); + serverinfo.append_attribute("url") = g_config.getString(ConfigManager::URL).c_str(); + serverinfo.append_attribute("server") = STATUS_SERVER_NAME; + serverinfo.append_attribute("version") = STATUS_SERVER_VERSION; + serverinfo.append_attribute("client") = getClientVersionString(g_game.getClientVersion()).c_str(); + + pugi::xml_node owner = tsqp.append_child("owner"); + owner.append_attribute("name") = g_config.getString(ConfigManager::OWNER_NAME).c_str(); + owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str(); + + pugi::xml_node players = tsqp.append_child("players"); + uint32_t real = 0; + + std::map listIP; + + for (const auto& it : g_game.getPlayers()) { + if (it.second->isFakePlayer) { + real++; + } + else { + if (it.second->getIP() != 0) { + auto ip = listIP.find(it.second->getIP()); + if (ip != listIP.end()) { + listIP[it.second->getIP()]++; + if (listIP[it.second->getIP()] < 5) { + real++; + } + } + else { + listIP[it.second->getIP()] = 1; + real++; + } + } + } + } + players.append_attribute("online") = std::to_string(real).c_str(); + + players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str(); + players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str(); + + pugi::xml_node monsters = tsqp.append_child("monsters"); + monsters.append_attribute("total") = std::to_string(g_game.getMonstersOnline()).c_str(); + + pugi::xml_node npcs = tsqp.append_child("npcs"); + npcs.append_attribute("total") = std::to_string(g_game.getNpcsOnline()).c_str(); + + pugi::xml_node rates = tsqp.append_child("rates"); + rates.append_attribute("experience") = std::to_string(g_config.getNumber(ConfigManager::RATE_EXPERIENCE)).c_str(); + rates.append_attribute("skill") = std::to_string(g_config.getNumber(ConfigManager::RATE_SKILL)).c_str(); + rates.append_attribute("loot") = std::to_string(g_config.getNumber(ConfigManager::RATE_LOOT)).c_str(); + rates.append_attribute("magic") = std::to_string(g_config.getNumber(ConfigManager::RATE_MAGIC)).c_str(); + rates.append_attribute("spawn") = std::to_string(g_config.getNumber(ConfigManager::RATE_SPAWN)).c_str(); + + pugi::xml_node map = tsqp.append_child("map"); + map.append_attribute("name") = g_config.getString(ConfigManager::MAP_NAME).c_str(); + map.append_attribute("author") = g_config.getString(ConfigManager::MAP_AUTHOR).c_str(); + + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + map.append_attribute("width") = std::to_string(mapWidth).c_str(); + map.append_attribute("height") = std::to_string(mapHeight).c_str(); + + pugi::xml_node motd = tsqp.append_child("motd"); + motd.text() = g_config.getString(ConfigManager::MOTD).c_str(); + + std::ostringstream ss; + doc.save(ss, "", pugi::format_raw); + + std::string data = ss.str(); + output->addBytes(data.c_str(), data.size()); + send(output); + disconnect(); +} + +void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& characterName) +{ + auto output = OutputMessagePool::getOutputMessage(); + + if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { + output->addByte(0x10); + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->addString(g_config.getString(ConfigManager::IP)); + output->addString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT))); + } + + if (requestedInfo & REQUEST_OWNER_SERVER_INFO) { + output->addByte(0x11); + output->addString(g_config.getString(ConfigManager::OWNER_NAME)); + output->addString(g_config.getString(ConfigManager::OWNER_EMAIL)); + } + + if (requestedInfo & REQUEST_MISC_SERVER_INFO) { + output->addByte(0x12); + output->addString(g_config.getString(ConfigManager::MOTD)); + output->addString(g_config.getString(ConfigManager::LOCATION)); + output->addString(g_config.getString(ConfigManager::URL)); + output->add((OTSYS_TIME() - ProtocolStatus::start) / 1000); + } + + if (requestedInfo & REQUEST_PLAYERS_INFO) { + output->addByte(0x20); + output->add(g_game.getPlayersOnline()); + output->add(g_config.getNumber(ConfigManager::MAX_PLAYERS)); + output->add(g_game.getPlayersRecord()); + } + + if (requestedInfo & REQUEST_MAP_INFO) { + output->addByte(0x30); + output->addString(g_config.getString(ConfigManager::MAP_NAME)); + output->addString(g_config.getString(ConfigManager::MAP_AUTHOR)); + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + output->add(mapWidth); + output->add(mapHeight); + } + + if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) { + output->addByte(0x21); // players info - online players list + + const auto& players = g_game.getPlayers(); + output->add(players.size()); + for (const auto& it : players) { + output->addString(it.second->getName()); + output->add(it.second->getLevel()); + } + } + + if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { + output->addByte(0x22); // players info - online status info of a player + if (g_game.getPlayerByName(characterName) != nullptr) { + output->addByte(0x01); + } else { + output->addByte(0x00); + } + } + + if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) { + output->addByte(0x23); // server software info + output->addString(STATUS_SERVER_NAME); + output->addString(STATUS_SERVER_VERSION); + output->addString(getClientVersionString(g_game.getClientVersion())); + } + send(output); + disconnect(); +} diff --git a/app/SabrehavenServer/src/protocolstatus.h b/app/SabrehavenServer/src/protocolstatus.h new file mode 100644 index 0000000..2e6cae2 --- /dev/null +++ b/app/SabrehavenServer/src/protocolstatus.h @@ -0,0 +1,49 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 +#define FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 + +#include "networkmessage.h" +#include "protocol.h" + +class ProtocolStatus final : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0xFF}; + static const char* protocol_name() { + return "status protocol"; + } + + explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) final; + + void sendStatusString(); + void sendInfo(uint16_t requestedInfo, const std::string& characterName); + + static const uint64_t start; + + protected: + static std::map ipConnectMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/pugicast.h b/app/SabrehavenServer/src/pugicast.h new file mode 100644 index 0000000..456a2c0 --- /dev/null +++ b/app/SabrehavenServer/src/pugicast.h @@ -0,0 +1,39 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 +#define FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 + +#include + +namespace pugi { + template + T cast(const pugi::char_t* str) + { + T value; + try { + value = boost::lexical_cast(str); + } catch (boost::bad_lexical_cast&) { + value = T(); + } + return value; + } +} + +#endif diff --git a/app/SabrehavenServer/src/quests.cpp b/app/SabrehavenServer/src/quests.cpp new file mode 100644 index 0000000..d28d9e7 --- /dev/null +++ b/app/SabrehavenServer/src/quests.cpp @@ -0,0 +1,230 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "quests.h" + +#include "pugicast.h" + +std::string Mission::getDescription(Player* player) const +{ + int32_t value; + player->getStorageValue(storageID, value); + + if (!mainDescription.empty()) { + std::string desc = mainDescription; + replaceString(desc, "|STATE|", std::to_string(value)); + replaceString(desc, "\\n", "\n"); + return desc; + } + + if (ignoreEndValue) { + for (int32_t current = endValue; current >= startValue; current--) { + if (value >= current) { + auto sit = descriptions.find(current); + if (sit != descriptions.end()) { + return sit->second; + } + } + } + } + else { + for (int32_t current = endValue; current >= startValue; current--) { + if (value == current) { + auto sit = descriptions.find(current); + if (sit != descriptions.end()) { + return sit->second; + } + } + } + } + return "An error has occurred, please contact a gamemaster."; +} + +bool Mission::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (value < startValue) { + return false; + } + + if (!ignoreEndValue && value > endValue) { + return false; + } + + return true; +} + +bool Mission::isCompleted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (ignoreEndValue) { + return value >= endValue; + } + + return value == endValue; +} + +std::string Mission::getName(Player* player) const +{ + if (isCompleted(player)) { + return name + " (completed)"; + } + return name; +} + +uint16_t Quest::getMissionsCount(Player* player) const +{ + uint16_t count = 0; + for (const Mission& mission : missions) { + if (mission.isStarted(player)) { + count++; + } + } + return count; +} + +bool Quest::isCompleted(Player* player) const +{ + for (const Mission& mission : missions) { + if (!mission.isCompleted(player)) { + return false; + } + } + return true; +} + +bool Quest::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(startStorageID, value) || value < startStorageValue) { + return false; + } + + return true; +} + +bool Quests::reload() +{ + quests.clear(); + return loadFromXml(); +} + +bool Quests::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/quests.xml"); + if (!result) { + printXMLError("Error - Quests::loadFromXml", "data/XML/quests.xml", result); + return false; + } + + uint16_t id = 0; + for (auto questNode : doc.child("quests").children()) { + quests.emplace_back( + questNode.attribute("name").as_string(), + ++id, + pugi::cast(questNode.attribute("startstorageid").value()), + pugi::cast(questNode.attribute("startstoragevalue").value()) + ); + Quest& quest = quests.back(); + + for (auto missionNode : questNode.children()) { + std::string mainDescription = missionNode.attribute("description").as_string(); + + quest.missions.emplace_back( + missionNode.attribute("name").as_string(), + pugi::cast(missionNode.attribute("storageid").value()), + pugi::cast(missionNode.attribute("startvalue").value()), + pugi::cast(missionNode.attribute("endvalue").value()), + missionNode.attribute("ignoreendvalue").as_bool() + ); + Mission& mission = quest.missions.back(); + + if (mainDescription.empty()) { + for (auto missionStateNode : missionNode.children()) { + int32_t missionId = pugi::cast(missionStateNode.attribute("id").value()); + mission.descriptions.emplace(missionId, missionStateNode.attribute("description").as_string()); + } + } + else { + mission.mainDescription = mainDescription; + } + } + } + return true; +} + +Quest* Quests::getQuestByID(uint16_t id) +{ + for (Quest& quest : quests) { + if (quest.id == id) { + return ? + } + } + return nullptr; +} + +uint16_t Quests::getQuestsCount(Player* player) const +{ + uint16_t count = 0; + for (const Quest& quest : quests) { + if (quest.isStarted(player)) { + count++; + } + } + return count; +} + +bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const +{ + for (const Quest& quest : quests) { + if (quest.getStartStorageId() == key && quest.getStartStorageValue() == value) { + return true; + } + + for (const Mission& mission : quest.getMissions()) { + if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && value <= mission.getEndStorageValue()) { + return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || oldValue > mission.getEndStorageValue(); + } + } + } + return false; +} diff --git a/app/SabrehavenServer/src/quests.h b/app/SabrehavenServer/src/quests.h new file mode 100644 index 0000000..45cb002 --- /dev/null +++ b/app/SabrehavenServer/src/quests.h @@ -0,0 +1,119 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 +#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 + +#include "player.h" +#include "networkmessage.h" + +class Mission; +class Quest; + +using MissionsList = std::list; +using QuestsList = std::list; + +class Mission +{ +public: + Mission(std::string name, int32_t storageID, int32_t startValue, int32_t endValue, bool ignoreEndValue) : + name(std::move(name)), storageID(storageID), startValue(startValue), endValue(endValue), ignoreEndValue(ignoreEndValue) {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + std::string getName(Player* player) const; + std::string getDescription(Player* player) const; + + uint32_t getStorageId() const { + return storageID; + } + int32_t getStartStorageValue() const { + return startValue; + } + int32_t getEndStorageValue() const { + return endValue; + } + + std::map descriptions; + std::string mainDescription; + +private: + std::string name; + uint32_t storageID; + int32_t startValue, endValue; + bool ignoreEndValue; +}; + +class Quest +{ +public: + Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) : + name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + uint16_t getID() const { + return id; + } + std::string getName() const { + return name; + } + uint16_t getMissionsCount(Player* player) const; + + uint32_t getStartStorageId() const { + return startStorageID; + } + int32_t getStartStorageValue() const { + return startStorageValue; + } + + const MissionsList& getMissions() const { + return missions; + } + +private: + std::string name; + + uint32_t startStorageID; + int32_t startStorageValue; + uint16_t id; + + MissionsList missions; + + friend class Quests; +}; + +class Quests +{ +public: + const QuestsList& getQuests() const { + return quests; + } + + bool loadFromXml(); + Quest* getQuestByID(uint16_t id); + bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const; + uint16_t getQuestsCount(Player* player) const; + bool reload(); + +private: + QuestsList quests; +}; + +#endif diff --git a/app/SabrehavenServer/src/raids.cpp b/app/SabrehavenServer/src/raids.cpp new file mode 100644 index 0000000..6fd4f1f --- /dev/null +++ b/app/SabrehavenServer/src/raids.cpp @@ -0,0 +1,595 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "raids.h" + +#include "pugicast.h" + +#include "game.h" +#include "configmanager.h" +#include "scheduler.h" +#include "monster.h" + +extern Game g_game; +extern ConfigManager g_config; + +Raids::Raids() +{ + scriptInterface.initState(); +} + +Raids::~Raids() +{ + for (Raid* raid : raidList) { + delete raid; + } +} + +bool Raids::loadFromXml() +{ + if (isLoaded()) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/raids/raids.xml"); + if (!result) { + printXMLError("Error - Raids::loadFromXml", "data/raids/raids.xml", result); + return false; + } + + for (auto raidNode : doc.child("raids").children()) { + std::string name, file; + uint32_t interval, margin; + + pugi::xml_attribute attr; + if ((attr = raidNode.attribute("name"))) { + name = attr.as_string(); + } else { + std::cout << "[Error - Raids::loadFromXml] Name tag missing for raid" << std::endl; + continue; + } + + if ((attr = raidNode.attribute("file"))) { + file = attr.as_string(); + } else { + std::ostringstream ss; + ss << "raids/" << name << ".xml"; + file = ss.str(); + std::cout << "[Warning - Raids::loadFromXml] File tag missing for raid " << name << ". Using default: " << file << std::endl; + } + + interval = pugi::cast(raidNode.attribute("interval2").value()) * 60; + if (interval == 0) { + std::cout << "[Error - Raids::loadFromXml] interval2 tag missing or zero (would divide by 0) for raid: " << name << std::endl; + continue; + } + + if ((attr = raidNode.attribute("margin"))) { + margin = pugi::cast(attr.value()) * 60 * 1000; + } else { + std::cout << "[Warning - Raids::loadFromXml] margin tag missing for raid: " << name << std::endl; + margin = 0; + } + + bool repeat; + if ((attr = raidNode.attribute("repeat"))) { + repeat = booleanString(attr.as_string()); + } else { + repeat = false; + } + + Raid* newRaid = new Raid(name, interval, margin, repeat); + if (newRaid->loadFromXml("data/raids/" + file)) { + raidList.push_back(newRaid); + } else { + std::cout << "[Error - Raids::loadFromXml] Failed to load raid: " << name << std::endl; + delete newRaid; + } + } + + loaded = true; + return true; +} + +static constexpr int32_t MAX_RAND_RANGE = 10000000; + +bool Raids::startup() +{ + if (!isLoaded() || isStarted()) { + return false; + } + + setLastRaidEnd(OTSYS_TIME()); + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); + + started = true; + return started; +} + +void Raids::checkRaids() +{ + if (!getRunning()) { + uint64_t now = OTSYS_TIME(); + + for (auto it = raidList.begin(), end = raidList.end(); it != end; ++it) { + Raid* raid = *it; + if (now >= (getLastRaidEnd() + raid->getMargin())) { + if (((MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL) / raid->getInterval()) >= static_cast(uniform_random(0, MAX_RAND_RANGE))) { + setRunning(raid); + raid->startRaid(); + + if (!raid->canBeRepeated()) { + raidList.erase(it); + } + break; + } + } + } + } + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); +} + +void Raids::clear() +{ + g_scheduler.stopEvent(checkRaidsEvent); + checkRaidsEvent = 0; + + for (Raid* raid : raidList) { + raid->stopEvents(); + delete raid; + } + raidList.clear(); + + loaded = false; + started = false; + running = nullptr; + lastRaidEnd = 0; + + scriptInterface.reInitState(); +} + +bool Raids::reload() +{ + clear(); + return loadFromXml(); +} + +Raid* Raids::getRaidByName(const std::string& name) +{ + for (Raid* raid : raidList) { + if (strcasecmp(raid->getName().c_str(), name.c_str()) == 0) { + return raid; + } + } + return nullptr; +} + +Raid::~Raid() +{ + for (RaidEvent* raidEvent : raidEvents) { + delete raidEvent; + } +} + +bool Raid::loadFromXml(const std::string& filename) +{ + if (isLoaded()) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Raid::loadFromXml", filename, result); + return false; + } + + for (auto eventNode : doc.child("raid").children()) { + RaidEvent* event; + if (strcasecmp(eventNode.name(), "announce") == 0) { + event = new AnnounceEvent(); + } else if (strcasecmp(eventNode.name(), "singlespawn") == 0) { + event = new SingleSpawnEvent(); + } else if (strcasecmp(eventNode.name(), "areaspawn") == 0) { + event = new AreaSpawnEvent(); + } else if (strcasecmp(eventNode.name(), "script") == 0) { + event = new ScriptEvent(&g_game.raids.getScriptInterface()); + } else { + continue; + } + + if (event->configureRaidEvent(eventNode)) { + raidEvents.push_back(event); + } else { + std::cout << "[Error - Raid::loadFromXml] In file (" << filename << "), eventNode: " << eventNode.name() << std::endl; + delete event; + } + } + + //sort by delay time + std::sort(raidEvents.begin(), raidEvents.end(), RaidEvent::compareEvents); + + loaded = true; + return true; +} + +void Raid::startRaid() +{ + RaidEvent* raidEvent = getNextRaidEvent(); + if (raidEvent) { + state = RAIDSTATE_EXECUTING; + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent))); + } +} + +void Raid::executeRaidEvent(RaidEvent* raidEvent) +{ + if (raidEvent->executeEvent()) { + nextEvent++; + RaidEvent* newRaidEvent = getNextRaidEvent(); + + if (newRaidEvent) { + uint32_t ticks = static_cast(std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay())); + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent))); + } else { + resetRaid(); + } + } else { + resetRaid(); + } +} + +void Raid::resetRaid() +{ + nextEvent = 0; + state = RAIDSTATE_IDLE; + g_game.raids.setRunning(nullptr); + g_game.raids.setLastRaidEnd(OTSYS_TIME()); +} + +void Raid::stopEvents() +{ + if (nextEventEvent != 0) { + g_scheduler.stopEvent(nextEventEvent); + nextEventEvent = 0; + } +} + +RaidEvent* Raid::getNextRaidEvent() +{ + if (nextEvent < raidEvents.size()) { + return raidEvents[nextEvent]; + } else { + return nullptr; + } +} + +bool RaidEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + pugi::xml_attribute delayAttribute = eventNode.attribute("delay"); + if (!delayAttribute) { + std::cout << "[Error] Raid: delay tag missing." << std::endl; + return false; + } + + delay = std::max(RAID_MINTICKS, pugi::cast(delayAttribute.value())); + return true; +} + +bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute messageAttribute = eventNode.attribute("message"); + if (!messageAttribute) { + std::cout << "[Error] Raid: message tag missing for announce event." << std::endl; + return false; + } + message = messageAttribute.as_string(); + + pugi::xml_attribute typeAttribute = eventNode.attribute("type"); + if (typeAttribute) { + std::string tmpStrValue = asLowerCaseString(typeAttribute.as_string()); + if (tmpStrValue == "warning") { + messageType = MESSAGE_STATUS_WARNING; + } else if (tmpStrValue == "event") { + messageType = MESSAGE_EVENT_ADVANCE; + } else if (tmpStrValue == "default") { + messageType = MESSAGE_EVENT_DEFAULT; + } else if (tmpStrValue == "description") { + messageType = MESSAGE_INFO_DESCR; + } else if (tmpStrValue == "smallstatus") { + messageType = MESSAGE_STATUS_SMALL; + } else if (tmpStrValue == "blueconsole") { + messageType = MESSAGE_STATUS_CONSOLE_BLUE; + } else if (tmpStrValue == "redconsole") { + messageType = MESSAGE_STATUS_CONSOLE_RED; + } else { + std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + } + } else { + messageType = MESSAGE_EVENT_ADVANCE; + std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + } + return true; +} + +bool AnnounceEvent::executeEvent() +{ + g_game.broadcastMessage(message, messageType); + return true; +} + +bool SingleSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = eventNode.attribute("name"))) { + monsterName = attr.as_string(); + } else { + std::cout << "[Error] Raid: name tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("x"))) { + position.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: x tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("y"))) { + position.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: y tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("z"))) { + position.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: z tag missing for singlespawn event." << std::endl; + return false; + } + return true; +} + +bool SingleSpawnEvent::executeEvent() +{ + Monster* monster = Monster::createMonster(monsterName); + if (!monster) { + std::cout << "[Error] Raids: Cant create monster " << monsterName << std::endl; + return false; + } + + if (!g_game.placeCreature(monster, position, false, true)) { + delete monster; + std::cout << "[Error] Raids: Cant place monster " << monsterName << std::endl; + return false; + } + return true; +} + +bool AreaSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = eventNode.attribute("radius"))) { + int32_t radius = pugi::cast(attr.value()); + Position centerPos; + + if ((attr = eventNode.attribute("centerx"))) { + centerPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centerx tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("centery"))) { + centerPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centery tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("centerz"))) { + centerPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centerz tag missing for areaspawn event." << std::endl; + return false; + } + + fromPos.x = std::max(0, centerPos.getX() - radius); + fromPos.y = std::max(0, centerPos.getY() - radius); + fromPos.z = centerPos.z; + + toPos.x = std::min(0xFFFF, centerPos.getX() + radius); + toPos.y = std::min(0xFFFF, centerPos.getY() + radius); + toPos.z = centerPos.z; + } else { + if ((attr = eventNode.attribute("fromx"))) { + fromPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromx tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("fromy"))) { + fromPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromy tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("fromz"))) { + fromPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromz tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("tox"))) { + toPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: tox tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("toy"))) { + toPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: toy tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("toz"))) { + toPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: toz tag missing for areaspawn event." << std::endl; + return false; + } + } + + if ((attr = eventNode.attribute("lifetime"))) { + lifetime = pugi::cast(attr.value()); + } + + for (auto monsterNode : eventNode.children()) { + const char* name; + + if ((attr = monsterNode.attribute("name"))) { + name = attr.value(); + } else { + std::cout << "[Error] Raid: name tag missing for monster node." << std::endl; + return false; + } + + uint32_t minAmount; + if ((attr = monsterNode.attribute("minamount"))) { + minAmount = pugi::cast(attr.value()); + } else { + minAmount = 0; + } + + uint32_t maxAmount; + if ((attr = monsterNode.attribute("maxamount"))) { + maxAmount = pugi::cast(attr.value()); + } else { + maxAmount = 0; + } + + if (maxAmount == 0 && minAmount == 0) { + if ((attr = monsterNode.attribute("amount"))) { + minAmount = pugi::cast(attr.value()); + maxAmount = minAmount; + } else { + std::cout << "[Error] Raid: amount tag missing for monster node." << std::endl; + return false; + } + } + + spawnList.emplace_back(name, minAmount, maxAmount); + } + return true; +} + +bool AreaSpawnEvent::executeEvent() +{ + for (const MonsterSpawn& spawn : spawnList) { + uint32_t amount = uniform_random(spawn.minAmount, spawn.maxAmount); + for (uint32_t i = 0; i < amount; ++i) { + Monster* monster = Monster::createMonster(spawn.name); + if (!monster) { + std::cout << "[Error - AreaSpawnEvent::executeEvent] Can't create monster " << spawn.name << std::endl; + return false; + } + + bool success = false; + for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) { + Tile* tile = g_game.map.getTile(uniform_random(fromPos.x, toPos.x), uniform_random(fromPos.y, toPos.y), uniform_random(fromPos.z, toPos.z)); + if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game.placeCreature(monster, tile->getPosition(), false, true)) { + success = true; + break; + } + } + + if (!success) { + delete monster; + } + + if (lifetime > 0) { + monster->lifetime = OTSYS_TIME() + lifetime; + } + } + } + return true; +} + +bool ScriptEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute scriptAttribute = eventNode.attribute("script"); + if (!scriptAttribute) { + std::cout << "Error: [ScriptEvent::configureRaidEvent] No script file found for raid" << std::endl; + return false; + } + + if (!loadScript("data/raids/scripts/" + std::string(scriptAttribute.as_string()))) { + std::cout << "Error: [ScriptEvent::configureRaidEvent] Can not load raid script." << std::endl; + return false; + } + return true; +} + +std::string ScriptEvent::getScriptEventName() const +{ + return "onRaid"; +} + +bool ScriptEvent::executeEvent() +{ + //onRaid() + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - ScriptEvent::onRaid] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + scriptInterface->pushFunction(scriptId); + + return scriptInterface->callFunction(0); +} diff --git a/app/SabrehavenServer/src/raids.h b/app/SabrehavenServer/src/raids.h new file mode 100644 index 0000000..3c97d1d --- /dev/null +++ b/app/SabrehavenServer/src/raids.h @@ -0,0 +1,233 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 +#define FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 + +#include "const.h" +#include "position.h" +#include "baseevents.h" + +enum RaidState_t { + RAIDSTATE_IDLE, + RAIDSTATE_EXECUTING, +}; + +struct MonsterSpawn { + MonsterSpawn(std::string name, uint32_t minAmount, uint32_t maxAmount) : + name(std::move(name)), minAmount(minAmount), maxAmount(maxAmount) {} + + std::string name; + uint32_t minAmount; + uint32_t maxAmount; +}; + +//How many times it will try to find a tile to add the monster to before giving up +static constexpr int32_t MAXIMUM_TRIES_PER_MONSTER = 10; +static constexpr int32_t CHECK_RAIDS_INTERVAL = 60; +static constexpr int32_t RAID_MINTICKS = 1000; + +class Raid; +class RaidEvent; + +class Raids +{ + public: + Raids(); + ~Raids(); + + // non-copyable + Raids(const Raids&) = delete; + Raids& operator=(const Raids&) = delete; + + bool loadFromXml(); + bool startup(); + + void clear(); + bool reload(); + + bool isLoaded() const { + return loaded; + } + bool isStarted() const { + return started; + } + + Raid* getRunning() { + return running; + } + void setRunning(Raid* newRunning) { + running = newRunning; + } + + Raid* getRaidByName(const std::string& name); + + uint64_t getLastRaidEnd() const { + return lastRaidEnd; + } + void setLastRaidEnd(uint64_t newLastRaidEnd) { + lastRaidEnd = newLastRaidEnd; + } + + void checkRaids(); + + LuaScriptInterface& getScriptInterface() { + return scriptInterface; + } + + private: + LuaScriptInterface scriptInterface{"Raid Interface"}; + + std::list raidList; + Raid* running = nullptr; + uint64_t lastRaidEnd = 0; + uint32_t checkRaidsEvent = 0; + bool loaded = false; + bool started = false; +}; + +class Raid +{ + public: + Raid(std::string name, uint32_t interval, uint32_t marginTime, bool repeat) : + name(std::move(name)), interval(interval), margin(marginTime), repeat(repeat) {} + ~Raid(); + + // non-copyable + Raid(const Raid&) = delete; + Raid& operator=(const Raid&) = delete; + + bool loadFromXml(const std::string& filename); + + void startRaid(); + + void executeRaidEvent(RaidEvent* raidEvent); + void resetRaid(); + + RaidEvent* getNextRaidEvent(); + void setState(RaidState_t newState) { + state = newState; + } + const std::string& getName() const { + return name; + } + + bool isLoaded() const { + return loaded; + } + uint64_t getMargin() const { + return margin; + } + uint32_t getInterval() const { + return interval; + } + bool canBeRepeated() const { + return repeat; + } + + void stopEvents(); + + private: + std::vector raidEvents; + std::string name; + uint32_t interval; + uint32_t nextEvent = 0; + uint64_t margin; + RaidState_t state = RAIDSTATE_IDLE; + uint32_t nextEventEvent = 0; + bool loaded = false; + bool repeat; +}; + +class RaidEvent +{ + public: + virtual ~RaidEvent() = default; + + virtual bool configureRaidEvent(const pugi::xml_node& eventNode); + + virtual bool executeEvent() = 0; + uint32_t getDelay() const { + return delay; + } + + static bool compareEvents(const RaidEvent* lhs, const RaidEvent* rhs) { + return lhs->getDelay() < rhs->getDelay(); + } + + private: + uint32_t delay; +}; + +class AnnounceEvent final : public RaidEvent +{ + public: + AnnounceEvent() = default; + + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::string message; + MessageClasses messageType = MESSAGE_EVENT_ADVANCE; +}; + +class SingleSpawnEvent final : public RaidEvent +{ + public: + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::string monsterName; + Position position; +}; + +class AreaSpawnEvent final : public RaidEvent +{ + public: + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::list spawnList; + Position fromPos, toPos; + uint32_t lifetime = 0; +}; + +class ScriptEvent final : public RaidEvent, public Event +{ + public: + explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} + + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureEvent(const pugi::xml_node&) final { + return false; + } + + bool executeEvent() final; + + protected: + std::string getScriptEventName() const final; +}; + +#endif diff --git a/app/SabrehavenServer/src/rsa.cpp b/app/SabrehavenServer/src/rsa.cpp new file mode 100644 index 0000000..a04fd98 --- /dev/null +++ b/app/SabrehavenServer/src/rsa.cpp @@ -0,0 +1,85 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "rsa.h" + +#include +#include + +#include +#include + +static CryptoPP::AutoSeededRandomPool prng; + +void RSA::decrypt(char* msg) const +{ + try { + CryptoPP::Integer m{ reinterpret_cast(msg), 128 }; + auto c = pk.CalculateInverse(prng, m); + c.Encode(reinterpret_cast(msg), 128); + } + catch (const CryptoPP::Exception& e) { + std::cout << e.what() << '\n'; + } +} + +static const std::string header = "-----BEGIN RSA PRIVATE KEY-----"; +static const std::string footer = "-----END RSA PRIVATE KEY-----"; + +void RSA::loadPEM(const std::string& filename) +{ + std::ifstream file{ filename }; + + if (!file.is_open()) { + throw std::runtime_error("Missing file " + filename + "."); + } + + std::ostringstream oss; + for (std::string line; std::getline(file, line); oss << line); + std::string key = oss.str(); + + if (key.substr(0, header.size()) != header) { + throw std::runtime_error("Missing RSA private key header."); + } + + if (key.substr(key.size() - footer.size(), footer.size()) != footer) { + throw std::runtime_error("Missing RSA private key footer."); + } + + key = key.substr(header.size(), key.size() - footer.size()); + + CryptoPP::ByteQueue queue; + CryptoPP::Base64Decoder decoder; + decoder.Attach(new CryptoPP::Redirector(queue)); + decoder.Put(reinterpret_cast(key.c_str()), key.size()); + decoder.MessageEnd(); + + try { + pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable()); + + if (!pk.Validate(prng, 3)) { + throw std::runtime_error("RSA private key is not valid."); + } + } + catch (const CryptoPP::Exception& e) { + std::cout << e.what() << '\n'; + } +} diff --git a/app/SabrehavenServer/src/rsa.h b/app/SabrehavenServer/src/rsa.h new file mode 100644 index 0000000..0783e7c --- /dev/null +++ b/app/SabrehavenServer/src/rsa.h @@ -0,0 +1,43 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 +#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 + +#include + +#include + +class RSA +{ +public: + RSA() = default; + + // non-copyable + RSA(const RSA&) = delete; + RSA& operator=(const RSA&) = delete; + + void loadPEM(const std::string& filename); + void decrypt(char* msg) const; + +private: + CryptoPP::RSA::PrivateKey pk; +}; + +#endif \ No newline at end of file diff --git a/app/SabrehavenServer/src/scheduler.cpp b/app/SabrehavenServer/src/scheduler.cpp new file mode 100644 index 0000000..28fe462 --- /dev/null +++ b/app/SabrehavenServer/src/scheduler.cpp @@ -0,0 +1,134 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "scheduler.h" + +void Scheduler::threadMain() +{ + std::unique_lock eventLockUnique(eventLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { + std::cv_status ret = std::cv_status::no_timeout; + + eventLockUnique.lock(); + if (eventList.empty()) { + eventSignal.wait(eventLockUnique); + } else { + ret = eventSignal.wait_until(eventLockUnique, eventList.top()->getCycle()); + } + + // the mutex is locked again now... + if (ret == std::cv_status::timeout) { + // ok we had a timeout, so there has to be an event we have to execute... + SchedulerTask* task = eventList.top(); + eventList.pop(); + + // check if the event was stopped + auto it = eventIds.find(task->getEventId()); + if (it == eventIds.end()) { + eventLockUnique.unlock(); + delete task; + continue; + } + eventIds.erase(it); + eventLockUnique.unlock(); + + task->setDontExpire(); + g_dispatcher.addTask(task, true); + } else { + eventLockUnique.unlock(); + } + } +} + +uint32_t Scheduler::addEvent(SchedulerTask* task) +{ + bool do_signal = false; + eventLock.lock(); + + if (getState() == THREAD_STATE_RUNNING) { + // check if the event has a valid id + if (task->getEventId() == 0) { + // if not generate one + if (++lastEventId == 0) { + lastEventId = 1; + } + + task->setEventId(lastEventId); + } + + // insert the event id in the list of active events + eventIds.insert(task->getEventId()); + + // add the event to the queue + eventList.push(task); + + // if the list was empty or this event is the top in the list + // we have to signal it + do_signal = (task == eventList.top()); + } else { + eventLock.unlock(); + delete task; + return 0; + } + + eventLock.unlock(); + + if (do_signal) { + eventSignal.notify_one(); + } + + return task->getEventId(); +} + +bool Scheduler::stopEvent(uint32_t eventid) +{ + if (eventid == 0) { + return false; + } + + std::lock_guard lockClass(eventLock); + + // search the event id.. + auto it = eventIds.find(eventid); + if (it == eventIds.end()) { + return false; + } + + eventIds.erase(it); + return true; +} + +void Scheduler::shutdown() +{ + setState(THREAD_STATE_TERMINATED); + eventLock.lock(); + + //this list should already be empty + while (!eventList.empty()) { + delete eventList.top(); + eventList.pop(); + } + + eventIds.clear(); + eventLock.unlock(); + eventSignal.notify_one(); +} + diff --git a/app/SabrehavenServer/src/scheduler.h b/app/SabrehavenServer/src/scheduler.h new file mode 100644 index 0000000..cc8a839 --- /dev/null +++ b/app/SabrehavenServer/src/scheduler.h @@ -0,0 +1,85 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 +#define FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 + +#include "tasks.h" +#include +#include + +#include "thread_holder_base.h" + +static constexpr int32_t SCHEDULER_MINTICKS = 50; + +class SchedulerTask : public Task +{ + public: + void setEventId(uint32_t id) { + eventId = id; + } + uint32_t getEventId() const { + return eventId; + } + + std::chrono::system_clock::time_point getCycle() const { + return expiration; + } + + protected: + SchedulerTask(uint32_t delay, const std::function& f) : Task(delay, f) {} + + uint32_t eventId = 0; + + friend SchedulerTask* createSchedulerTask(uint32_t, const std::function&); +}; + +inline SchedulerTask* createSchedulerTask(uint32_t delay, const std::function& f) +{ + return new SchedulerTask(delay, f); +} + +struct TaskComparator { + bool operator()(const SchedulerTask* lhs, const SchedulerTask* rhs) const { + return lhs->getCycle() > rhs->getCycle(); + } +}; + +class Scheduler : public ThreadHolder +{ + public: + uint32_t addEvent(SchedulerTask* task); + bool stopEvent(uint32_t eventId); + + void shutdown(); + + void threadMain(); + protected: + std::thread thread; + std::mutex eventLock; + std::condition_variable eventSignal; + + uint32_t lastEventId {0}; + std::priority_queue, TaskComparator> eventList; + std::unordered_set eventIds; +}; + +extern Scheduler g_scheduler; + +#endif diff --git a/app/SabrehavenServer/src/script.cpp b/app/SabrehavenServer/src/script.cpp new file mode 100644 index 0000000..40e955e --- /dev/null +++ b/app/SabrehavenServer/src/script.cpp @@ -0,0 +1,432 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "otpch.h" + +#include "script.h" + +void ScriptReader::nextToken() +{ + signed int v1; // esi@3 + int v4; // eax@5 + int v5; // eax@8 + int v6; // edi@8 + int v7; // eax@24 + int v9; // eax@32 + int v10; // eax@36 + int v11; // edi@36 + int v12; // eax@45 + int v13; // edi@45 + int v14; // eax@52 + int v15; // edi@52 + int v16; // eax@55 + int v17; // eax@62 + int v18; // eax@70 + int v19; // eax@78 + int v20; // edi@78 + int v21; // eax@86 + int v22; // eax@90 + int v23; // edi@90 + int v24; // eax@94 + int v25; // eax@98 + int v26; // edi@98 + int v27; // eax@102 + int v28; // eax@121 + int v29; // eax@127 + int v30; // eax@131 + int v31; // eax@136 + int v32; // eax@139 + std::string FileName; // [sp+1Ch] [bp-1Ch]@2 + int Sign; // [sp+20h] [bp-18h]@5 + FILE *f; // [sp+24h] [bp-14h]@5 + int pos; // [sp+28h] [bp-10h]@3 + + if (this->RecursionDepth == -1) + { + error("ScriptReader::nextToken: Kein Skript zum Lesen geffnet.\n"); + LABEL_30: + this->Token = ENDOFFILE; + return; + } + FileName = String; +LABEL_3: + pos = 0; + v1 = 0; + this->String = ""; + v4 = this->RecursionDepth; + this->Number = 0; + Sign = 1; + f = this->File[v4]; + while (2) + { + if (pos == 3999) + error("string too long"); + switch (v1) + { + case 0: + v5 = getc(f); + v6 = v5; + if (v5 == -1) + goto LABEL_24; + if (v5 == 10) + { + ++this->Line[this->RecursionDepth]; + } else if (!isspace(v5)) + { + v1 = 1; + if (v6 != 35) + { + v1 = 30; + if (v6 != 64) + { + if (isalpha(v6)) + { + v1 = 2; + this->String += v6; + pos++; + } else if (IsDigit(v6)) + { + this->Number = v6 - 48; + v1 = 3; + } else + { + v1 = 6; + if (v6 != 34) + { + v1 = 11; + if (v6 != 91) + { + v1 = 22; + if (v6 != 60) + { + v1 = 25; + if (v6 != 62) + { + v1 = 27; + if (v6 != 45) + { + v1 = 10; + this->Special = v6; + } + } + } + } + } + } + } + } + } + continue; + case 1: + v9 = getc(f); + if (v9 != -1) + { + if (v9 == 10) + { + ++this->Line[this->RecursionDepth]; + LABEL_35: + v1 = 0; + } + continue; + } + LABEL_24: + v7 = this->RecursionDepth; + if (v7 <= 0) + goto LABEL_30; + if (v7 == -1) + { + printf("ScriptReader::close: Keine Datei offen.\n"); + } else + { + if (fclose(this->File[v7])) + { + printf("ScriptReader::close: Fehler %d beim Schlieen der Datei.\n", RecursionDepth); + } + --this->RecursionDepth; + } + goto LABEL_3; + case 2: + v10 = getc(f); + v11 = v10; + if (pos == 30) + error("identifier too long"); + if (v10 == -1) + goto LABEL_43; + if (isalpha(v10) || IsDigit(v11) || v11 == 95) + goto LABEL_39; + ungetc(v11, f); + LABEL_43: + this->Token = IDENTIFIER; + return; + case 3: + v12 = getc(f); + v13 = v12; + if (v12 == -1) + goto LABEL_50; + if (IsDigit(v12)) + goto LABEL_51; + if (v13 == 45) + goto LABEL_48; + ungetc(v13, f); + LABEL_50: + this->Token = NUMBER; + return; + case 4: + v14 = getc(f); + v15 = v14; + if (v14 == -1) + error("unexpected end of file"); + if (!IsDigit(v14)) + error("syntax error"); + v1 = 5; + this->Number = v15 - 48; + continue; + case 5: + v16 = getc(f); + v13 = v16; + if (v16 == -1) + goto LABEL_59; + if (IsDigit(v16)) + goto LABEL_51; + if (v13 != 45) + { + ungetc(v13, f); + LABEL_59: + this->Bytes[pos] = this->Number; + this->Token = BYTES; + return; + } + LABEL_48: + this->Bytes[pos++] = this->Number; + v1 = 4; + continue; + case 6: + v17 = getc(f); + v11 = v17; + if (v17 == -1) + error("unexpected end of file"); + if (v17 == 34) + { + v1 = 8; + } else if (v17 == 92) + { + v1 = 7; + } else + { + if (v17 == 10) + ++this->Line[this->RecursionDepth]; + LABEL_39: + this->String += v11; + pos++; + } + continue; + case 7: + v18 = getc(f); + if (v18 == -1) + error("unexpected end of file"); + if (v18 == 110) { + this->String += 10; + pos++; + } else { + this->String += v18; + pos++; + } + v1 = 6; + continue; + case 8: + this->Token = STRING; + return; + case 10: + goto LABEL_77; + case 11: + v19 = getc(f); + this->Special = 91; + v20 = v19; + if (v19 == -1) + goto LABEL_77; + if (IsDigit(v19)) + { + Sign = 1; + this->Number = v20 - 48; + LABEL_82: + v1 = 12; + continue; + } + if (v20 == 45) + { + Sign = -1; + this->Number = 0; + goto LABEL_82; + } + LABEL_83: + ungetc(v20, f); + LABEL_77: + this->Token = SPECIAL; + return; + case 12: + v21 = getc(f); + v13 = v21; + if (v21 == -1) + error("unexpected end of file"); + if (IsDigit(v21)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 13; + this->CoordX = this->Number * Sign; + continue; + case 13: + v22 = getc(f); + v23 = v22; + if (v22 == -1) + error("unexpected end of file"); + if (IsDigit(v22)) + { + Sign = 1; + this->Number = v23 - 48; + } else + { + if (v23 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 14; + continue; + case 14: + v24 = getc(f); + v13 = v24; + if (v24 == -1) + error("unexpected end of file"); + if (IsDigit(v24)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 15; + this->CoordY = this->Number * Sign; + continue; + case 15: + v25 = getc(f); + v26 = v25; + if (v25 == -1) + error("unexpected end of file"); + if (IsDigit(v25)) + { + Sign = 1; + this->Number = v26 - 48; + } else + { + if (v26 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 16; + continue; + case 16: + v27 = getc(f); + v13 = v27; + if (v27 == -1) + error("unexpected end of file"); + if (IsDigit(v27)) + { + LABEL_51: + this->Number = v13 + 10 * this->Number - 48; + } else + { + if (v13 != 93) + error("syntax error"); + v1 = 17; + this->CoordZ = this->Number * Sign; + } + continue; + case 17: + this->Token = COORDINATE; + return; + case 22: + v28 = getc(f); + this->Special = 60; + if (v28 == -1) + goto LABEL_77; + v1 = 23; + if (v28 == 61) + continue; + v1 = 24; + if (v28 == 62) + continue; + ungetc(v28, f); + goto LABEL_77; + case 23: + this->Special = 76; + goto LABEL_77; + case 24: + this->Special = 78; + goto LABEL_77; + case 25: + v29 = getc(f); + this->Special = 62; + v20 = v29; + if (v29 == -1) + goto LABEL_77; + v1 = 26; + if (v29 != 61) + goto LABEL_83; + continue; + case 26: + this->Special = 71; + goto LABEL_77; + case 27: + v30 = getc(f); + this->Special = 45; + if (v30 == -1) + goto LABEL_77; + v1 = 28; + if (v30 == 62) + continue; + ungetc(v30, f); + goto LABEL_77; + case 28: + this->Special = 73; + goto LABEL_77; + default: + error("ScriptReader::nextToken: Ungnltiger Zustand.\n"); + goto LABEL_35; + case 30: + v31 = getc(f); + if (v31 == -1) + error("unexpected end of file"); + if (v31 != 34) + error("syntax error"); + v1 = 31; + continue; + case 31: + v32 = getc(f); + v11 = v32; + if (v32 == -1) + error("unexpected end of file"); + if (v32 != 34) + goto LABEL_39; + v1 = 32; + continue; + case 32: + open(CurrentDirectory + "/" + String); + goto LABEL_3; + } + } +} diff --git a/app/SabrehavenServer/src/script.h b/app/SabrehavenServer/src/script.h new file mode 100644 index 0000000..60d646a --- /dev/null +++ b/app/SabrehavenServer/src/script.h @@ -0,0 +1,277 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 +#define FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 + +#include "tools.h" + +enum TOKEN +{ + ENDOFFILE = 0, + IDENTIFIER, + NUMBER, + STRING, + BYTES, + COORDINATE, + SPECIAL +}; + +class ScriptReader +{ +public: + ScriptReader() + { + Token = ENDOFFILE; + RecursionDepth = -1; + } + + ~ScriptReader() + { + if (RecursionDepth != -1) + { + std::cout << "ScriptReader::~ScriptReader: File is still open.\n"; + for (int i = RecursionDepth; i != -1; i = RecursionDepth) + { + if (fclose(File[i])) + { + std::cout << "ScriptReader::close: Error when closing the file.\n"; + } + --RecursionDepth; + } + } + } + + TOKEN Token; + FILE* File[3]; + int RecursionDepth; + char Filename[3][4096]; + std::string CurrentDirectory; + std::string String; + unsigned char Bytes[1000]; + int Line[3]; + int Number; + uint16_t CoordX; + uint16_t CoordY; + uint8_t CoordZ; + char Special; + + bool open(const std::string& FileName) + { + RecursionDepth++; + if (RecursionDepth == 3) + { + error("ScriptReader::open: too big recursion.\n"); + error("Recursion depth too high.\n"); + return false; + } + + if (RecursionDepth > -1) + { + CurrentDirectory = FileName; + if (FileName.find('/') != std::string::npos) { + int32_t end = FileName.find_last_of('/'); + CurrentDirectory = FileName.substr(0, end); + strcpy(Filename[RecursionDepth], FileName.substr(end + 1, FileName.length() - end).c_str()); + } else { + strcpy(Filename[RecursionDepth], FileName.c_str()); + } + + File[RecursionDepth] = fopen(FileName.c_str(), "rb"); + if (!File[RecursionDepth]) + { + printf("ScriptReader::open: Can not open file %s.\n", FileName.c_str()); + RecursionDepth--; + printf("Cannot open script-file\n"); + return false; + } + } + + Line[RecursionDepth] = 1; + return true; + } + + void close() + { + int depth; // eax@1 + + depth = RecursionDepth; + if (depth == -1) + { + std::cout << "ScriptReader::close: Invalid recursion depth.\n"; + } else + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --RecursionDepth; + } + } + + void error(const std::string& text) + { + int depth = this->RecursionDepth; + if (depth != -1) + { + printf("error in script-file \"%s\", line %d: %s\n", Filename[this->RecursionDepth], this->Line[this->RecursionDepth], text.c_str()); + do + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --this->RecursionDepth; + depth = this->RecursionDepth; + } while (this->RecursionDepth != -1); + } + } + + void nextToken(); + + std::string readIdentifier() + { + nextToken(); + if (this->Token != IDENTIFIER) + error("identifier expected"); + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return std::string(this->String); + } + + int readNumber() + { + TOKEN v1; // edx@1 + int v2; // esi@1 + + nextToken(); + v1 = this->Token; + v2 = 1; + if (this->Token == SPECIAL && this->Special == 45) + { + v2 = -1; + nextToken(); + v1 = this->Token; + } + if (v1 != NUMBER) + error("number expected"); + if (this->Token != NUMBER) + error("number expected"); + return this->Number * v2; + } + + std::string readString() + { + nextToken(); + if (this->Token != STRING) + error("string expected"); + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* readBytesequence() + { + nextToken(); + if (this->Token != 4) + error("byte-sequence expected"); + if (this->Token != 4) + error("byte-sequence expected"); + return this->Bytes; + } + + void readCoordinate(uint16_t& x, uint16_t& y, uint8_t& z) + { + nextToken(); + if (this->Token != COORDINATE) + error("coordinates expected"); + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int readSpecial() + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } + + void readSymbol(char Symbol) + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (Symbol != this->Special) + error("special-char expected"); + } + + std::string getIdentifier() + { + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return this->String; + } + + int getNumber() + { + if (this->Token != NUMBER) + error("number expected"); + return this->Number; + } + + std::string getString() + { + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* getBytesequence() + { + if (this->Token != BYTES) + error("byte-sequence expected"); + return this->Bytes; + } + + void getcoordinate(int32_t& x, int32_t& y, int32_t& z) + { + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int getSpecial() + { + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } +}; + +#endif diff --git a/app/SabrehavenServer/src/scriptmanager.cpp b/app/SabrehavenServer/src/scriptmanager.cpp new file mode 100644 index 0000000..f372962 --- /dev/null +++ b/app/SabrehavenServer/src/scriptmanager.cpp @@ -0,0 +1,107 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "scriptmanager.h" + +#include "actions.h" +#include "chat.h" +#include "talkaction.h" +#include "spells.h" +#include "movement.h" +#include "globalevent.h" +#include "events.h" + +Actions* g_actions = nullptr; +CreatureEvents* g_creatureEvents = nullptr; +Chat* g_chat = nullptr; +Events* g_events = nullptr; +GlobalEvents* g_globalEvents = nullptr; +Spells* g_spells = nullptr; +TalkActions* g_talkActions = nullptr; +MoveEvents* g_moveEvents = nullptr; + +extern LuaEnvironment g_luaEnvironment; + +ScriptingManager::~ScriptingManager() +{ + delete g_events; + delete g_spells; + delete g_actions; + delete g_talkActions; + delete g_moveEvents; + delete g_chat; + delete g_creatureEvents; + delete g_globalEvents; +} + +bool ScriptingManager::loadScriptSystems() +{ + if (g_luaEnvironment.loadFile("data/global.lua") == -1) { + std::cout << "[Warning - ScriptingManager::loadScriptSystems] Can not load data/global.lua" << std::endl; + } + + g_chat = new Chat(); + + g_spells = new Spells(); + if (!g_spells->loadFromXml()) { + std::cout << "> ERROR: Unable to load spells!" << std::endl; + return false; + } + + g_actions = new Actions(); + if (!g_actions->loadFromXml()) { + std::cout << "> ERROR: Unable to load actions!" << std::endl; + return false; + } + + g_talkActions = new TalkActions(); + if (!g_talkActions->loadFromXml()) { + std::cout << "> ERROR: Unable to load talk actions!" << std::endl; + return false; + } + + g_moveEvents = new MoveEvents(); + if (!g_moveEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load move events!" << std::endl; + return false; + } + + g_creatureEvents = new CreatureEvents(); + if (!g_creatureEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load creature events!" << std::endl; + return false; + } + + g_globalEvents = new GlobalEvents(); + if (!g_globalEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load global events!" << std::endl; + return false; + } + + g_events = new Events(); + if (!g_events->load()) { + std::cout << "> ERROR: Unable to load events!" << std::endl; + return false; + } + + + return true; +} diff --git a/app/SabrehavenServer/src/scriptmanager.h b/app/SabrehavenServer/src/scriptmanager.h new file mode 100644 index 0000000..fc5da53 --- /dev/null +++ b/app/SabrehavenServer/src/scriptmanager.h @@ -0,0 +1,41 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B +#define FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B + +class ScriptingManager +{ + public: + ScriptingManager() = default; + ~ScriptingManager(); + + // non-copyable + ScriptingManager(const ScriptingManager&) = delete; + ScriptingManager& operator=(const ScriptingManager&) = delete; + + static ScriptingManager* getInstance() { + static ScriptingManager instance; + return &instance; + } + + bool loadScriptSystems(); +}; + +#endif diff --git a/app/SabrehavenServer/src/server.cpp b/app/SabrehavenServer/src/server.cpp new file mode 100644 index 0000000..d681456 --- /dev/null +++ b/app/SabrehavenServer/src/server.cpp @@ -0,0 +1,210 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "outputmessage.h" +#include "server.h" +#include "scheduler.h" +#include "configmanager.h" +#include "ban.h" + +extern ConfigManager g_config; +Ban g_bans; + +ServiceManager::~ServiceManager() +{ + stop(); +} + +void ServiceManager::die() +{ + io_service.stop(); +} + +void ServiceManager::run() +{ + assert(!running); + running = true; + io_service.run(); +} + +void ServiceManager::stop() +{ + if (!running) { + return; + } + + running = false; + + for (auto& servicePortIt : acceptors) { + try { + io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); + } + catch (boost::system::system_error& e) { + std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; + } + } + + acceptors.clear(); + + death_timer.expires_from_now(boost::posix_time::seconds(3)); + death_timer.async_wait(std::bind(&ServiceManager::die, this)); +} + +ServicePort::~ServicePort() +{ + close(); +} + +bool ServicePort::is_single_socket() const +{ + return !services.empty() && services.front()->is_single_socket(); +} + +std::string ServicePort::get_protocol_names() const +{ + if (services.empty()) { + return std::string(); + } + + std::string str = services.front()->get_protocol_name(); + for (size_t i = 1; i < services.size(); ++i) { + str.push_back(','); + str.push_back(' '); + str.append(services[i]->get_protocol_name()); + } + return str; +} + +void ServicePort::accept() +{ + if (!acceptor) { + return; + } + + auto connection = ConnectionManager::getInstance().createConnection(io_service, shared_from_this()); + acceptor->async_accept(connection->getSocket(), std::bind(&ServicePort::onAccept, shared_from_this(), connection, std::placeholders::_1)); +} + +void ServicePort::onAccept(Connection_ptr connection, const boost::system::error_code& error) +{ + if (!error) { + if (services.empty()) { + return; + } + + auto remote_ip = connection->getIP(); + if (remote_ip != 0 && g_bans.acceptConnection(remote_ip)) { + Service_ptr service = services.front(); + if (service->is_single_socket()) { + connection->accept(service->make_protocol(connection)); + } + else { + connection->accept(); + } + } + else { + connection->close(Connection::FORCE_CLOSE); + } + + accept(); + } + else if (error != boost::asio::error::operation_aborted) { + if (!pendingStart) { + close(); + pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); + } + } +} + +Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const +{ + uint8_t protocolID = msg.getByte(); + for (auto& service : services) { + if (protocolID != service->get_protocol_identifier()) { + continue; + } + + return service->make_protocol(connection); + } + return nullptr; +} + +void ServicePort::onStopServer() +{ + close(); +} + +void ServicePort::openAcceptor(std::weak_ptr weak_service, uint16_t port) +{ + if (auto service = weak_service.lock()) { + service->open(port); + } +} + +void ServicePort::open(uint16_t port) +{ + close(); + + serverPort = port; + pendingStart = false; + + try { + if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { + acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); + } + else { + acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); + } + + acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); + + accept(); + } + catch (boost::system::system_error& e) { + std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; + + pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); + } +} + +void ServicePort::close() +{ + if (acceptor && acceptor->is_open()) { + boost::system::error_code error; + acceptor->close(error); + } +} + +bool ServicePort::add_service(const Service_ptr& new_svc) +{ + if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket(); })) { + return false; + } + + services.push_back(new_svc); + return true; +} diff --git a/app/SabrehavenServer/src/server.h b/app/SabrehavenServer/src/server.h new file mode 100644 index 0000000..e1d9aa2 --- /dev/null +++ b/app/SabrehavenServer/src/server.h @@ -0,0 +1,151 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SERVER_H_984DA68ABF744127850F90CC710F281B +#define FS_SERVER_H_984DA68ABF744127850F90CC710F281B + +#include "connection.h" +#include + +class Protocol; + +class ServiceBase +{ +public: + virtual bool is_single_socket() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; + + virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; +}; + +template +class Service final : public ServiceBase +{ +public: + bool is_single_socket() const final { + return ProtocolType::server_sends_first; + } + uint8_t get_protocol_identifier() const final { + return ProtocolType::protocol_identifier; + } + const char* get_protocol_name() const final { + return ProtocolType::protocol_name(); + } + + Protocol_ptr make_protocol(const Connection_ptr& c) const final { + return std::make_shared(c); + } +}; + +class ServicePort : public std::enable_shared_from_this +{ +public: + explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} + ~ServicePort(); + + // non-copyable + ServicePort(const ServicePort&) = delete; + ServicePort& operator=(const ServicePort&) = delete; + + static void openAcceptor(std::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; + + bool add_service(const Service_ptr& new_svc); + Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; + + void onStopServer(); + void onAccept(Connection_ptr connection, const boost::system::error_code& error); + +protected: + void accept(); + + boost::asio::io_service& io_service; + std::unique_ptr acceptor; + std::vector services; + + uint16_t serverPort = 0; + bool pendingStart = false; +}; + +class ServiceManager +{ +public: + ServiceManager() = default; + ~ServiceManager(); + + // non-copyable + ServiceManager(const ServiceManager&) = delete; + ServiceManager& operator=(const ServiceManager&) = delete; + + void run(); + void stop(); + + template + bool add(uint16_t port); + + bool is_running() const { + return acceptors.empty() == false; + } + +protected: + void die(); + + std::unordered_map acceptors; + + boost::asio::io_service io_service; + boost::asio::deadline_timer death_timer{ io_service }; + bool running = false; +}; + +template +bool ServiceManager::add(uint16_t port) +{ + if (port == 0) { + std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." << std::endl; + return false; + } + + ServicePort_ptr service_port; + + auto foundServicePort = acceptors.find(port); + + if (foundServicePort == acceptors.end()) { + service_port = std::make_shared(io_service); + service_port->open(port); + acceptors[port] = service_port; + } + else { + service_port = foundServicePort->second; + + if (service_port->is_single_socket() || ProtocolType::server_sends_first) { + std::cout << "ERROR: " << ProtocolType::protocol_name() << + " and " << service_port->get_protocol_names() << + " cannot use the same port " << port << '.' << std::endl; + return false; + } + } + + return service_port->add_service(std::make_shared>()); +} + +#endif diff --git a/app/SabrehavenServer/src/spawn.cpp b/app/SabrehavenServer/src/spawn.cpp new file mode 100644 index 0000000..26350aa --- /dev/null +++ b/app/SabrehavenServer/src/spawn.cpp @@ -0,0 +1,344 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "spawn.h" +#include "game.h" +#include "monster.h" +#include "configmanager.h" +#include "scheduler.h" + +#include "pugicast.h" + +extern ConfigManager g_config; +extern Monsters g_monsters; +extern Game g_game; + +static constexpr int32_t MINSPAWN_INTERVAL = 1000; + +bool Spawns::loadFromXml(const std::string& filename) +{ + if (loaded) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Spawns::loadFromXml", filename, result); + return false; + } + + this->filename = filename; + loaded = true; + + for (auto spawnNode : doc.child("spawns").children()) { + Position centerPos( + pugi::cast(spawnNode.attribute("centerx").value()), + pugi::cast(spawnNode.attribute("centery").value()), + pugi::cast(spawnNode.attribute("centerz").value()) + ); + + int32_t radius; + pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius"); + if (radiusAttribute) { + radius = pugi::cast(radiusAttribute.value()); + } else { + radius = -1; + } + + for (auto childNode : spawnNode.children()) { + if (strcasecmp(childNode.name(), "monster") == 0) { + pugi::xml_attribute nameAttribute = childNode.attribute("name"); + if (!nameAttribute) { + continue; + } + + Direction dir; + + pugi::xml_attribute directionAttribute = childNode.attribute("direction"); + if (directionAttribute) { + dir = static_cast(pugi::cast(directionAttribute.value())); + } else { + dir = DIRECTION_NORTH; + } + + Position pos( + centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), + centerPos.z + ); + + spawnList.emplace_front(pos, radius); + Spawn& spawn = spawnList.front(); + + uint32_t interval = uniform_random(pugi::cast(childNode.attribute("spawntime").value()) * g_config.getNumber(ConfigManager::MIN_RATE_SPAWN), pugi::cast(childNode.attribute("spawntime").value()) * g_config.getNumber(ConfigManager::MAX_RATE_SPAWN)); + if (interval > MINSPAWN_INTERVAL) { + uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN); + if (exInterval) { + spawn.addMonster(nameAttribute.as_string(), pos, dir, exInterval * 1000); + } else { + spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); + } + } else { + std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + } + } else if (strcasecmp(childNode.name(), "npc") == 0) { + pugi::xml_attribute nameAttribute = childNode.attribute("name"); + if (!nameAttribute) { + continue; + } + + Npc* npc = Npc::createNpc(nameAttribute.as_string()); + if (!npc) { + continue; + } + + pugi::xml_attribute directionAttribute = childNode.attribute("direction"); + if (directionAttribute) { + npc->setDirection(static_cast(pugi::cast(directionAttribute.value()))); + } + + npc->setMasterPos(Position( + centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), + centerPos.z + ), radius); + npcList.push_front(npc); + } + } + } + return true; +} + +void Spawns::startup() +{ + if (!loaded || isStarted()) { + return; + } + + for (Npc* npc : npcList) { + g_game.placeCreature(npc, npc->getMasterPos(), false, true); + } + npcList.clear(); + + for (Spawn& spawn : spawnList) { + spawn.startup(); + } + + started = true; +} + +void Spawns::clear() +{ + for (Spawn& spawn : spawnList) { + spawn.stopEvent(); + } + spawnList.clear(); + + loaded = false; + started = false; + filename.clear(); +} + +bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos) +{ + if (radius == -1) { + return true; + } + + return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) && + (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius)); +} + +void Spawn::startSpawnCheck() +{ + if (checkSpawnEvent == 0) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + } +} + +Spawn::~Spawn() +{ + for (const auto& it : spawnedMap) { + Monster* monster = it.second; + monster->setSpawn(nullptr); + monster->decrementReferenceCounter(); + } +} + +bool Spawn::findPlayer(const Position& pos) +{ + SpectatorVec list; + g_game.map.getSpectators(list, pos, false, true); + for (Creature* spectator : list) { + if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { + return true; + } + } + return false; +} + +bool Spawn::isInSpawnZone(const Position& pos) +{ + return Spawns::isInZone(centerPos, radius, pos); +} + +bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/) +{ + std::unique_ptr monster_ptr(new Monster(mType)); + if (startup) { + //No need to send out events to the surrounding since there is no one out there to listen! + if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) { + return false; + } + } else { + if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) { + return false; + } + } + + Monster* monster = monster_ptr.release(); + monster->setDirection(dir); + monster->setSpawn(this); + monster->setMasterPos(pos); + monster->incrementReferenceCounter(); + + spawnedMap.insert(spawned_pair(spawnId, monster)); + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + return true; +} + +uint32_t Spawn::getInterval() const +{ + uint32_t newInterval = interval; + + if (newInterval > 500000) { + size_t playersOnline = g_game.getPlayersOnline(); + if (playersOnline <= 800) { + if (playersOnline > 200) { + newInterval = 200 * interval / (playersOnline / 2 + 100); + } + } else { + newInterval = 2 * interval / 5; + } + + return normal_random(newInterval / 2, newInterval); + } + + return newInterval; +} + +void Spawn::startup() +{ + for (const auto& it : spawnMap) { + uint32_t spawnId = it.first; + const spawnBlock_t& sb = it.second; + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true); + } +} + +void Spawn::checkSpawn() +{ + checkSpawnEvent = 0; + + cleanup(); + + for (auto& it : spawnMap) { + uint32_t spawnId = it.first; + if (spawnedMap.find(spawnId) != spawnedMap.end()) { + continue; + } + + spawnBlock_t& sb = it.second; + if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { + if (findPlayer(sb.pos)) { + sb.lastSpawn = OTSYS_TIME(); + continue; + } + + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); + } + } + + if (spawnedMap.size() < spawnMap.size()) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + } +} + +void Spawn::cleanup() +{ + auto it = spawnedMap.begin(); + while (it != spawnedMap.end()) { + uint32_t spawnId = it->first; + Monster* monster = it->second; + if (monster->isRemoved()) { + if (spawnId != 0) { + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + } + + monster->decrementReferenceCounter(); + it = spawnedMap.erase(it); + } else { + ++it; + } + } +} + +bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval) +{ + MonsterType* mType = g_monsters.getMonsterType(name); + if (!mType) { + std::cout << "[Spawn::addMonster] Can not find " << name << std::endl; + return false; + } + + this->interval = std::min(this->interval, interval); + + spawnBlock_t sb; + sb.mType = mType; + sb.pos = pos; + sb.direction = dir; + sb.interval = interval; + sb.lastSpawn = 0; + + uint32_t spawnId = spawnMap.size() + 1; + spawnMap[spawnId] = sb; + return true; +} + +void Spawn::removeMonster(Monster* monster) +{ + for (auto it = spawnedMap.begin(), end = spawnedMap.end(); it != end; ++it) { + if (it->second == monster) { + monster->decrementReferenceCounter(); + spawnedMap.erase(it); + break; + } + } +} + +void Spawn::stopEvent() +{ + if (checkSpawnEvent != 0) { + g_scheduler.stopEvent(checkSpawnEvent); + checkSpawnEvent = 0; + } +} diff --git a/app/SabrehavenServer/src/spawn.h b/app/SabrehavenServer/src/spawn.h new file mode 100644 index 0000000..aece74e --- /dev/null +++ b/app/SabrehavenServer/src/spawn.h @@ -0,0 +1,101 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B +#define FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B + +#include "tile.h" +#include "position.h" + +class Monster; +class MonsterType; +class Npc; + +struct spawnBlock_t { + Position pos; + MonsterType* mType; + int64_t lastSpawn; + uint32_t interval; + Direction direction; +}; + +class Spawn +{ + public: + Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {} + ~Spawn(); + + // non-copyable + Spawn(const Spawn&) = delete; + Spawn& operator=(const Spawn&) = delete; + + bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); + void removeMonster(Monster* monster); + + uint32_t getInterval() const; + void startup(); + + void startSpawnCheck(); + void stopEvent(); + + bool isInSpawnZone(const Position& pos); + void cleanup(); + + private: + //map of the spawned creatures + typedef std::multimap SpawnedMap; + typedef SpawnedMap::value_type spawned_pair; + SpawnedMap spawnedMap; + + //map of creatures in the spawn + std::map spawnMap; + + Position centerPos; + int32_t radius; + + uint32_t interval = 60000; + uint32_t checkSpawnEvent = 0; + + static bool findPlayer(const Position& pos); + bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); + void checkSpawn(); +}; + +class Spawns +{ + public: + static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); + + bool loadFromXml(const std::string& filename); + void startup(); + void clear(); + + bool isStarted() const { + return started; + } + + private: + std::forward_list npcList; + std::forward_list spawnList; + std::string filename; + bool loaded = false; + bool started = false; +}; + +#endif diff --git a/app/SabrehavenServer/src/spells.cpp b/app/SabrehavenServer/src/spells.cpp new file mode 100644 index 0000000..228872a --- /dev/null +++ b/app/SabrehavenServer/src/spells.cpp @@ -0,0 +1,1910 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "combat.h" +#include "configmanager.h" +#include "game.h" +#include "monster.h" +#include "pugicast.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern Vocations g_vocations; +extern ConfigManager g_config; +extern LuaEnvironment g_luaEnvironment; + +Spells::Spells() +{ + scriptInterface.initState(); +} + +Spells::~Spells() +{ + clear(); +} + +TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) +{ + std::string str_words = words; + + //strip trailing spaces + trimString(str_words); + + std::ostringstream str_instantSpell; + for (size_t i = 0; i < str_words.length(); i++) { + if (!isspace(str_words[i]) || (i < str_words.length() - 1 && !isspace(str_words[i + 1]))) { + str_instantSpell << str_words[i]; + } + } + + str_words = str_instantSpell.str(); + + InstantSpell* instantSpell = getInstantSpell(str_words); + if (!instantSpell) { + return TALKACTION_CONTINUE; + } + + std::string param; + + if (instantSpell->getHasParam()) { + size_t spellLen = instantSpell->getWords().length(); + size_t paramLen = str_words.length() - spellLen; + std::string paramText = str_words.substr(spellLen, paramLen); + if (!paramText.empty() && paramText.front() == ' ') { + size_t loc1 = paramText.find('"', 1); + if (loc1 != std::string::npos) { + size_t loc2 = paramText.find('"', loc1 + 1); + if (loc2 == std::string::npos) { + loc2 = paramText.length(); + } else if (paramText.find_last_not_of(' ') != loc2) { + return TALKACTION_CONTINUE; + } + + param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); + } else { + trimString(paramText); + loc1 = paramText.find(' ', 0); + if (loc1 == std::string::npos) { + param = paramText; + } else { + return TALKACTION_CONTINUE; + } + } + } + } + + if (instantSpell->playerCastInstant(player, param)) { + return TALKACTION_BREAK; + } + + return TALKACTION_FAILED; +} + +void Spells::clear() +{ + for (const auto& it : runes) { + delete it.second; + } + runes.clear(); + + for (const auto& it : instants) { + delete it.second; + } + instants.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& Spells::getScriptInterface() +{ + return scriptInterface; +} + +std::string Spells::getScriptBaseName() const +{ + return "spells"; +} + +Event* Spells::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "rune") == 0) { + return new RuneSpell(&scriptInterface); + } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { + return new InstantSpell(&scriptInterface); + } else if (strcasecmp(nodeName.c_str(), "conjure") == 0) { + return new ConjureSpell(&scriptInterface); + } + return nullptr; +} + +bool Spells::registerEvent(Event* event, const pugi::xml_node&) +{ + InstantSpell* instant = dynamic_cast(event); + if (instant) { + auto result = instants.emplace(instant->getWords(), instant); + if (!result.second) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; + } + return result.second; + } + + RuneSpell* rune = dynamic_cast(event); + if (rune) { + auto result = runes.emplace(rune->getRuneItemId(), rune); + if (!result.second) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; + } + return result.second; + } + + return false; +} + +Spell* Spells::getSpellByName(const std::string& name) +{ + Spell* spell = getRuneSpellByName(name); + if (!spell) { + spell = getInstantSpellByName(name); + } + return spell; +} + +RuneSpell* Spells::getRuneSpell(uint32_t id) +{ + auto it = runes.find(id); + if (it == runes.end()) { + return nullptr; + } + return it->second; +} + +RuneSpell* Spells::getRuneSpellByName(const std::string& name) +{ + for (const auto& it : runes) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +InstantSpell* Spells::getInstantSpell(const std::string& words) +{ + InstantSpell* result = nullptr; + + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + + const std::string& instantSpellWords = instantSpell->getWords(); + size_t spellLen = instantSpellWords.length(); + if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { + if (!result || spellLen > result->getWords().length()) { + result = instantSpell; + if (words.length() == spellLen) { + break; + } + } + } + } + + if (result) { + const std::string& resultWords = result->getWords(); + if (words.length() > resultWords.length()) { + size_t spellLen = resultWords.length(); + size_t paramLen = words.length() - spellLen; + if (paramLen < 2 || words[spellLen] != ' ') { + return nullptr; + } + } + + return result; + } + + return nullptr; +} + +uint32_t Spells::getInstantSpellCount(const Player* player) const +{ + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + ++count; + } + } + return count; +} + +InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) +{ + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + if (count == index) { + return instantSpell; + } + ++count; + } + } + return nullptr; +} + +InstantSpell* Spells::getInstantSpellByName(const std::string& name) +{ + for (const auto& it : instants) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +Position Spells::getCasterPosition(Creature* creature, Direction dir) +{ + return getNextPosition(dir, creature->getPosition()); +} + +CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) : + Event(&g_spells->getScriptInterface()), + combat(combat), + needDirection(needDirection), + needTarget(needTarget) +{} + +CombatSpell::~CombatSpell() +{ + if (!scripted) { + delete combat; + } +} + +bool CombatSpell::loadScriptCombat() +{ + combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId); + return combat != nullptr; +} + +bool CombatSpell::castSpell(Creature* creature) +{ + if (scripted) { + LuaVariant var; + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + + return executeCastSpell(creature, var); + } + + Position pos; + if (needDirection) { + pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + pos = creature->getPosition(); + } + + combat->doCombat(creature, pos); + return true; +} + +bool CombatSpell::castSpell(Creature* creature, Creature* target) +{ + if (scripted) { + LuaVariant var; + + if (combat->hasArea()) { + var.type = VARIANT_POSITION; + + if (needTarget) { + var.pos = target->getPosition(); + } else if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + } else { + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } + return executeCastSpell(creature, var); + } + + if (combat->hasArea()) { + if (needTarget) { + combat->doCombat(creature, target->getPosition()); + } else { + return castSpell(creature); + } + } else { + combat->doCombat(creature, target); + } + return true; +} + +bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(creature, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + return scriptInterface->callFunction(2); +} + +bool Spell::configureSpell(const pugi::xml_node& node) +{ + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl; + return false; + } + + name = nameAttribute.as_string(); + + /*static const char* reservedList[] = { + "melee", + "physical", + "poison", + "fire", + "energy", + "drown", + "lifedrain", + "manadrain", + "healing", + "speed", + "outfit", + "invisible", + "drunk", + "firefield", + "poisonfield", + "energyfield", + "firecondition", + "poisoncondition", + "energycondition", + "drowncondition", + }; + + //static size_t size = sizeof(reservedList) / sizeof(const char*); + //for (size_t i = 0; i < size; ++i) { + for (const char* reserved : reservedList) { + if (strcasecmp(reserved, name.c_str()) == 0) { + std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; + return false; + } + }*/ + + pugi::xml_attribute attr; + if ((attr = node.attribute("spellid"))) { + spellId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("lvl"))) { + level = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("maglv"))) { + magLevel = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("mana"))) { + mana = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("manapercent"))) { + manaPercent = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("soul"))) { + soul = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("range"))) { + range = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) { + cooldown = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("prem"))) { + premium = attr.as_bool(); + } + + if ((attr = node.attribute("enabled"))) { + enabled = attr.as_bool(); + } + + if ((attr = node.attribute("needtarget"))) { + needTarget = attr.as_bool(); + } + + if ((attr = node.attribute("needweapon"))) { + needWeapon = attr.as_bool(); + } + + if ((attr = node.attribute("selftarget"))) { + selfTarget = attr.as_bool(); + } + + if ((attr = node.attribute("needlearn"))) { + learnable = attr.as_bool(); + } + + if ((attr = node.attribute("blocking"))) { + blockingSolid = attr.as_bool(); + blockingCreature = blockingSolid; + } + + if ((attr = node.attribute("blocktype"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + if (tmpStrValue == "all") { + blockingSolid = true; + blockingCreature = true; + } else if (tmpStrValue == "solid") { + blockingSolid = true; + } else if (tmpStrValue == "creature") { + blockingCreature = true; + } else { + std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl; + } + } + + if ((attr = node.attribute("aggressive"))) { + aggressive = booleanString(attr.as_string()); + } + + for (auto vocationNode : node.children()) { + if (!(attr = vocationNode.attribute("name"))) { + continue; + } + + int32_t vocationId = g_vocations.getVocationId(attr.as_string()); + if (vocationId != -1) { + attr = vocationNode.attribute("showInDescription"); + vocSpellMap[vocationId] = !attr || attr.as_bool(); + } else { + std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl; + } + } + return true; +} + +bool Spell::playerSpellCheck(Player* player) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (!enabled) { + return false; + } + + if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); + return false; + } + + if (player->hasCondition(CONDITION_EXHAUST)) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + + if (isInstant()) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + + return false; + } + + if (player->getLevel() < level) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMagicLevel() < magLevel) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (isInstant() && isLearnable()) { + if (!player->hasLearnedInstantSpell(getName())) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) { + player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (needWeapon) { + Item* weapon = player->getWeapon(); + if (!weapon) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + switch (weapon->getWeaponType()) { + case WEAPON_SWORD: + case WEAPON_CLUB: + case WEAPON_AXE: + break; + + default: { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + } + + if (isPremium() && !player->isPremium()) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + return true; +} + +bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) +{ + if (toPos.x == 0xFFFF) { + return true; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile) { + tile = new StaticTile(toPos.x, toPos.y, toPos.z); + g_game.map.setTile(toPos, tile); + } + + ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + return true; +} + +bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (toPos.x == 0xFFFF) { + return true; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + const Creature* visibleCreature = tile->getTopCreature(); + if (g_config.getBoolean(ConfigManager::UH_TRAP)) { + visibleCreature = tile->getBottomCreature(); + } + if (blockingCreature && visibleCreature) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (needTarget && !visibleCreature) { + player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (aggressive && needTarget && visibleCreature && player->hasSecureMode()) { + const Player* targetPlayer = visibleCreature->getPlayer(); + if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { + player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + return true; +} + +void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const +{ + if (finishedCast) { + if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { + if (aggressive) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 2000); + player->addCondition(condition); + } + } else { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 1000); + player->addCondition(condition); + } + } + } + + if (aggressive) { + player->addInFightTicks(); + } + } + + if (payCost) { + Spell::postCastSpell(player, getManaCost(player), getSoulCost()); + } +} + +void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) +{ + if (manaCost > 0) { + player->addManaSpent(manaCost); + player->changeMana(-static_cast(manaCost)); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + if (soulCost > 0) { + player->changeSoul(-static_cast(soulCost)); + } + } +} + +uint32_t Spell::getManaCost(const Player* player) const +{ + if (mana != 0) { + return mana; + } + + if (manaPercent != 0) { + return player->getLevel() * manaPercent; + } + + return 0; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) +{ + ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); + outfitCondition->setOutfit(outfit); + creature->addCondition(outfitCondition); + return RETURNVALUE_NOERROR; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) +{ + const auto mType = g_monsters.getMonsterType(name); + if (mType == nullptr) { + return RETURNVALUE_CREATUREDOESNOTEXIST; + } + + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { + if (!mType->info.isIllusionable) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + return CreateIllusion(creature, mType->info.outfit, time); +} + +ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) +{ + const ItemType& it = Item::items[itemId]; + if (it.id == 0) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Outfit_t outfit; + outfit.lookTypeEx = itemId; + + return CreateIllusion(creature, outfit, time); +} + +std::string InstantSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool InstantSpell::configureEvent(const pugi::xml_node& node) +{ + if (!Spell::configureSpell(node)) { + return false; + } + + if (!TalkAction::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("params"))) { + hasParam = attr.as_bool(); + } + + if ((attr = node.attribute("playernameparam"))) { + hasPlayerNameParam = attr.as_bool(); + } + + if ((attr = node.attribute("direction"))) { + needDirection = attr.as_bool(); + } else if ((attr = node.attribute("casterTargetOrDirection"))) { + casterTargetOrDirection = attr.as_bool(); + } + + if ((attr = node.attribute("blockwalls"))) { + checkLineOfSight = attr.as_bool(); + } + return true; +} + +bool InstantSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "edithouseguest") == 0) { + function = HouseGuestList; + } else if (strcasecmp(functionName, "edithousesubowner") == 0) { + function = HouseSubOwnerList; + } else if (strcasecmp(functionName, "edithousedoor") == 0) { + function = HouseDoorList; + } else if (strcasecmp(functionName, "housekick") == 0) { + function = HouseKick; + } else if (strcasecmp(functionName, "searchplayer") == 0) { + function = SearchPlayer; + } else if (strcasecmp(functionName, "levitate") == 0) { + function = Levitate; + } else if (strcasecmp(functionName, "illusion") == 0) { + function = Illusion; + } else if (strcasecmp(functionName, "summonmonster") == 0) { + function = SummonMonster; + } else { + std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +bool InstantSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + LuaVariant var; + + if (selfTarget) { + var.type = VARIANT_NUMBER; + var.number = player->getID(); + } else if (needTarget || casterTargetOrDirection) { + Creature* target = nullptr; + bool useDirection = false; + + if (hasParam) { + Player* playerTarget = nullptr; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) { + playerTarget = nullptr; + } + + target = playerTarget; + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + useDirection = true; + } + + if (playerTarget) { + param = playerTarget->getName(); + } + } else { + target = player->getAttackedCreature(); + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + useDirection = true; + } + } + + if (!useDirection) { + if (!canThrowSpell(player, target)) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } else { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(player, player->getDirection()); + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + } else if (hasParam) { + var.type = VARIANT_STRING; + + if (getHasPlayerNameParam()) { + Player* playerTarget = nullptr; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) { + param = playerTarget->getName(); + } + } + + var.text = param; + } else { + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(player, player->getDirection()); + } else { + var.pos = player->getPosition(); + } + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + + bool result = internalCastSpell(player, var); + if (result) { + postCastSpell(player); + } + + return result; +} + +bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const +{ + const Position& fromPos = creature->getPosition(); + const Position& toPos = target->getPosition(); + if (fromPos.z != toPos.z || + (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || + (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { + return false; + } + return true; +} + +bool InstantSpell::castSpell(Creature* creature) +{ + LuaVariant var; + + if (casterTargetOrDirection) { + Creature* target = creature->getAttackedCreature(); + if (target && target->getHealth() > 0) { + if (!canThrowSpell(creature, target)) { + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } + + return false; + } else if (needDirection) { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.type = VARIANT_POSITION; + var.pos = creature->getPosition(); + } + + return internalCastSpell(creature, var); +} + +bool InstantSpell::castSpell(Creature* creature, Creature* target) +{ + if (needTarget) { + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } else { + return castSpell(creature); + } +} + +bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) +{ + if (scripted) { + return executeCastSpell(creature, var); + } else if (function) { + return function(this, creature, var.text); + } + + return false; +} + +bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(creature, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + return scriptInterface->callFunction(2); +} + +House* InstantSpell::getHouseFromPos(Creature* creature) +{ + if (!creature) { + return nullptr; + } + + Player* player = creature->getPlayer(); + if (!player) { + return nullptr; + } + + HouseTile* houseTile = dynamic_cast(player->getTile()); + if (!houseTile) { + return nullptr; + } + + House* house = houseTile->getHouse(); + if (!house) { + return nullptr; + } + + return house; +} + +bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(GUEST_LIST, player)) { + player->setEditHouse(house, GUEST_LIST); + player->sendHouseWindow(house, GUEST_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(SUBOWNER_LIST, player)) { + player->setEditHouse(house, SUBOWNER_LIST); + player->sendHouseWindow(house, SUBOWNER_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + Position pos = Spells::getCasterPosition(player, player->getDirection()); + Door* door = house->getDoorByPosition(pos); + if (door && house->canEditAccessList(door->getDoorId(), player)) { + player->setEditHouse(house, door->getDoorId()); + player->sendHouseWindow(house, door->getDoorId()); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + Player* targetPlayer = g_game.getPlayerByName(param); + if (!targetPlayer) { + targetPlayer = player; + } + + House* house = getHouseFromPos(targetPlayer); + if (!house) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + + if (!house->kickPlayer(player, targetPlayer)) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + return true; +} + +bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param) +{ + //a. From 1 to 4 sq's [Person] is standing next to you. + //b. From 5 to 100 sq's [Person] is to the south, north, east, west. + //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. + //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. + //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. + //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + enum distance_t { + DISTANCE_BESIDE, + DISTANCE_CLOSE, + DISTANCE_FAR, + DISTANCE_VERYFAR, + }; + + enum direction_t { + DIR_N, DIR_S, DIR_E, DIR_W, + DIR_NE, DIR_NW, DIR_SE, DIR_SW, + }; + + enum level_t { + LEVEL_HIGHER, + LEVEL_LOWER, + LEVEL_SAME, + }; + + Player* playerExiva = g_game.getPlayerByName(param); + if (!playerExiva) { + return false; + } + + if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { + player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + const Position& lookPos = player->getPosition(); + const Position& searchPos = playerExiva->getPosition(); + + int32_t dx = Position::getOffsetX(lookPos, searchPos); + int32_t dy = Position::getOffsetY(lookPos, searchPos); + int32_t dz = Position::getOffsetZ(lookPos, searchPos); + + distance_t distance; + + direction_t direction; + + level_t level; + + //getting floor + if (dz > 0) { + level = LEVEL_HIGHER; + } else if (dz < 0) { + level = LEVEL_LOWER; + } else { + level = LEVEL_SAME; + } + + //getting distance + if (std::abs(dx) < 4 && std::abs(dy) < 4) { + distance = DISTANCE_BESIDE; + } else { + int32_t distance2 = dx * dx + dy * dy; + if (distance2 < 10000) { + distance = DISTANCE_CLOSE; + } else if (distance2 < 75076) { + distance = DISTANCE_FAR; + } else { + distance = DISTANCE_VERYFAR; + } + } + + //getting direction + float tan; + if (dx != 0) { + tan = static_cast(dy) / dx; + } else { + tan = 10.; + } + + if (std::abs(tan) < 0.4142) { + if (dx > 0) { + direction = DIR_W; + } else { + direction = DIR_E; + } + } else if (std::abs(tan) < 2.4142) { + if (tan > 0) { + if (dy > 0) { + direction = DIR_NW; + } else { + direction = DIR_SE; + } + } else { + if (dx > 0) { + direction = DIR_SW; + } else { + direction = DIR_NE; + } + } + } else { + if (dy > 0) { + direction = DIR_N; + } else { + direction = DIR_S; + } + } + + std::ostringstream ss; + ss << playerExiva->getName(); + + if (distance == DISTANCE_BESIDE) { + if (level == LEVEL_SAME) { + ss << " is standing next to you."; + } else if (level == LEVEL_HIGHER) { + ss << " is above you."; + } else if (level == LEVEL_LOWER) { + ss << " is below you."; + } + } else { + switch (distance) { + case DISTANCE_CLOSE: + if (level == LEVEL_SAME) { + ss << " is to the "; + } else if (level == LEVEL_HIGHER) { + ss << " is on a higher level to the "; + } else if (level == LEVEL_LOWER) { + ss << " is on a lower level to the "; + } + break; + case DISTANCE_FAR: + ss << " is far to the "; + break; + case DISTANCE_VERYFAR: + ss << " is very far to the "; + break; + default: + break; + } + + switch (direction) { + case DIR_N: + ss << "north."; + break; + case DIR_S: + ss << "south."; + break; + case DIR_E: + ss << "east."; + break; + case DIR_W: + ss << "west."; + break; + case DIR_NE: + ss << "north-east."; + break; + case DIR_NW: + ss << "north-west."; + break; + case DIR_SE: + ss << "south-east."; + break; + case DIR_SW: + ss << "south-west."; + break; + } + } + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + return true; +} + +bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + MonsterType* mType = g_monsters.getMonsterType(param); + if (!mType) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!player->hasFlag(PlayerFlag_CanSummonAll)) { + if (!mType->info.isSummonable) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMana() < mType->info.manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getSummonCount() >= 2) { + player->sendCancelMessage("You cannot summon more creatures."); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Monster* monster = Monster::createMonster(param); + if (!monster) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + // Place the monster + creature->addSummon(monster); + + if (!g_game.placeCreature(monster, creature->getPosition(), true)) { + creature->removeSummon(monster); + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, mType->info.manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const Position& currentPos = creature->getPosition(); + const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); + + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + + if (strcasecmp(param.c_str(), "up") == 0) { + if (currentPos.z != 8) { + Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } else if (strcasecmp(param.c_str(), "down") == 0) { + if (currentPos.z != 7) { + Tile* tmpTile = g_game.map.getTile(destPos); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } + + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + ReturnValue ret = CreateIllusion(creature, param, 180000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool InstantSpell::canCast(const Player* player) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (isLearnable()) { + if (player->hasLearnedInstantSpell(getName())) { + return true; + } + } else { + if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) { + return true; + } + } + + return false; +} + +std::string ConjureSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool ConjureSpell::configureEvent(const pugi::xml_node& node) +{ + if (!InstantSpell::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("conjureId"))) { + conjureId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("conjureCount"))) { + conjureCount = pugi::cast(attr.value()); + } else if (conjureId != 0) { + // load default charges from items.xml + const ItemType& it = Item::items[conjureId]; + if (it.charges != 0) { + conjureCount = it.charges; + } + } + + if ((attr = node.attribute("reagentId"))) { + reagentId = pugi::cast(attr.value()); + } + + ItemType& iType = Item::items.getItemType(conjureId); + if (iType.isRune()) { + iType.runeSpellName = words; + } + + return true; +} + +bool ConjureSpell::loadFunction(const pugi::xml_attribute&) +{ + scripted = false; + return true; +} + +bool ConjureSpell::conjureItem(Creature* creature) const +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const uint32_t conjureCost = getManaCost(player); + const uint32_t soulCost = getSoulCost(); + + if (reagentId != 0) { + bool foundReagent = false; + + Item* item = player->getInventoryItem(CONST_SLOT_LEFT); + if (item && item->getID() == reagentId) { + foundReagent = true; + + // left arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + item = player->getInventoryItem(CONST_SLOT_RIGHT); + if (item && item->getID() == reagentId && player->getMana() >= conjureCost) { + foundReagent = true; + + // right arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + if (!foundReagent) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } else { + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + Spell::postCastSpell(player, conjureCost, soulCost); + } + + postCastSpell(player, true, false); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool ConjureSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (scripted) { + LuaVariant var; + var.type = VARIANT_STRING; + var.text = param; + return executeCastSpell(player, var); + } + return conjureItem(player); +} + +std::string RuneSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool RuneSpell::configureEvent(const pugi::xml_node& node) +{ + if (!Spell::configureSpell(node)) { + return false; + } + + if (!Action::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if (!(attr = node.attribute("id"))) { + std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; + return false; + } + runeId = pugi::cast(attr.value()); + + uint32_t charges; + if ((attr = node.attribute("charges"))) { + charges = pugi::cast(attr.value()); + } else { + charges = 0; + } + + hasCharges = (charges > 0); + + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(runeId); + iType.runeMagLevel = magLevel; + iType.runeLevel = level; + iType.charges = charges; + + return true; +} + +bool RuneSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "chameleon") == 0) { + runeFunction = Illusion; + } else if (strcasecmp(functionName, "convince") == 0) { + runeFunction = Convince; + } else { + std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo) +{ + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Item* illusionItem = thing->getItem(); + if (!illusionItem || !illusionItem->isMoveable()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t itemId = illusionItem->getID(); + if (illusionItem->isDisguised()) { + itemId = illusionItem->getDisguiseId(); + } + + ReturnValue ret = CreateIllusion(player, itemId, 200000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo) +{ + if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (player->getSummonCount() >= 2) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Creature* convinceCreature = thing->getCreature(); + if (!convinceCreature) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t manaCost = 0; + if (convinceCreature->getMonster()) { + manaCost = convinceCreature->getMonster()->getManaCost(); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!convinceCreature->convinceCreature(player)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos) +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + + ReturnValue ret = Action::canExecuteAction(player, toPos); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (toPos.x == 0xFFFF) { + if (needTarget) { + return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES; + } else if (!selfTarget) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + + return RETURNVALUE_NOERROR; +} + +bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey) +{ + if (!playerRuneSpellCheck(player, toPosition)) { + return false; + } + + bool result = false; + if (scripted) { + LuaVariant var; + + if (needTarget) { + var.type = VARIANT_NUMBER; + + if (target == nullptr) { + Tile* toTile = g_game.map.getTile(toPosition); + if (toTile) { + const Creature* visibleCreature = toTile->getTopCreature(); + if (g_config.getBoolean(ConfigManager::UH_TRAP)) { + visibleCreature = toTile->getBottomCreature(); + } + if (visibleCreature) { + var.number = visibleCreature->getID(); + } + } + } + else { + var.number = target->getCreature()->getID(); + } + } + else { + var.type = VARIANT_POSITION; + var.pos = toPosition; + } + + result = internalCastSpell(player, var, isHotkey); + } else if (runeFunction) { + result = runeFunction(this, player, toPosition); + } + + if (!result) { + return false; + } + + postCastSpell(player); + if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { + int32_t newCount = std::max(0, item->getCharges() - 1); + g_game.transformItem(item, item->getID(), newCount); + } + return true; +} + +bool RuneSpell::castSpell(Creature* creature) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = creature->getID(); + return internalCastSpell(creature, var, false); +} + +bool RuneSpell::castSpell(Creature* creature, Creature* target) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var, false); +} + +bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) +{ + bool result; + if (scripted) { + result = executeCastSpell(creature, var, isHotkey); + } + else { + result = false; + } + return result; +} + +bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) +{ + //onCastSpell(creature, var, isHotkey) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + LuaScriptInterface::pushBoolean(L, isHotkey); + + return scriptInterface->callFunction(3); +} diff --git a/app/SabrehavenServer/src/spells.h b/app/SabrehavenServer/src/spells.h new file mode 100644 index 0000000..d0907fe --- /dev/null +++ b/app/SabrehavenServer/src/spells.h @@ -0,0 +1,322 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 +#define FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 + +#include "luascript.h" +#include "player.h" +#include "actions.h" +#include "talkaction.h" +#include "baseevents.h" + +class InstantSpell; +class ConjureSpell; +class RuneSpell; +class Spell; + +typedef std::map VocSpellMap; + +class Spells final : public BaseEvents +{ + public: + Spells(); + ~Spells(); + + // non-copyable + Spells(const Spells&) = delete; + Spells& operator=(const Spells&) = delete; + + Spell* getSpellByName(const std::string& name); + RuneSpell* getRuneSpell(uint32_t id); + RuneSpell* getRuneSpellByName(const std::string& name); + + InstantSpell* getInstantSpell(const std::string& words); + InstantSpell* getInstantSpellByName(const std::string& name); + + uint32_t getInstantSpellCount(const Player* player) const; + InstantSpell* getInstantSpellByIndex(const Player* player, uint32_t index); + + TalkActionResult_t playerSaySpell(Player* player, std::string& words); + + static Position getCasterPosition(Creature* creature, Direction dir); + std::string getScriptBaseName() const override; + + protected: + void clear() final; + LuaScriptInterface& getScriptInterface() final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + std::map runes; + std::map instants; + + friend class CombatSpell; + LuaScriptInterface scriptInterface { "Spell Interface" }; +}; + +typedef bool (InstantSpellFunction)(const InstantSpell* spell, Creature* creature, const std::string& param); +typedef bool (RuneSpellFunction)(const RuneSpell* spell, Player* player, const Position& posTo); + +class BaseSpell +{ + public: + constexpr BaseSpell() = default; + virtual ~BaseSpell() = default; + + virtual bool castSpell(Creature* creature) = 0; + virtual bool castSpell(Creature* creature, Creature* target) = 0; +}; + +class CombatSpell final : public Event, public BaseSpell +{ + public: + CombatSpell(Combat* combat, bool needTarget, bool needDirection); + ~CombatSpell(); + + // non-copyable + CombatSpell(const CombatSpell&) = delete; + CombatSpell& operator=(const CombatSpell&) = delete; + + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; + bool configureEvent(const pugi::xml_node&) override { + return true; + } + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool loadScriptCombat(); + Combat* getCombat() { + return combat; + } + + protected: + std::string getScriptEventName() const override { + return "onCastSpell"; + } + + Combat* combat; + + bool needDirection; + bool needTarget; +}; + +class Spell : public BaseSpell +{ + public: + Spell() = default; + + bool configureSpell(const pugi::xml_node& node); + const std::string& getName() const { + return name; + } + + void postCastSpell(Player* player, bool finishedSpell = true, bool payCost = true) const; + static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); + + uint32_t getManaCost(const Player* player) const; + uint32_t getSoulCost() const { + return soul; + } + uint32_t getLevel() const { + return level; + } + uint32_t getMagicLevel() const { + return magLevel; + } + uint32_t getManaPercent() const { + return manaPercent; + } + bool isPremium() const { + return premium; + } + + virtual bool isInstant() const = 0; + bool isLearnable() const { + return learnable; + } + + static ReturnValue CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, const std::string& name, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, uint32_t itemId, int32_t time); + + const VocSpellMap& getVocMap() const { + return vocSpellMap; + } + + protected: + bool playerSpellCheck(Player* player) const; + bool playerInstantSpellCheck(Player* player, const Position& toPos); + bool playerRuneSpellCheck(Player* player, const Position& toPos); + + uint8_t spellId = 0; + + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t soul = 0; + uint32_t cooldown = 0; + uint32_t level = 0; + uint32_t magLevel = 0; + int32_t range = -1; + + bool needTarget = false; + bool needWeapon = false; + bool selfTarget = false; + bool blockingSolid = false; + bool blockingCreature = false; + bool aggressive = true; + bool learnable = false; + bool enabled = true; + bool premium = false; + + VocSpellMap vocSpellMap; + + private: + std::string name; +}; + +class InstantSpell : public TalkAction, public Spell +{ + public: + explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr) override; + + virtual bool playerCastInstant(Player* player, std::string& param); + + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool isInstant() const override { + return true; + } + bool getHasParam() const { + return hasParam; + } + bool getHasPlayerNameParam() const { + return hasPlayerNameParam; + } + bool canCast(const Player* player) const; + bool canThrowSpell(const Creature* creature, const Creature* target) const; + + protected: + std::string getScriptEventName() const override; + + static InstantSpellFunction HouseGuestList; + static InstantSpellFunction HouseSubOwnerList; + static InstantSpellFunction HouseDoorList; + static InstantSpellFunction HouseKick; + static InstantSpellFunction SearchPlayer; + static InstantSpellFunction SummonMonster; + static InstantSpellFunction Levitate; + static InstantSpellFunction Illusion; + + static House* getHouseFromPos(Creature* creature); + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + InstantSpellFunction* function = nullptr; + + bool needDirection = false; + bool hasParam = false; + bool hasPlayerNameParam = false; + bool checkLineOfSight = true; + bool casterTargetOrDirection = false; +}; + +class ConjureSpell final : public InstantSpell +{ + public: + explicit ConjureSpell(LuaScriptInterface* interface) : InstantSpell(interface) { + aggressive = false; + } + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + bool playerCastInstant(Player* player, std::string& param) final; + + bool castSpell(Creature*) final { + return false; + } + bool castSpell(Creature*, Creature*) final { + return false; + } + + protected: + std::string getScriptEventName() const final; + + bool conjureItem(Creature* creature) const; + + uint32_t conjureId = 0; + uint32_t conjureCount = 1; + uint32_t reagentId = 0; +}; + +class RuneSpell final : public Action, public Spell +{ + public: + explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + ReturnValue canExecuteAction(const Player* player, const Position& toPos) final; + bool hasOwnErrorHandler() final { + return true; + } + Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const final { + return targetCreature; + } + + bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) override; + + bool castSpell(Creature* creature) final; + bool castSpell(Creature* creature, Creature* target) final; + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); + + bool isInstant() const final { + return false; + } + uint16_t getRuneItemId() const { + return runeId; + } + + protected: + std::string getScriptEventName() const final; + + static RuneSpellFunction Illusion; + static RuneSpellFunction Convince; + + bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); + + RuneSpellFunction* runeFunction = nullptr; + uint16_t runeId = 0; + bool hasCharges = true; +}; + +#endif diff --git a/app/SabrehavenServer/src/talkaction.cpp b/app/SabrehavenServer/src/talkaction.cpp new file mode 100644 index 0000000..8e2a0b7 --- /dev/null +++ b/app/SabrehavenServer/src/talkaction.cpp @@ -0,0 +1,154 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "player.h" +#include "talkaction.h" +#include "pugicast.h" + +TalkActions::TalkActions() + : scriptInterface("TalkAction Interface") +{ + scriptInterface.initState(); +} + +TalkActions::~TalkActions() +{ + clear(); +} + +void TalkActions::clear() +{ + talkActions.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& TalkActions::getScriptInterface() +{ + return scriptInterface; +} + +std::string TalkActions::getScriptBaseName() const +{ + return "talkactions"; +} + +Event* TalkActions::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "talkaction") != 0) { + return nullptr; + } + return new TalkAction(&scriptInterface); +} + +bool TalkActions::registerEvent(Event* event, const pugi::xml_node&) +{ + auto talkAction = std::unique_ptr(static_cast(event)); // event is guaranteed to be a TalkAction + talkActions.push_front(std::move(*talkAction)); + + return true; +} + +TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const +{ + size_t wordsLength = words.length(); + for (const TalkAction& talkAction : talkActions) { + const std::string& talkactionWords = talkAction.getWords(); + size_t talkactionLength = talkactionWords.length(); + if (wordsLength < talkactionLength || strncasecmp(words.c_str(), talkactionWords.c_str(), talkactionLength) != 0) { + continue; + } + + std::string param; + if (wordsLength != talkactionLength) { + param = words.substr(talkactionLength); + if (param.front() != ' ') { + continue; + } + trim_left(param, ' '); + + char separator = talkAction.getSeparator(); + if (separator != ' ') { + if (!param.empty()) { + if (param.front() != separator) { + continue; + } else { + param.erase(param.begin()); + } + } + } + } + + if (talkAction.executeSay(player, param, type)) { + return TALKACTION_CONTINUE; + } else { + return TALKACTION_BREAK; + } + } + return TALKACTION_CONTINUE; +} + +bool TalkAction::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute wordsAttribute = node.attribute("words"); + if (!wordsAttribute) { + std::cout << "[Error - TalkAction::configureEvent] Missing words for talk action or spell" << std::endl; + return false; + } + + pugi::xml_attribute separatorAttribute = node.attribute("separator"); + if (separatorAttribute) { + separator = pugi::cast(separatorAttribute.value()); + } + + words = wordsAttribute.as_string(); + return true; +} + +std::string TalkAction::getScriptEventName() const +{ + return "onSay"; +} + +bool TalkAction::executeSay(Player* player, const std::string& param, SpeakClasses type) const +{ + //onSay(player, words, param, type) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TalkAction::executeSay] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushString(L, words); + LuaScriptInterface::pushString(L, param); + lua_pushnumber(L, type); + + return scriptInterface->callFunction(4); +} diff --git a/app/SabrehavenServer/src/talkaction.h b/app/SabrehavenServer/src/talkaction.h new file mode 100644 index 0000000..765f336 --- /dev/null +++ b/app/SabrehavenServer/src/talkaction.h @@ -0,0 +1,84 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C +#define FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C + +#include "luascript.h" +#include "baseevents.h" +#include "const.h" + +enum TalkActionResult_t { + TALKACTION_CONTINUE, + TALKACTION_BREAK, + TALKACTION_FAILED, +}; + +class TalkAction; + +class TalkActions : public BaseEvents +{ + public: + TalkActions(); + ~TalkActions(); + + // non-copyable + TalkActions(const TalkActions&) = delete; + TalkActions& operator=(const TalkActions&) = delete; + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; + + protected: + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + void clear() final; + + std::forward_list talkActions; + + LuaScriptInterface scriptInterface; +}; + +class TalkAction : public Event +{ + public: + explicit TalkAction(LuaScriptInterface* interface) : Event(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + + const std::string& getWords() const { + return words; + } + char getSeparator() const { + return separator; + } + + //scripting + bool executeSay(Player* player, const std::string& param, SpeakClasses type) const; + // + + protected: + std::string getScriptEventName() const override; + + std::string words; + char separator = '"'; +}; + +#endif diff --git a/app/SabrehavenServer/src/tasks.cpp b/app/SabrehavenServer/src/tasks.cpp new file mode 100644 index 0000000..1ed727f --- /dev/null +++ b/app/SabrehavenServer/src/tasks.cpp @@ -0,0 +1,98 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "tasks.h" +#include "game.h" + +extern Game g_game; + +void Dispatcher::threadMain() +{ + // NOTE: second argument defer_lock is to prevent from immediate locking + std::unique_lock taskLockUnique(taskLock, std::defer_lock); + + while (getState() != THREAD_STATE_TERMINATED) { + // check if there are tasks waiting + taskLockUnique.lock(); + + if (taskList.empty()) { + //if the list is empty wait for signal + taskSignal.wait(taskLockUnique); + } + + if (!taskList.empty()) { + // take the first task + Task* task = taskList.front(); + taskList.pop_front(); + taskLockUnique.unlock(); + + if (!task->hasExpired()) { + ++dispatcherCycle; + // execute it + (*task)(); + + g_game.map.clearSpectatorCache(); + } + delete task; + } else { + taskLockUnique.unlock(); + } + } +} + +void Dispatcher::addTask(Task* task, bool push_front /*= false*/) +{ + bool do_signal = false; + + taskLock.lock(); + + if (getState() == THREAD_STATE_RUNNING) { + do_signal = taskList.empty(); + + if (push_front) { + taskList.push_front(task); + } else { + taskList.push_back(task); + } + } else { + delete task; + } + + taskLock.unlock(); + + // send a signal if the list was empty + if (do_signal) { + taskSignal.notify_one(); + } +} + +void Dispatcher::shutdown() +{ + Task* task = createTask([this]() { + setState(THREAD_STATE_TERMINATED); + taskSignal.notify_one(); + }); + + std::lock_guard lockClass(taskLock); + taskList.push_back(task); + + taskSignal.notify_one(); +} diff --git a/app/SabrehavenServer/src/tasks.h b/app/SabrehavenServer/src/tasks.h new file mode 100644 index 0000000..4ea459c --- /dev/null +++ b/app/SabrehavenServer/src/tasks.h @@ -0,0 +1,95 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 +#define FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 + +#include +#include "thread_holder_base.h" +#include "enums.h" + +const int DISPATCHER_TASK_EXPIRATION = 2000; +const auto SYSTEM_TIME_ZERO = std::chrono::system_clock::time_point(std::chrono::milliseconds(0)); + +class Task +{ + public: + // DO NOT allocate this class on the stack + explicit Task(std::function f) : func(std::move(f)) {} + Task(uint32_t ms, std::function f) : + expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) {} + + virtual ~Task() = default; + void operator()() { + func(); + } + + void setDontExpire() { + expiration = SYSTEM_TIME_ZERO; + } + + bool hasExpired() const { + if (expiration == SYSTEM_TIME_ZERO) { + return false; + } + return expiration < std::chrono::system_clock::now(); + } + + protected: + // Expiration has another meaning for scheduler tasks, + // then it is the time the task should be added to the + // dispatcher + std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; + std::function func; +}; + +inline Task* createTask(const std::function& f) +{ + return new Task(f); +} + +inline Task* createTask(uint32_t expiration, const std::function& f) +{ + return new Task(expiration, f); +} + +class Dispatcher : public ThreadHolder { + public: + void addTask(Task* task, bool push_front = false); + + void shutdown(); + + uint64_t getDispatcherCycle() const { + return dispatcherCycle; + } + + void threadMain(); + + protected: + std::thread thread; + std::mutex taskLock; + std::condition_variable taskSignal; + + std::list taskList; + uint64_t dispatcherCycle = 0; +}; + +extern Dispatcher g_dispatcher; + +#endif diff --git a/app/SabrehavenServer/src/teleport.cpp b/app/SabrehavenServer/src/teleport.cpp new file mode 100644 index 0000000..562fd0b --- /dev/null +++ b/app/SabrehavenServer/src/teleport.cpp @@ -0,0 +1,122 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "teleport.h" +#include "game.h" + +extern Game g_game; + +Attr_ReadValue Teleport::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_TELE_DEST) { + if (!propStream.read(destPos.x) || !propStream.read(destPos.y) || !propStream.read(destPos.z)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +void Teleport::serializeAttr(PropWriteStream& propWriteStream) const +{ + Item::serializeAttr(propWriteStream); + + propWriteStream.write(ATTR_TELE_DEST); + propWriteStream.write(destPos.x); + propWriteStream.write(destPos.y); + propWriteStream.write(destPos.z); +} + +ReturnValue Teleport::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Teleport::queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Teleport::queryRemove(const Thing&, uint32_t, uint32_t) const +{ + return RETURNVALUE_NOERROR; +} + +Cylinder* Teleport::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) +{ + return this; +} + +void Teleport::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Teleport::addThing(int32_t, Thing* thing) +{ + Tile* destTile = g_game.map.getTile(destPos); + if (!destTile) { + return; + } + + const MagicEffectClasses effect = Item::items[id].magicEffect; + + if (Creature* creature = thing->getCreature()) { + Position origPos = creature->getPosition(); + g_game.internalCreatureTurn(creature, origPos.x > destPos.x ? DIRECTION_WEST : DIRECTION_EAST); + g_game.map.moveCreature(*creature, *destTile); + if (effect != CONST_ME_NONE) { + g_game.addMagicEffect(origPos, effect); + g_game.addMagicEffect(destTile->getPosition(), effect); + } + } else if (Item* item = thing->getItem()) { + if (effect != CONST_ME_NONE) { + g_game.addMagicEffect(destTile->getPosition(), effect); + g_game.addMagicEffect(item->getPosition(), effect); + } + g_game.internalMoveItem(getTile(), destTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr); + } +} + +void Teleport::updateThing(Thing*, uint16_t, uint32_t) +{ + // +} + +void Teleport::replaceThing(uint32_t, Thing*) +{ + // +} + +void Teleport::removeThing(Thing*, uint32_t) +{ + // +} + +void Teleport::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Teleport::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); +} diff --git a/app/SabrehavenServer/src/teleport.h b/app/SabrehavenServer/src/teleport.h new file mode 100644 index 0000000..410b2ab --- /dev/null +++ b/app/SabrehavenServer/src/teleport.h @@ -0,0 +1,72 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC +#define FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC + +#include "tile.h" + +class Teleport final : public Item, public Cylinder +{ + public: + explicit Teleport(uint16_t type) : Item(type) {}; + + Teleport* getTeleport() final { + return this; + } + const Teleport* getTeleport() const final { + return this; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; + + const Position& getDestPos() const { + return destPos; + } + void setDestPos(Position pos) { + destPos = std::move(pos); + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + private: + Position destPos; +}; + +#endif diff --git a/app/SabrehavenServer/src/thing.cpp b/app/SabrehavenServer/src/thing.cpp new file mode 100644 index 0000000..21e72f6 --- /dev/null +++ b/app/SabrehavenServer/src/thing.cpp @@ -0,0 +1,42 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "thing.h" +#include "tile.h" + +const Position& Thing::getPosition() const +{ + const Tile* tile = getTile(); + if (!tile) { + return Tile::nullptr_tile.getPosition(); + } + return tile->getPosition(); +} + +Tile* Thing::getTile() +{ + return dynamic_cast(this); +} + +const Tile* Thing::getTile() const +{ + return dynamic_cast(this); +} diff --git a/app/SabrehavenServer/src/thing.h b/app/SabrehavenServer/src/thing.h new file mode 100644 index 0000000..b23be2b --- /dev/null +++ b/app/SabrehavenServer/src/thing.h @@ -0,0 +1,86 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 +#define FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 + +#include "position.h" + +class Tile; +class Cylinder; +class Item; +class Creature; +class Container; + +class Thing +{ + protected: + constexpr Thing() = default; + ~Thing() = default; + + public: + // non-copyable + Thing(const Thing&) = delete; + Thing& operator=(const Thing&) = delete; + + virtual std::string getDescription(int32_t lookDistance) const = 0; + + virtual Cylinder* getParent() const { + return nullptr; + } + virtual Cylinder* getRealParent() const { + return getParent(); + } + + virtual void setParent(Cylinder*) { + // + } + + virtual Tile* getTile(); + virtual const Tile* getTile() const; + + virtual const Position& getPosition() const; + virtual int32_t getThrowRange() const = 0; + virtual bool isPushable() const = 0; + + virtual Container* getContainer() { + return nullptr; + } + virtual const Container* getContainer() const { + return nullptr; + } + virtual Item* getItem() { + return nullptr; + } + virtual const Item* getItem() const { + return nullptr; + } + virtual Creature* getCreature() { + return nullptr; + } + virtual const Creature* getCreature() const { + return nullptr; + } + + virtual bool isRemoved() const { + return true; + } +}; + +#endif diff --git a/app/SabrehavenServer/src/thread_holder_base.h b/app/SabrehavenServer/src/thread_holder_base.h new file mode 100644 index 0000000..91280db --- /dev/null +++ b/app/SabrehavenServer/src/thread_holder_base.h @@ -0,0 +1,59 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 +#define FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 + +#include +#include +#include "enums.h" + +template +class ThreadHolder +{ + public: + ThreadHolder() {} + void start() { + setState(THREAD_STATE_RUNNING); + thread = std::thread(&Derived::threadMain, static_cast(this)); + } + + void stop() { + setState(THREAD_STATE_CLOSING); + } + + void join() { + if (thread.joinable()) { + thread.join(); + } + } + protected: + void setState(ThreadState newState) { + threadState.store(newState, std::memory_order_relaxed); + } + + ThreadState getState() const { + return threadState.load(std::memory_order_relaxed); + } + private: + std::atomic threadState{THREAD_STATE_TERMINATED}; + std::thread thread; +}; + +#endif diff --git a/app/SabrehavenServer/src/tile.cpp b/app/SabrehavenServer/src/tile.cpp new file mode 100644 index 0000000..93f7070 --- /dev/null +++ b/app/SabrehavenServer/src/tile.cpp @@ -0,0 +1,1567 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "tile.h" + +#include "creature.h" +#include "combat.h" +#include "game.h" +#include "mailbox.h" +#include "monster.h" +#include "movement.h" +#include "teleport.h" + +extern Game g_game; +extern MoveEvents* g_moveEvents; + +StaticTile real_nullptr_tile(0xFFFF, 0xFFFF, 0xFF); +Tile& Tile::nullptr_tile = real_nullptr_tile; + +bool Tile::hasProperty(ITEMPROPERTY prop) const +{ + if (ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item->hasProperty(prop)) { + return true; + } + } + } + return false; +} + +bool Tile::hasProperty(const Item* exclude, ITEMPROPERTY prop) const +{ + assert(exclude); + + if (ground && exclude != ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item != exclude && item->hasProperty(prop)) { + return true; + } + } + } + + return false; +} + +bool Tile::hasHeight(uint32_t n) const +{ + uint32_t height = 0; + + if (ground) { + if (ground->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + } + return false; +} + +int32_t Tile::getHeight() { + int32_t height = 0; + if (ground) { + if (ground->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + if ((*it)->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + } + } + + return std::min(height, 4); +} + +size_t Tile::getCreatureCount() const +{ + if (const CreatureVector* creatures = getCreatures()) { + return creatures->size(); + } + return 0; +} + +size_t Tile::getItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->size(); + } + return 0; +} + +uint32_t Tile::getTopItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopItemCount(); + } + return 0; +} + +uint32_t Tile::getDownItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getDownItemCount(); + } + return 0; +} + +std::string Tile::getDescription(int32_t) const +{ + return "You dont know why, but you cant see anything!"; +} + +Teleport* Tile::getTeleportItem() const +{ + if (!hasFlag(TILESTATE_TELEPORT)) { + return nullptr; + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getTeleport()) { + return (*it)->getTeleport(); + } + } + } + return nullptr; +} + +MagicField* Tile::getFieldItem() const +{ + if (!hasFlag(TILESTATE_MAGICFIELD)) { + return nullptr; + } + + if (ground && ground->getMagicField()) { + return ground->getMagicField(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getMagicField()) { + return (*it)->getMagicField(); + } + } + } + return nullptr; +} + +Mailbox* Tile::getMailbox() const +{ + if (!hasFlag(TILESTATE_MAILBOX)) { + return nullptr; + } + + if (ground && ground->getMailbox()) { + return ground->getMailbox(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getMailbox()) { + return (*it)->getMailbox(); + } + } + } + return nullptr; +} + +DepotLocker* Tile::getDepotLocker() const +{ + if (!hasFlag(TILESTATE_DEPOT)) { + return nullptr; + } + + if (ground && ground->getDepotLocker()) { + return ground->getDepotLocker(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getDepotLocker()) { + return (*it)->getDepotLocker(); + } + } + } + return nullptr; +} + + +BedItem* Tile::getBedItem() const +{ + if (!hasFlag(TILESTATE_BED)) { + return nullptr; + } + + if (ground && ground->getBed()) { + return ground->getBed(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getBed()) { + return (*it)->getBed(); + } + } + } + return nullptr; +} + +Creature* Tile::getTopCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->begin(); + } + } + return nullptr; +} + +Creature* Tile::getBottomCreatureUH() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->rbegin(); + } + } + return nullptr; +} + +const Creature* Tile::getBottomCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->rbegin(); + } + } + return nullptr; +} + +Creature* Tile::getTopVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + if (player && player->isAccessPlayer()) { + return getTopCreature(); + } + + for (Creature* tileCreature : *creatures) { + if (creature->canSeeCreature(tileCreature)) { + return tileCreature; + } + } + } else { + for (Creature* tileCreature : *creatures) { + if (!tileCreature->isInvisible()) { + const Player* player = tileCreature->getPlayer(); + if (!player || !player->isInGhostMode()) { + return tileCreature; + } + } + } + } + } + return nullptr; +} + +Creature* Tile::getBottomVisibleCreatureUH(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + if (player && player->isAccessPlayer()) { + return getBottomCreatureUH(); + } + + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } + else { + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + return nullptr; +} + +const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + if (player && player->isAccessPlayer()) { + return getBottomCreature(); + } + + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } else { + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + return nullptr; +} + +Item* Tile::getTopDownItem() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopDownItem(); + } + return nullptr; +} + +Item* Tile::getTopTopItem() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopTopItem(); + } + return nullptr; +} + +Item* Tile::getItemByTopOrder(int32_t topOrder) +{ + //topOrder: + //1: borders + //2: ladders, signs, splashes + //3: doors etc + //4: creatures + if (TileItemVector* items = getItemList()) { + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) { + return (*it); + } + } + } + return nullptr; +} + +Thing* Tile::getTopVisibleThing(const Creature* creature) +{ + Thing* thing = getTopVisibleCreature(creature); + if (thing) { + return thing; + } + + TileItemVector* items = getItemList(); + if (items) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + return (*it); + } + + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + return (*it); + } + } + + return ground; +} + +void Tile::onAddTileItem(Item* item) +{ + setTileFlags(item); + + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); + + //send to client + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onAddTileItem(this, cylinderMapPos); + } +} + +void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); + + //send to client + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); + } +} + +void Tile::onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item) +{ + resetTileFlags(item); + + const Position& cylinderMapPos = getPosition(); + const ItemType& iType = Item::items[item->getID()]; + + //send to client + size_t i = 0; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendRemoveTileThing(cylinderMapPos, oldStackPosVector[i++]); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onRemoveTileItem(this, cylinderMapPos, iType, item); + } +} + +void Tile::onUpdateTile(const SpectatorVec& list) +{ + const Position& cylinderMapPos = getPosition(); + + //send to clients + for (Creature* spectator : list) { + spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos); + } +} + +ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const +{ + if (const Creature* creature = thing.getCreature()) { + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RETURNVALUE_NOERROR; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (ground == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (const Monster* monster = creature->getMonster()) { + if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_TELEPORT)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKPATH | TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PLACECHECK, flags) && hasFlag(TILESTATE_BLOCKSOLID)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const CreatureVector* creatures = getCreatures(); + if (monster->canPushCreatures() && !monster->isSummon()) { + if (creatures) { + for (Creature* tileCreature : *creatures) { + if (tileCreature->getPlayer() && tileCreature->getPlayer()->isInGhostMode()) { + continue; + } + + const Monster* creatureMonster = tileCreature->getMonster(); + if (!creatureMonster || !tileCreature->isPushable() || + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + } + } else if (creatures && !creatures->empty()) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_BLOCKSOLID) || (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) { + if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + if (monster->hasCondition(CONDITION_AGGRESSIVE) && !monster->canPushItems()) { + if (hasFlag(TILESTATE_FIREDAMAGE) && !monster->isImmune(COMBAT_FIREDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_POISONDAMAGE) && !monster->isImmune(COMBAT_EARTHDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_ENERGYDAMAGE) && !monster->isImmune(COMBAT_ENERGYDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && + !hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { + if (hasFlag(TILESTATE_FIREDAMAGE) && !monster->isImmune(COMBAT_FIREDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_POISONDAMAGE) && !monster->isImmune(COMBAT_EARTHDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_ENERGYDAMAGE) && !monster->isImmune(COMBAT_ENERGYDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + return RETURNVALUE_NOERROR; + } + + const CreatureVector* creatures = getCreatures(); + if (const Player* player = creature->getPlayer()) { + if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_BLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) { + //player is trying to login to a "no logout" tile + return RETURNVALUE_NOTPOSSIBLE; + } + + const Tile* playerTile = player->getTile(); + if (playerTile && player->isPzLocked()) { + if (!playerTile->hasFlag(TILESTATE_PVPZONE)) { + //player is trying to enter a pvp zone while being pz-locked + if (hasFlag(TILESTATE_PVPZONE)) { + return RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE; + } + } else if (!hasFlag(TILESTATE_PVPZONE)) { + // player is trying to leave a pvp zone while being pz-locked + return RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE; + } + + if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) || + (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) { + // player is trying to enter a non-pvp/protection zone while being pz-locked + return RETURNVALUE_PLAYERISPZLOCKED; + } + } + } else if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) { + //If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item + if (hasFlag(TILESTATE_BLOCKSOLID)) { + return RETURNVALUE_NOTENOUGHROOM; + } + } else { + //FLAG_IGNOREBLOCKITEM is set + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + if (iiType.blockSolid) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + if (const auto items = getItemList()) { + for (const Item* item : *items) { + const ItemType& iiType = Item::items[item->getID()]; + if (iiType.blockSolid && !iiType.moveable) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + } + } + } else if (const Item* item = thing.getItem()) { + const TileItemVector* items = getItemList(); + if (items && items->size() >= 0xFFFF) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RETURNVALUE_NOERROR; + } + + bool itemIsHangable = item->isHangable(); + if (ground == nullptr && !itemIsHangable) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const CreatureVector* creatures = getCreatures(); + if (creatures && !creatures->empty() && item->isBlocking() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (item->isMagicField() && hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + if (items) { + for (const Item* tileItem : *items) { + if (tileItem->isHangable()) { + return RETURNVALUE_NEEDEXCHANGE; + } + } + } + } else { + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + if (iiType.blockSolid) { + if (!iiType.allowPickupable || item->isMagicField() || item->isBlocking()) { + if (!item->isPickupable()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + } + + if (items) { + for (const Item* tileItem : *items) { + const ItemType& iiType = Item::items[tileItem->getID()]; + if (!iiType.blockSolid) { + continue; + } + + if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) { + continue; + } + + if (!item->isPickupable()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Tile::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const +{ + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; +} + +ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + + return RETURNVALUE_NOERROR; +} + +Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t&) +{ + Thing* destThing = getTopDownItem(); + if (destThing) { + *destItem = destThing->getItem(); + } + + return this; +} + +void Tile::addThing(Thing* thing) +{ + addThing(0, thing); +} + +void Tile::addThing(int32_t, Thing* thing) +{ + Creature* creature = thing->getCreature(); + if (creature) { + g_game.map.clearSpectatorCache(); + creature->setParent(this); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->end(), creature); + } else { + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + TileItemVector* items = getItemList(); + if (items && items->size() >= 0xFFFF) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.isGroundTile()) { + if (ground == nullptr) { + ground = item; + onAddTileItem(item); + } else { + const ItemType& oldType = Item::items[ground->getID()]; + + Item* oldGround = ground; + ground->setParent(nullptr); + g_game.ReleaseItem(ground); + ground = item; + resetTileFlags(oldGround); + setTileFlags(item); + onUpdateTileItem(oldGround, oldType, item, itemType); + postRemoveNotification(oldGround, nullptr, 0); + } + } else if (itemType.alwaysOnTop) { + if (itemType.isSplash() && items) { + //remove old splash if exists + for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + Item* oldSplash = *it; + if (!Item::items[oldSplash->getID()].isSplash()) { + continue; + } + + removeThing(oldSplash, 1); + oldSplash->setParent(nullptr); + g_game.ReleaseItem(oldSplash); + postRemoveNotification(oldSplash, nullptr, 0); + break; + } + } + + bool isInserted = false; + + if (items) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + //Note: this is different from internalAddThing + if (itemType.alwaysOnTopOrder <= Item::items[(*it)->getID()].alwaysOnTopOrder) { + items->insert(it, item); + isInserted = true; + break; + } + } + } else { + items = makeItemList(); + } + + if (!isInserted) { + items->push_back(item); + } + + onAddTileItem(item); + } else { + if (itemType.isMagicField()) { + //remove old field item if exists + if (items) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + MagicField* oldField = (*it)->getMagicField(); + if (oldField) { + if (oldField->isReplaceable()) { + removeThing(oldField, 1); + + oldField->setParent(nullptr); + g_game.ReleaseItem(oldField); + postRemoveNotification(oldField, nullptr, 0); + break; + } else { + //This magic field cannot be replaced. + item->setParent(nullptr); + g_game.ReleaseItem(item); + return; + } + } + } + } + } + + items = makeItemList(); + items->insert(items->getBeginDownItem(), item); + items->addDownItemCount(1); + onAddTileItem(item); + } + } +} + +void Tile::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[item->getID()]; + const ItemType& newType = Item::items[itemId]; + resetTileFlags(item); + item->setID(itemId); + item->setSubType(count); + setTileFlags(item); + onUpdateTileItem(item, oldType, item, newType); +} + +void Tile::replaceThing(uint32_t index, Thing* thing) +{ + int32_t pos = index; + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* oldItem = nullptr; + bool isInserted = false; + + if (ground) { + if (pos == 0) { + oldItem = ground; + ground = item; + isInserted = true; + } + + --pos; + } + + TileItemVector* items = getItemList(); + if (items && !isInserted) { + int32_t topItemSize = getTopItemCount(); + if (pos < topItemSize) { + auto it = items->getBeginTopItem(); + it += pos; + + oldItem = (*it); + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + + pos -= topItemSize; + } + + CreatureVector* creatures = getCreatures(); + if (creatures) { + if (!isInserted && pos < static_cast(creatures->size())) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + pos -= static_cast(creatures->size()); + } + + if (items && !isInserted) { + int32_t downItemSize = getDownItemCount(); + if (pos < downItemSize) { + auto it = items->getBeginDownItem() + pos; + oldItem = *it; + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + } + + if (isInserted) { + item->setParent(this); + + resetTileFlags(oldItem); + setTileFlags(item); + const ItemType& oldType = Item::items[oldItem->getID()]; + const ItemType& newType = Item::items[item->getID()]; + onUpdateTileItem(oldItem, oldType, item, newType); + + oldItem->setParent(nullptr); + return /*RETURNVALUE_NOERROR*/; + } +} + +void Tile::removeThing(Thing* thing, uint32_t count) +{ + Creature* creature = thing->getCreature(); + if (creature) { + CreatureVector* creatures = getCreatures(); + if (creatures) { + auto it = std::find(creatures->begin(), creatures->end(), thing); + if (it != creatures->end()) { + g_game.map.clearSpectatorCache(); + creatures->erase(it); + } + } + return; + } + + Item* item = thing->getItem(); + if (!item) { + return; + } + + int32_t index = getThingIndex(item); + if (index == -1) { + return; + } + + if (item == ground) { + ground->setParent(nullptr); + ground = nullptr; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + onRemoveTileItem(list, std::vector(list.size(), 0), item); + return; + } + + TileItemVector* items = getItemList(); + if (!items) { + return; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.alwaysOnTop) { + auto it = std::find(items->getBeginTopItem(), items->getEndTopItem(), item); + if (it == items->getEndTopItem()) { + return; + } + + std::vector oldStackPosVector; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); + } + } + + item->setParent(nullptr); + items->erase(it); + onRemoveTileItem(list, oldStackPosVector, item); + } else { + auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item); + if (it == items->getEndDownItem()) { + return; + } + + if (itemType.stackable && count != item->getItemCount()) { + uint8_t newCount = static_cast(std::max(0, static_cast(item->getItemCount() - count))); + item->setItemCount(newCount); + onUpdateTileItem(item, itemType, item, itemType); + } else { + std::vector oldStackPosVector; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); + } + } + + item->setParent(nullptr); + items->erase(it); + items->addDownItemCount(-1); + onRemoveTileItem(list, oldStackPosVector, item); + } + } +} + +void Tile::removeCreature(Creature* creature) +{ + g_game.map.getQTNode(tilePos.x, tilePos.y)->removeCreature(creature); + removeThing(creature, 0); +} + +int32_t Tile::getThingIndex(const Thing* thing) const +{ + int32_t n = -1; + if (ground) { + if (ground == thing) { + return 0; + } + ++n; + } + + const TileItemVector* items = getItemList(); + if (items) { + const Item* item = thing->getItem(); + if (item && item->isAlwaysOnTop()) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + ++n; + if (*it == item) { + return n; + } + } + } else { + n += items->getTopItemCount(); + } + } + + if (const CreatureVector* creatures = getCreatures()) { + if (thing->getCreature()) { + for (Creature* creature : *creatures) { + ++n; + if (creature == thing) { + return n; + } + } + } else { + n += creatures->size(); + } + } + + if (items) { + const Item* item = thing->getItem(); + if (item && !item->isAlwaysOnTop()) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + ++n; + if (*it == item) { + return n; + } + } + } + } + return -1; +} + +int32_t Tile::getClientIndexOfCreature(const Player* player, const Creature* creature) const +{ + int32_t n; + if (ground) { + n = 1; + } else { + n = 0; + } + + const TileItemVector* items = getItemList(); + if (items) { + n += items->getTopItemCount(); + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* c : boost::adaptors::reverse(*creatures)) { + if (c == creature) { + return n; + } else if (player->canSeeCreature(c)) { + ++n; + } + } + } + return -1; +} + +int32_t Tile::getStackposOfCreature(const Player* player, const Creature* creature) const +{ + int32_t n; + if (ground) { + n = 1; + } else { + n = 0; + } + + const TileItemVector* items = getItemList(); + if (items) { + n += items->getTopItemCount(); + if (n >= 10) { + return -1; + } + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* c : boost::adaptors::reverse(*creatures)) { + if (c == creature) { + return n; + } else if (player->canSeeCreature(c)) { + if (++n >= 10) { + return -1; + } + } + } + } + return -1; +} + +int32_t Tile::getStackposOfItem(const Player* player, const Item* item) const +{ + int32_t n = 0; + if (ground) { + if (ground == item) { + return n; + } + ++n; + } + + const TileItemVector* items = getItemList(); + if (items) { + if (item->isAlwaysOnTop()) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + if (*it == item) { + return n; + } else if (++n == 10) { + return -1; + } + } + } else { + n += items->getTopItemCount(); + if (n >= 10) { + return -1; + } + } + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* creature : *creatures) { + if (player->canSeeCreature(creature)) { + if (++n >= 10) { + return -1; + } + } + } + } + + if (items && !item->isAlwaysOnTop()) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + if (*it == item) { + return n; + } else if (++n >= 10) { + return -1; + } + } + } + return -1; +} + +size_t Tile::getFirstIndex() const +{ + return 0; +} + +size_t Tile::getLastIndex() const +{ + return getThingCount(); +} + +uint32_t Tile::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + if (ground && ground->getID() == itemId) { + count += Item::countByType(ground, subType); + } + + const TileItemVector* items = getItemList(); + if (items) { + for (const Item* item : *items) { + if (item->getID() == itemId) { + count += Item::countByType(item, subType); + } + } + } + return count; +} + +Thing* Tile::getThing(size_t index) const +{ + if (ground) { + if (index == 0) { + return ground; + } + + --index; + } + + const TileItemVector* items = getItemList(); + if (items) { + uint32_t topItemSize = items->getTopItemCount(); + if (index < topItemSize) { + return items->at(items->getDownItemCount() + index); + } + index -= topItemSize; + } + + if (const CreatureVector* creatures = getCreatures()) { + if (index < creatures->size()) { + return (*creatures)[index]; + } + index -= creatures->size(); + } + + if (items && index < items->getDownItemCount()) { + return items->at(index); + } + return nullptr; +} + +void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + + //add a reference to this item, it may be deleted after being added (mailbox for example) + Creature* creature = thing->getCreature(); + Item* item; + if (creature) { + creature->incrementReferenceCounter(); + item = nullptr; + } else { + item = thing->getItem(); + if (item) { + item->incrementReferenceCounter(); + } + } + + if (link == LINK_OWNER) { + if (hasFlag(TILESTATE_TELEPORT)) { + Teleport* teleport = getTeleportItem(); + if (teleport) { + teleport->addThing(thing); + } + } else if (hasFlag(TILESTATE_MAILBOX)) { + Mailbox* mailbox = getMailbox(); + if (mailbox) { + mailbox->addThing(thing); + } + } + + //calling movement scripts + if (creature) { + g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_IN); + } else if (item) { + g_moveEvents->onItemMove(item, this, true); + } + } + + //release the reference to this item onces we are finished + if (creature) { + g_game.ReleaseCreature(creature); + } else if (item) { + g_game.ReleaseItem(item); + } +} + +void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); + + if (getThingCount() > 8) { + onUpdateTile(list); + } + + for (Creature* spectator : list) { + spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); + } + + //calling movement scripts + Creature* creature = thing->getCreature(); + if (creature) { + g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_OUT); + } else { + Item* item = thing->getItem(); + if (item) { + g_moveEvents->onItemMove(item, this, false); + } + } +} + +void Tile::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Tile::internalAddThing(uint32_t, Thing* thing) +{ + thing->setParent(this); + + Creature* creature = thing->getCreature(); + if (creature) { + g_game.map.clearSpectatorCache(); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->end(), creature); + } else { + Item* item = thing->getItem(); + if (item == nullptr) { + return; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.isGroundTile()) { + if (ground == nullptr) { + ground = item; + setTileFlags(item); + } + return; + } + + TileItemVector* items = makeItemList(); + if (items->size() >= 0xFFFF) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (itemType.alwaysOnTop) { + bool isInserted = false; + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder > itemType.alwaysOnTopOrder) { + items->insert(it, item); + isInserted = true; + break; + } + } + + if (!isInserted) { + items->push_back(item); + } + } else { + items->insert(items->getBeginDownItem(), item); + items->addDownItemCount(1); + } + + setTileFlags(item); + } +} + +void Tile::setTileFlags(const Item* item) +{ + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { + setFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_BLOCKPATH)) { + setFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH)) { + setFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + setFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + setFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + setFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + setFlag(TILESTATE_MAILBOX); + } + + if (item->hasProperty(CONST_PROP_BLOCKSOLID)) { + setFlag(TILESTATE_BLOCKSOLID); + } + + if (item->getBed()) { + setFlag(TILESTATE_BED); + } + + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + setFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + setFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + setFlag(TILESTATE_POISONDAMAGE); + } + + const Container* container = item->getContainer(); + if (container && container->getDepotLocker()) { + setFlag(TILESTATE_DEPOT); + } + + if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) { + setFlag(TILESTATE_SUPPORTS_HANGABLE); + } +} + +void Tile::resetTileFlags(const Item* item) +{ + if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) { + resetFlag(TILESTATE_BLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKSOLID)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_BLOCKPATH) && !hasProperty(item, CONST_PROP_BLOCKPATH)) { + resetFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_NOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + resetFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + resetFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + resetFlag(TILESTATE_MAILBOX); + } + + if (item->getBed()) { + resetFlag(TILESTATE_BED); + } + + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + resetFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + resetFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + resetFlag(TILESTATE_POISONDAMAGE); + } + + const Container* container = item->getContainer(); + if (container && container->getDepotLocker()) { + resetFlag(TILESTATE_DEPOT); + } + + if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) { + resetFlag(TILESTATE_SUPPORTS_HANGABLE); + } +} + +bool Tile::isMoveableBlocking() const +{ + return !ground || hasFlag(TILESTATE_BLOCKSOLID); +} + +Item* Tile::getUseItem(int32_t index) const +{ + const TileItemVector* items = getItemList(); + if (!items || items->size() == 0) { + return ground; + } + + if (Thing* thing = getThing(index)) { + return thing->getItem(); + } + + return nullptr; +} diff --git a/app/SabrehavenServer/src/tile.h b/app/SabrehavenServer/src/tile.h new file mode 100644 index 0000000..cf44bcd --- /dev/null +++ b/app/SabrehavenServer/src/tile.h @@ -0,0 +1,397 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 +#define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 + +#include + +#include "cylinder.h" +#include "item.h" +#include "tools.h" + +class Creature; +class Teleport; +class Mailbox; +class DepotLocker; +class MagicField; +class QTreeLeafNode; +class BedItem; + +typedef std::vector CreatureVector; +typedef std::vector ItemVector; +typedef std::unordered_set SpectatorVec; + +enum tileflags_t : uint32_t { + TILESTATE_NONE = 0, + + TILESTATE_PROTECTIONZONE = 1 << 0, + TILESTATE_NOPVPZONE = 1 << 1, + TILESTATE_NOLOGOUT = 1 << 2, + TILESTATE_PVPZONE = 1 << 3, + TILESTATE_REFRESH = 1 << 4, + TILESTATE_TELEPORT = 1 << 5, + TILESTATE_MAGICFIELD = 1 << 6, + TILESTATE_MAILBOX = 1 << 7, + TILESTATE_BED = 1 << 8, + TILESTATE_DEPOT = 1 << 9, + TILESTATE_BLOCKSOLID = 1 << 10, + TILESTATE_BLOCKPATH = 1 << 11, + TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 12, + TILESTATE_IMMOVABLEBLOCKPATH = 1 << 13, + TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 14, + TILESTATE_NOFIELDBLOCKPATH = 1 << 15, + TILESTATE_SUPPORTS_HANGABLE = 1 << 16, + TILESTATE_FIREDAMAGE = 1 << 17, + TILESTATE_POISONDAMAGE = 1 << 18, + TILESTATE_ENERGYDAMAGE = 1 << 19, +}; + +enum ZoneType_t { + ZONE_PROTECTION, + ZONE_NOPVP, + ZONE_PVP, + ZONE_NOLOGOUT, + ZONE_NORMAL, +}; + +class TileItemVector : private ItemVector +{ + public: + using ItemVector::begin; + using ItemVector::end; + using ItemVector::rbegin; + using ItemVector::rend; + using ItemVector::size; + using ItemVector::clear; + using ItemVector::at; + using ItemVector::insert; + using ItemVector::erase; + using ItemVector::push_back; + using ItemVector::value_type; + using ItemVector::iterator; + using ItemVector::const_iterator; + using ItemVector::reverse_iterator; + using ItemVector::const_reverse_iterator; + + iterator getBeginDownItem() { + return begin(); + } + const_iterator getBeginDownItem() const { + return begin(); + } + iterator getEndDownItem() { + return begin() + downItemCount; + } + const_iterator getEndDownItem() const { + return begin() + downItemCount; + } + iterator getBeginTopItem() { + return getEndDownItem(); + } + const_iterator getBeginTopItem() const { + return getEndDownItem(); + } + iterator getEndTopItem() { + return end(); + } + const_iterator getEndTopItem() const { + return end(); + } + + uint32_t getTopItemCount() const { + return size() - downItemCount; + } + uint32_t getDownItemCount() const { + return downItemCount; + } + inline Item* getTopTopItem() const { + if (getTopItemCount() == 0) { + return nullptr; + } + return *(getEndTopItem() - 1); + } + inline Item* getTopDownItem() const { + if (downItemCount == 0) { + return nullptr; + } + return *getBeginDownItem(); + } + void addDownItemCount(int32_t increment) { + downItemCount += increment; + } + + private: + uint16_t downItemCount = 0; +}; + +class Tile : public Cylinder +{ + public: + static Tile& nullptr_tile; + Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} + virtual ~Tile(); + + // non-copyable + Tile(const Tile&) = delete; + Tile& operator=(const Tile&) = delete; + + virtual TileItemVector* getItemList() = 0; + virtual const TileItemVector* getItemList() const = 0; + virtual TileItemVector* makeItemList() = 0; + + virtual CreatureVector* getCreatures() = 0; + virtual const CreatureVector* getCreatures() const = 0; + virtual CreatureVector* makeCreatures() = 0; + + int32_t getThrowRange() const final { + return 0; + } + bool isPushable() const final { + return false; + } + + MagicField* getFieldItem() const; + Teleport* getTeleportItem() const; + Mailbox* getMailbox() const; + DepotLocker* getDepotLocker() const; + BedItem* getBedItem() const; + + Creature* getTopCreature() const; + Creature* getBottomCreatureUH() const; + const Creature* getBottomCreature() const; + Creature* getTopVisibleCreature(const Creature* creature) const; + Creature* getBottomVisibleCreatureUH(const Creature* creature) const; + const Creature* getBottomVisibleCreature(const Creature* creature) const; + Item* getTopTopItem() const; + Item* getTopDownItem() const; + bool isMoveableBlocking() const; + Thing* getTopVisibleThing(const Creature* creature); + Item* getItemByTopOrder(int32_t topOrder); + + size_t getThingCount() const { + size_t thingCount = getCreatureCount() + getItemCount(); + if (ground) { + thingCount++; + } + return thingCount; + } + // If these return != 0 the associated vectors are guaranteed to exists + size_t getCreatureCount() const; + size_t getItemCount() const; + uint32_t getTopItemCount() const; + uint32_t getDownItemCount() const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; + + inline bool hasFlag(uint32_t flag) const { + return hasBitSet(flag, this->flags); + } + inline void setFlag(uint32_t flag) { + this->flags |= flag; + } + inline void resetFlag(uint32_t flag) { + this->flags &= ~flag; + } + + ZoneType_t getZone() const { + if (hasFlag(TILESTATE_PROTECTIONZONE)) { + return ZONE_PROTECTION; + } else if (hasFlag(TILESTATE_NOPVPZONE)) { + return ZONE_NOPVP; + } else if (hasFlag(TILESTATE_PVPZONE)) { + return ZONE_PVP; + } else { + return ZONE_NORMAL; + } + } + + bool hasHeight(uint32_t n) const; + int32_t getHeight(); + + std::string getDescription(int32_t lookDistance) const final; + + int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; + int32_t getStackposOfCreature(const Player* player, const Creature* creature) const; + int32_t getStackposOfItem(const Player* player, const Item* item) const; + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) override; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void removeCreature(Creature* creature); + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + Thing* getThing(size_t index) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) override; + + const Position& getPosition() const final { + return tilePos; + } + + bool isRemoved() const final { + return false; + } + + Item* getUseItem(int32_t index) const; + + Item* getGround() const { + return ground; + } + void setGround(Item* item) { + ground = item; + } + + private: + void onAddTileItem(Item* item); + void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); + void onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& list); + + void setTileFlags(const Item* item); + void resetTileFlags(const Item* item); + + protected: + Item* ground = nullptr; + Position tilePos; + uint32_t flags = 0; +}; + +// Used for walkable tiles, where there is high likeliness of +// items being added/removed +class DynamicTile : public Tile +{ + // By allocating the vectors in-house, we avoid some memory fragmentation + TileItemVector items; + CreatureVector creatures; + + public: + DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~DynamicTile(); + + // non-copyable + DynamicTile(const DynamicTile&) = delete; + DynamicTile& operator=(const DynamicTile&) = delete; + + TileItemVector* getItemList() final { + return &items; + } + const TileItemVector* getItemList() const final { + return &items; + } + TileItemVector* makeItemList() final { + return &items; + } + + CreatureVector* getCreatures() final { + return &creatures; + } + const CreatureVector* getCreatures() const final { + return &creatures; + } + CreatureVector* makeCreatures() final { + return &creatures; + } +}; + +// For blocking tiles, where we very rarely actually have items +class StaticTile final : public Tile +{ + // We very rarely even need the vectors, so don't keep them in memory + std::unique_ptr items; + std::unique_ptr creatures; + + public: + StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~StaticTile(); + + // non-copyable + StaticTile(const StaticTile&) = delete; + StaticTile& operator=(const StaticTile&) = delete; + + TileItemVector* getItemList() final { + return items.get(); + } + const TileItemVector* getItemList() const final { + return items.get(); + } + TileItemVector* makeItemList() final { + if (!items) { + items.reset(new TileItemVector); + } + return items.get(); + } + + CreatureVector* getCreatures() final { + return creatures.get(); + } + const CreatureVector* getCreatures() const final { + return creatures.get(); + } + CreatureVector* makeCreatures() final { + if (!creatures) { + creatures.reset(new CreatureVector); + } + return creatures.get(); + } +}; + +inline Tile::~Tile() +{ + delete ground; +} + +inline StaticTile::~StaticTile() +{ + if (items) { + for (Item* item : *items) { + item->decrementReferenceCounter(); + } + } +} + +inline DynamicTile::~DynamicTile() +{ + for (Item* item : items) { + item->decrementReferenceCounter(); + } +} + +#endif diff --git a/app/SabrehavenServer/src/tools.cpp b/app/SabrehavenServer/src/tools.cpp new file mode 100644 index 0000000..7846671 --- /dev/null +++ b/app/SabrehavenServer/src/tools.cpp @@ -0,0 +1,1267 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "tools.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result) +{ + std::cout << '[' << where << "] Failed to load " << fileName << ": " << result.description() << std::endl; + + FILE* file = fopen(fileName.c_str(), "rb"); + if (!file) { + return; + } + + char buffer[32768]; + uint32_t currentLine = 1; + std::string line; + + size_t offset = static_cast(result.offset); + size_t lineOffsetPosition = 0; + size_t index = 0; + size_t bytes; + do { + bytes = fread(buffer, 1, 32768, file); + for (size_t i = 0; i < bytes; ++i) { + char ch = buffer[i]; + if (ch == '\n') { + if ((index + i) >= offset) { + lineOffsetPosition = line.length() - ((index + i) - offset); + bytes = 0; + break; + } + ++currentLine; + line.clear(); + } else { + line.push_back(ch); + } + } + index += bytes; + } while (bytes == 32768); + fclose(file); + + std::cout << "Line " << currentLine << ':' << std::endl; + std::cout << line << std::endl; + for (size_t i = 0; i < lineOffsetPosition; i++) { + if (line[i] == '\t') { + std::cout << '\t'; + } else { + std::cout << ' '; + } + } + std::cout << '^' << std::endl; +} + +inline static uint32_t circularShift(int bits, uint32_t value) +{ + return (value << bits) | (value >> (32 - bits)); +} + +static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) +{ + uint32_t W[80]; + for (int i = 0; i < 16; ++i) { + const size_t offset = i << 2; + W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | messageBlock[offset + 3]; + } + + for (int i = 16; i < 80; ++i) { + W[i] = circularShift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]); + } + + uint32_t A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; + + for (int i = 0; i < 20; ++i) { + const uint32_t tmp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 20; i < 40; ++i) { + const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 40; i < 60; ++i) { + const uint32_t tmp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 60; i < 80; ++i) { + const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; +} + +std::string transformToSHA1(const std::string& input) +{ + uint32_t H[] = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0 + }; + + uint8_t messageBlock[64]; + size_t index = 0; + + uint32_t length_low = 0; + uint32_t length_high = 0; + for (char ch : input) { + messageBlock[index++] = ch; + + length_low += 8; + if (length_low == 0) { + length_high++; + } + + if (index == 64) { + processSHA1MessageBlock(messageBlock, H); + index = 0; + } + } + + messageBlock[index++] = 0x80; + + if (index > 56) { + while (index < 64) { + messageBlock[index++] = 0; + } + + processSHA1MessageBlock(messageBlock, H); + index = 0; + } + + while (index < 56) { + messageBlock[index++] = 0; + } + + messageBlock[56] = length_high >> 24; + messageBlock[57] = length_high >> 16; + messageBlock[58] = length_high >> 8; + messageBlock[59] = length_high; + + messageBlock[60] = length_low >> 24; + messageBlock[61] = length_low >> 16; + messageBlock[62] = length_low >> 8; + messageBlock[63] = length_low; + + processSHA1MessageBlock(messageBlock, H); + + char hexstring[41]; + static const char hexDigits[] = {"0123456789abcdef"}; + for (int hashByte = 20; --hashByte >= 0;) { + const uint8_t byte = H[hashByte >> 2] >> (((3 - hashByte) & 3) << 3); + index = hashByte << 1; + hexstring[index] = hexDigits[byte >> 4]; + hexstring[index + 1] = hexDigits[byte & 15]; + } + return std::string(hexstring, 40); +} + +uint8_t getLiquidColor(uint8_t type) +{ + uint8_t result = FLUID_COLOR_NONE; + switch (type) + { + case FLUID_WATER: + result = FLUID_COLOR_BLUE; + break; + case FLUID_NONE: + result = FLUID_COLOR_NONE; + break; + case FLUID_SLIME: + result = FLUID_COLOR_GREEN; + break; + case FLUID_BEER: + case FLUID_MUD: + case FLUID_OIL: + case FLUID_RUM: + result = FLUID_COLOR_BROWN; + break; + case FLUID_MILK: + case FLUID_COCONUTMILK: + result = FLUID_COLOR_WHITE; + break; + case FLUID_WINE: + case FLUID_MANAFLUID: + result = FLUID_COLOR_PURPLE; + break; + case FLUID_BLOOD: + case FLUID_LIFEFLUID: + result = FLUID_COLOR_RED; + break; + case FLUID_URINE: + case FLUID_LEMONADE: + case FLUID_FRUITJUICE: + result = FLUID_COLOR_YELLOW; + break; + default: + result = FLUID_COLOR_NONE; + break; + } + return result; +} + +void extractArticleAndName(std::string& data, std::string& article, std::string& name) +{ + std::string xarticle = data.substr(0, 3); + if (xarticle == "an ") + { + name = data.substr(3, data.size()); + article = "an"; + } else { + xarticle = data.substr(0, 2); + if (xarticle == "a ") + { + name = data.substr(2, data.size()); + article = "a"; + } else { + name = data; + article = ""; + } + } +} + +std::string pluralizeString(std::string str) +{ + if (str == "meat") return "meat"; + + int n = str.length(); + char ch = str[n - 1]; + char ch2 = str[n - 2]; + + std::string str2; + if (ch == 'y') + str2 = str.substr(0, n - 1) + "ies"; + else if (ch == 'o' || ch == 's' || ch == 'x') + str2 = str + "es"; + else if (ch == 'h'&& ch2 == 'c') + str2 = str + "es"; + else if (ch == 'f') + str2 = str.substr(0, n - 1) + "ves"; + else if (ch == 'e'&&ch2 == 'f') + str2 = str.substr(0, n - 2) + "ves"; + else + str2 = str + "s"; + + return str2; +} + +std::string generateToken(const std::string& key, uint32_t ticks) +{ + // generate message from ticks + std::string message(8, 0); + for (uint8_t i = 8; --i; ticks >>= 8) { + message[i] = static_cast(ticks & 0xFF); + } + + // hmac key pad generation + std::string iKeyPad(64, 0x36), oKeyPad(64, 0x5C); + for (uint8_t i = 0; i < key.length(); ++i) { + iKeyPad[i] ^= key[i]; + oKeyPad[i] ^= key[i]; + } + + oKeyPad.reserve(84); + + // hmac concat inner pad with message + iKeyPad.append(message); + + // hmac first pass + message.assign(transformToSHA1(iKeyPad)); + + // hmac concat outer pad with message, conversion from hex to int needed + for (uint8_t i = 0; i < message.length(); i += 2) { + oKeyPad.push_back(static_cast(std::strtoul(message.substr(i, 2).c_str(), nullptr, 16))); + } + + // hmac second pass + message.assign(transformToSHA1(oKeyPad)); + + // calculate hmac offset + uint32_t offset = static_cast(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF); + + // get truncated hash + uint32_t truncHash = static_cast(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF; + message.assign(std::to_string(truncHash)); + + // return only last AUTHENTICATOR_DIGITS (default 6) digits, also asserts exactly 6 digits + uint32_t hashLen = message.length(); + message.assign(message.substr(hashLen - std::min(hashLen, AUTHENTICATOR_DIGITS))); + message.insert(0, AUTHENTICATOR_DIGITS - std::min(hashLen, AUTHENTICATOR_DIGITS), '0'); + return message; +} + +void replaceString(std::string& str, const std::string& sought, const std::string& replacement) +{ + size_t pos = 0; + size_t start = 0; + size_t soughtLen = sought.length(); + size_t replaceLen = replacement.length(); + + while ((pos = str.find(sought, start)) != std::string::npos) { + str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); + start = pos + replaceLen; + } +} + +void trim_right(std::string& source, char t) +{ + source.erase(source.find_last_not_of(t) + 1); +} + +void trim_left(std::string& source, char t) +{ + source.erase(0, source.find_first_not_of(t)); +} + +void toLowerCaseString(std::string& source) +{ + std::transform(source.begin(), source.end(), source.begin(), tolower); +} + +std::string asLowerCaseString(std::string source) +{ + toLowerCaseString(source); + return source; +} + +std::string asUpperCaseString(std::string source) +{ + std::transform(source.begin(), source.end(), source.begin(), toupper); + return source; +} + +std::string asCamelCaseString(std::string source) { + bool active = true; + + for (int i = 0; source[i] != '\0'; i++) { + if (std::isalpha(source[i])) { + if (active) { + source[i] = std::toupper(source[i]); + active = false; + } + else { + source[i] = std::tolower(source[i]); + } + } + else if (source[i] == ' ') { + active = true; + } + } + + return source; +} + +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +{ + StringVec returnVector; + std::string::size_type start = 0, end = 0; + + while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { + returnVector.push_back(inString.substr(start, end - start)); + start = end + separator.size(); + } + + returnVector.push_back(inString.substr(start)); + return returnVector; +} + +IntegerVec vectorAtoi(const StringVec& stringVector) +{ + IntegerVec returnVector; + for (const auto& string : stringVector) { + returnVector.push_back(std::stoi(string)); + } + return returnVector; +} + +std::mt19937& getRandomGenerator() +{ + static std::random_device rd; + static std::mt19937 generator(rd()); + return generator; +} + +int32_t uniform_random(int32_t minNumber, int32_t maxNumber) +{ + static std::uniform_int_distribution uniformRand; + if (minNumber == maxNumber) { + return minNumber; + } else if (minNumber > maxNumber) { + std::swap(minNumber, maxNumber); + } + return uniformRand(getRandomGenerator(), std::uniform_int_distribution::param_type(minNumber, maxNumber)); +} + +int32_t normal_random(int32_t minNumber, int32_t maxNumber) +{ + static std::normal_distribution normalRand(0.5f, 0.25f); + if (minNumber == maxNumber) { + return minNumber; + } else if (minNumber > maxNumber) { + std::swap(minNumber, maxNumber); + } + + int32_t increment; + const int32_t diff = maxNumber - minNumber; + const float v = normalRand(getRandomGenerator()); + if (v < 0.0) { + increment = diff / 2; + } else if (v > 1.0) { + increment = (diff + 1) / 2; + } else { + increment = round(v * diff); + } + return minNumber + increment; +} + +bool boolean_random(double probability/* = 0.5*/) +{ + static std::bernoulli_distribution booleanRand; + return booleanRand(getRandomGenerator(), std::bernoulli_distribution::param_type(probability)); +} + +void trimString(std::string& str) +{ + str.erase(str.find_last_not_of(' ') + 1); + str.erase(0, str.find_first_not_of(' ')); +} + +std::string convertIPToString(uint32_t ip) +{ + char buffer[17]; + + int res = sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24)); + if (res < 0) { + return {}; + } + + return buffer; +} + +std::string formatDate(time_t time) +{ + const tm* tms = localtime(&time); + if (!tms) { + return {}; + } + + char buffer[20]; + int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); + if (res < 0) { + return {}; + } + return {buffer, 19}; +} + +std::string formatDateShort(time_t time) +{ + const tm* tms = localtime(&time); + if (!tms) { + return {}; + } + + char buffer[12]; + size_t res = strftime(buffer, 12, "%d %b %Y", tms); + if (res == 0) { + return {}; + } + return {buffer, 11}; +} + +Direction getDirection(const std::string& string) +{ + Direction direction = DIRECTION_NORTH; + + if (string == "north" || string == "n" || string == "0") { + direction = DIRECTION_NORTH; + } else if (string == "east" || string == "e" || string == "1") { + direction = DIRECTION_EAST; + } else if (string == "south" || string == "s" || string == "2") { + direction = DIRECTION_SOUTH; + } else if (string == "west" || string == "w" || string == "3") { + direction = DIRECTION_WEST; + } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") { + direction = DIRECTION_SOUTHWEST; + } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") { + direction = DIRECTION_SOUTHEAST; + } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") { + direction = DIRECTION_NORTHWEST; + } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") { + direction = DIRECTION_NORTHEAST; + } + + return direction; +} + +Position getNextPosition(Direction direction, Position pos) +{ + switch (direction) { + case DIRECTION_NORTH: + pos.y--; + break; + + case DIRECTION_SOUTH: + pos.y++; + break; + + case DIRECTION_WEST: + pos.x--; + break; + + case DIRECTION_EAST: + pos.x++; + break; + + case DIRECTION_SOUTHWEST: + pos.x--; + pos.y++; + break; + + case DIRECTION_NORTHWEST: + pos.x--; + pos.y--; + break; + + case DIRECTION_NORTHEAST: + pos.x++; + pos.y--; + break; + + case DIRECTION_SOUTHEAST: + pos.x++; + pos.y++; + break; + + default: + break; + } + + return pos; +} + +Direction getDirectionTo(const Position& from, const Position& to) +{ + Direction dir; + + int32_t x_offset = Position::getOffsetX(from, to); + if (x_offset < 0) { + dir = DIRECTION_EAST; + x_offset = std::abs(x_offset); + } else { + dir = DIRECTION_WEST; + } + + int32_t y_offset = Position::getOffsetY(from, to); + if (y_offset >= 0) { + if (y_offset > x_offset) { + dir = DIRECTION_NORTH; + } else if (y_offset == x_offset) { + if (dir == DIRECTION_EAST) { + dir = DIRECTION_NORTHEAST; + } else { + dir = DIRECTION_NORTHWEST; + } + } + } else { + y_offset = std::abs(y_offset); + if (y_offset > x_offset) { + dir = DIRECTION_SOUTH; + } else if (y_offset == x_offset) { + if (dir == DIRECTION_EAST) { + dir = DIRECTION_SOUTHEAST; + } else { + dir = DIRECTION_SOUTHWEST; + } + } + } + return dir; +} + +struct MagicEffectNames { + const char* name; + MagicEffectClasses effect; +}; + +struct ShootTypeNames { + const char* name; + ShootType_t shoot; +}; + +struct CombatTypeNames { + const char* name; + CombatType_t combat; +}; + +struct AmmoTypeNames { + const char* name; + Ammo_t ammoType; +}; + +struct WeaponActionNames { + const char* name; + WeaponAction_t weaponAction; +}; + +struct SkullNames { + const char* name; + Skulls_t skull; +}; + +struct FluidNames { + const char* name; + FluidTypes_t fluidType; +}; + +MagicEffectNames magicEffectNames[] = { + {"redspark", CONST_ME_DRAWBLOOD}, + {"bluebubble", CONST_ME_LOSEENERGY}, + {"poff", CONST_ME_POFF}, + {"yellowspark", CONST_ME_BLOCKHIT}, + {"explosionarea", CONST_ME_EXPLOSIONAREA}, + {"explosion", CONST_ME_EXPLOSIONHIT}, + {"firearea", CONST_ME_FIREAREA}, + {"yellowbubble", CONST_ME_YELLOW_RINGS}, + {"greenbubble", CONST_ME_GREEN_RINGS}, + {"blackspark", CONST_ME_HITAREA}, + {"teleport", CONST_ME_TELEPORT}, + {"energy", CONST_ME_ENERGYHIT}, + {"blueshimmer", CONST_ME_MAGIC_BLUE}, + {"redshimmer", CONST_ME_MAGIC_RED}, + {"greenshimmer", CONST_ME_MAGIC_GREEN}, + {"fire", CONST_ME_HITBYFIRE}, + {"greenspark", CONST_ME_HITBYPOISON}, + {"mortarea", CONST_ME_MORTAREA}, + {"greennote", CONST_ME_SOUND_GREEN}, + {"rednote", CONST_ME_SOUND_RED}, + {"poison", CONST_ME_POISONAREA}, + {"yellownote", CONST_ME_SOUND_YELLOW}, + {"purplenote", CONST_ME_SOUND_PURPLE}, + {"bluenote", CONST_ME_SOUND_BLUE}, + {"whitenote", CONST_ME_SOUND_WHITE}, + {"bubbles", CONST_ME_BUBBLES}, + {"dice", CONST_ME_CRAPS}, + {"giftwraps", CONST_ME_GIFT_WRAPS}, + {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, + {"redfirework", CONST_ME_FIREWORK_RED}, + {"bluefirework", CONST_ME_FIREWORK_BLUE}, +}; + +ShootTypeNames shootTypeNames[] = { + {"spear", CONST_ANI_SPEAR}, + {"bolt", CONST_ANI_BOLT}, + {"arrow", CONST_ANI_ARROW}, + {"fire", CONST_ANI_FIRE}, + {"energy", CONST_ANI_ENERGY}, + {"poisonarrow", CONST_ANI_POISONARROW}, + {"burstarrow", CONST_ANI_BURSTARROW}, + {"throwingstar", CONST_ANI_THROWINGSTAR}, + {"throwingknife", CONST_ANI_THROWINGKNIFE}, + {"smallstone", CONST_ANI_SMALLSTONE}, + {"death", CONST_ANI_DEATH}, + {"largerock", CONST_ANI_LARGEROCK}, + {"snowball", CONST_ANI_SNOWBALL}, + {"powerbolt", CONST_ANI_POWERBOLT}, + {"poison", CONST_ANI_POISON}, + {"infernalbolt", CONST_ANI_INFERNALBOLT}, +}; + +CombatTypeNames combatTypeNames[] = { + {"physical", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"drown", COMBAT_DROWNDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"poison", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"undefined", COMBAT_UNDEFINEDDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"healing", COMBAT_HEALING}, +}; + +AmmoTypeNames ammoTypeNames[] = { + {"spear", AMMO_SPEAR}, + {"bolt", AMMO_BOLT}, + {"arrow", AMMO_ARROW}, + {"poisonarrow", AMMO_ARROW}, + {"burstarrow", AMMO_ARROW}, + {"throwingstar", AMMO_THROWINGSTAR}, + {"throwingknife", AMMO_THROWINGKNIFE}, + {"smallstone", AMMO_STONE}, + {"largerock", AMMO_STONE}, + {"snowball", AMMO_SNOWBALL}, + {"powerbolt", AMMO_BOLT}, +}; + +WeaponActionNames weaponActionNames[] = { + {"move", WEAPONACTION_MOVE}, + {"removecharge", WEAPONACTION_REMOVECHARGE}, + {"removecount", WEAPONACTION_REMOVECOUNT}, +}; + +SkullNames skullNames[] = { + {"none", SKULL_NONE}, + {"yellow", SKULL_YELLOW}, + {"green", SKULL_GREEN}, + {"white", SKULL_WHITE}, + {"red", SKULL_RED}, +}; + +FluidNames fluidNames[] = { + {"none", FLUID_NONE}, + {"water", FLUID_WATER}, + {"wine", FLUID_WINE}, + {"beer", FLUID_BEER}, + {"mud", FLUID_MUD}, + {"blood", FLUID_BLOOD}, + {"slime", FLUID_SLIME}, + {"oil", FLUID_OIL}, + {"urine", FLUID_URINE}, + {"milk", FLUID_MILK}, + {"manafluid", FLUID_MANAFLUID}, + {"lifefluid", FLUID_LIFEFLUID}, + {"lemonade", FLUID_LEMONADE}, + {"rum", FLUID_RUM}, + {"coconutmilk", FLUID_COCONUTMILK}, + {"fruitjuice", FLUID_FRUITJUICE} +}; + +MagicEffectClasses getMagicEffect(const std::string& strValue) +{ + for (auto& magicEffectName : magicEffectNames) { + if (strcasecmp(strValue.c_str(), magicEffectName.name) == 0) { + return magicEffectName.effect; + } + } + return CONST_ME_NONE; +} + +ShootType_t getShootType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(shootTypeNames) / sizeof(ShootTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), shootTypeNames[i].name) == 0) { + return shootTypeNames[i].shoot; + } + } + return CONST_ANI_NONE; +} + +CombatType_t getCombatType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), combatTypeNames[i].name) == 0) { + return combatTypeNames[i].combat; + } + } + return COMBAT_NONE; +} + +std::string getCombatName(CombatType_t combatType) +{ + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (combatTypeNames[i].combat == combatType) { + return combatTypeNames[i].name; + } + } + return "unknown"; +} + +Ammo_t getAmmoType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(ammoTypeNames) / sizeof(AmmoTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), ammoTypeNames[i].name) == 0) { + return ammoTypeNames[i].ammoType; + } + } + return AMMO_NONE; +} + +WeaponAction_t getWeaponAction(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(weaponActionNames) / sizeof(WeaponActionNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), weaponActionNames[i].name) == 0) { + return weaponActionNames[i].weaponAction; + } + } + return WEAPONACTION_NONE; +} + +Skulls_t getSkullType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(skullNames) / sizeof(SkullNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), skullNames[i].name) == 0) { + return skullNames[i].skull; + } + } + return SKULL_NONE; +} + +FluidTypes_t getFluidType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(fluidNames) / sizeof(FluidNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), fluidNames[i].name) == 0) { + return fluidNames[i].fluidType; + } + } + return FLUID_NONE; +} + +std::string getSkillName(uint8_t skillid) +{ + switch (skillid) { + case SKILL_FIST: + return "fist fighting"; + + case SKILL_CLUB: + return "club fighting"; + + case SKILL_SWORD: + return "sword fighting"; + + case SKILL_AXE: + return "axe fighting"; + + case SKILL_DISTANCE: + return "distance fighting"; + + case SKILL_SHIELD: + return "shielding"; + + case SKILL_FISHING: + return "fishing"; + + case SKILL_MAGLEVEL: + return "magic level"; + + case SKILL_LEVEL: + return "level"; + + default: + return "unknown"; + } +} + + +std::string ucfirst(std::string str) +{ + for (char& i : str) { + if (i != ' ') { + i = toupper(i); + break; + } + } + return str; +} + +std::string ucwords(std::string str) +{ + size_t strLength = str.length(); + if (strLength == 0) { + return str; + } + + str[0] = toupper(str.front()); + for (size_t i = 1; i < strLength; ++i) { + if (str[i - 1] == ' ') { + str[i] = toupper(str[i]); + } + } + + return str; +} + +bool booleanString(const std::string& str) +{ + if (str.empty()) { + return false; + } + + char ch = tolower(str.front()); + return ch != 'f' && ch != 'n' && ch != '0'; +} + +std::string getWeaponName(WeaponType_t weaponType) +{ + switch (weaponType) { + case WEAPON_SWORD: return "sword"; + case WEAPON_CLUB: return "club"; + case WEAPON_AXE: return "axe"; + case WEAPON_DISTANCE: return "distance"; + case WEAPON_WAND: return "wand"; + case WEAPON_AMMO: return "ammunition"; + default: return std::string(); + } +} + +size_t combatTypeToIndex(CombatType_t combatType) +{ + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: + return 0; + case COMBAT_ENERGYDAMAGE: + return 1; + case COMBAT_EARTHDAMAGE: + return 2; + case COMBAT_FIREDAMAGE: + return 3; + case COMBAT_UNDEFINEDDAMAGE: + return 4; + case COMBAT_LIFEDRAIN: + return 5; + case COMBAT_MANADRAIN: + return 6; + case COMBAT_HEALING: + return 7; + case COMBAT_DROWNDAMAGE: + return 8; + default: + return 0; + } +} + +CombatType_t indexToCombatType(size_t v) +{ + return static_cast(1 << v); +} + +itemAttrTypes stringToItemAttribute(const std::string& str) +{ + if (str == "aid") { + return ITEM_ATTRIBUTE_ACTIONID; + } else if (str == "mid") { + return ITEM_ATTRIBUTE_MOVEMENTID; + } else if (str == "description") { + return ITEM_ATTRIBUTE_DESCRIPTION; + } else if (str == "text") { + return ITEM_ATTRIBUTE_TEXT; + } else if (str == "date") { + return ITEM_ATTRIBUTE_DATE; + } else if (str == "writer") { + return ITEM_ATTRIBUTE_WRITER; + } else if (str == "name") { + return ITEM_ATTRIBUTE_NAME; + } else if (str == "article") { + return ITEM_ATTRIBUTE_ARTICLE; + } else if (str == "pluralname") { + return ITEM_ATTRIBUTE_PLURALNAME; + } else if (str == "weight") { + return ITEM_ATTRIBUTE_WEIGHT; + } else if (str == "attack") { + return ITEM_ATTRIBUTE_ATTACK; + } else if (str == "defense") { + return ITEM_ATTRIBUTE_DEFENSE; + } else if (str == "armor") { + return ITEM_ATTRIBUTE_ARMOR; + } else if (str == "shootrange") { + return ITEM_ATTRIBUTE_SHOOTRANGE; + } else if (str == "owner") { + return ITEM_ATTRIBUTE_OWNER; + } else if (str == "duration") { + return ITEM_ATTRIBUTE_DURATION; + } else if (str == "decaystate") { + return ITEM_ATTRIBUTE_DECAYSTATE; + } else if (str == "corpseowner") { + return ITEM_ATTRIBUTE_CORPSEOWNER; + } else if (str == "charges") { + return ITEM_ATTRIBUTE_CHARGES; + } else if (str == "fluidtype") { + return ITEM_ATTRIBUTE_FLUIDTYPE; + } else if (str == "doorid") { + return ITEM_ATTRIBUTE_DOORID; + } + return ITEM_ATTRIBUTE_NONE; +} + +std::string getFirstLine(const std::string& str) +{ + std::string firstLine; + firstLine.reserve(str.length()); + for (const char c : str) { + if (c == '\n') { + break; + } + firstLine.push_back(c); + } + return firstLine; +} + +const char* getReturnMessage(ReturnValue value) +{ + switch (value) { + case RETURNVALUE_DESTINATIONOUTOFREACH: + return "Destination is out of reach."; + + case RETURNVALUE_NOTMOVEABLE: + return "You cannot move this object."; + + case RETURNVALUE_DROPTWOHANDEDITEM: + return "Drop the double-handed object first."; + + case RETURNVALUE_BOTHHANDSNEEDTOBEFREE: + return "Both hands need to be free."; + + case RETURNVALUE_CANNOTBEDRESSED: + return "You cannot dress this object there."; + + case RETURNVALUE_PUTTHISOBJECTINYOURHAND: + return "Put this object in your hand."; + + case RETURNVALUE_PUTTHISOBJECTINBOTHHANDS: + return "Put this object in both hands."; + + case RETURNVALUE_CANONLYUSEONEWEAPON: + return "You may only use one weapon."; + + case RETURNVALUE_TOOFARAWAY: + return "Too far away."; + + case RETURNVALUE_FIRSTGODOWNSTAIRS: + return "First go downstairs."; + + case RETURNVALUE_FIRSTGOUPSTAIRS: + return "First go upstairs."; + + case RETURNVALUE_NOTENOUGHCAPACITY: + return "This object is too heavy for you to carry."; + + case RETURNVALUE_CONTAINERNOTENOUGHROOM: + return "You cannot put more objects in this container."; + + case RETURNVALUE_NEEDEXCHANGE: + case RETURNVALUE_NOTENOUGHROOM: + return "There is not enough room."; + + case RETURNVALUE_CANNOTPICKUP: + return "You cannot take this object."; + + case RETURNVALUE_CANNOTTHROW: + return "You cannot throw there."; + + case RETURNVALUE_THEREISNOWAY: + return "There is no way."; + + case RETURNVALUE_THISISIMPOSSIBLE: + return "This is impossible."; + + case RETURNVALUE_PLAYERISPZLOCKED: + return "You can not enter a protection zone after attacking another player."; + + case RETURNVALUE_PLAYERISNOTINVITED: + return "You are not invited."; + + case RETURNVALUE_CREATUREDOESNOTEXIST: + return "Creature does not exist."; + + case RETURNVALUE_DEPOTISFULL: + return "You cannot put more items in this depot."; + + case RETURNVALUE_CANNOTUSETHISOBJECT: + return "You cannot use this object."; + + case RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE: + return "A player with this name is not online."; + + case RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE: + return "You do not have the required magic level to use this rune."; + + case RETURNVALUE_YOUAREALREADYTRADING: + return "You are already trading."; + + case RETURNVALUE_THISPLAYERISALREADYTRADING: + return "This player is already trading."; + + case RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT: + return "You may not logout during or immediately after a fight!"; + + case RETURNVALUE_DIRECTPLAYERSHOOT: + return "You are not allowed to shoot directly on players."; + + case RETURNVALUE_NOTENOUGHLEVEL: + return "You do not have enough level."; + + case RETURNVALUE_NOTENOUGHMAGICLEVEL: + return "You do not have enough magic level."; + + case RETURNVALUE_NOTENOUGHMANA: + return "You do not have enough mana."; + + case RETURNVALUE_NOTENOUGHSOUL: + return "You do not have enough soul."; + + case RETURNVALUE_YOUAREEXHAUSTED: + return "You are exhausted."; + + case RETURNVALUE_CANONLYUSETHISRUNEONCREATURES: + return "You can only use this rune on creatures."; + + case RETURNVALUE_PLAYERISNOTREACHABLE: + return "Player is not reachable."; + + case RETURNVALUE_CREATUREISNOTREACHABLE: + return "Creature is not reachable."; + + case RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE: + return "This action is not permitted in a protection zone."; + + case RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER: + return "You may not attack this player."; + + case RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE: + return "You may not attack this creature."; + + case RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE: + return "You may not attack a person in a protection zone."; + + case RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE: + return "You may not attack a person while you are in a protection zone."; + + case RETURNVALUE_YOUCANONLYUSEITONCREATURES: + return "You can only use it on creatures."; + + case RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS: + return "Turn secure mode off if you really want to attack unmarked players."; + + case RETURNVALUE_YOUNEEDPREMIUMACCOUNT: + return "You need a premium account."; + + case RETURNVALUE_YOUNEEDTOLEARNTHISSPELL: + return "You need to learn this spell first."; + + case RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL: + return "Your vocation cannot use this spell."; + + case RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL: + return "You need to equip a weapon to use this spell."; + + case RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE: + return "You can not leave a pvp zone after attacking another player."; + + case RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE: + return "You can not enter a pvp zone after attacking another player."; + + case RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE: + return "This action is not permitted in a non pvp zone."; + + case RETURNVALUE_YOUCANNOTLOGOUTHERE: + return "You can not logout here."; + + case RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL: + return "You need a magic item to cast this spell."; + + case RETURNVALUE_CANNOTCONJUREITEMHERE: + return "You cannot conjure items here."; + + case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS: + return "You need to split your spears first."; + + case RETURNVALUE_NAMEISTOOAMBIGIOUS: + return "Name is too ambigious."; + + case RETURNVALUE_CANONLYUSEONESHIELD: + return "You may use only one shield."; + + case RETURNVALUE_NOPARTYMEMBERSINRANGE: + return "No party members in range."; + + case RETURNVALUE_YOUARENOTTHEOWNER: + return "You are not the owner."; + + case RETURNVALUE_TRADEPLAYERFARAWAY: + return "Trade player is too far away."; + + case RETURNVALUE_YOUDONTOWNTHISHOUSE: + return "You don't own this house."; + + case RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE: + return "Trade player already owns a house."; + + case RETURNVALUE_TRADEPLAYERHIGHESTBIDDER: + return "Trade player is currently the highest bidder of an auctioned house."; + + case RETURNVALUE_YOUCANNOTTRADETHISHOUSE: + return "You can not trade this house."; + + default: // RETURNVALUE_NOTPOSSIBLE, etc + return "Sorry, not possible."; + } +} + +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret) +{ + if (!boost::filesystem::exists(root)) { + return; + } + + if (boost::filesystem::is_directory(root)) + { + boost::filesystem::recursive_directory_iterator it(root); + boost::filesystem::recursive_directory_iterator endit; + while (it != endit) + { + if (boost::filesystem::is_regular_file(*it) && it->path().extension() == ext) + { + ret.push_back(it->path().filename()); + } + ++it; + } + } +} + +std::string getClientVersionString(uint32_t version) +{ + return getClientVersionString(static_cast(version)); +} + +std::string getClientVersionString(ClientVersion_t version) +{ + std::string result; + switch (version) + { + case CLIENT_VERSION_780: + result = "7.80"; + break; + case CLIENT_VERSION_781: + result = "7.81"; + break; + case CLIENT_VERSION_790: + result = "7.90"; + break; + case CLIENT_VERSION_792: + result = "7.92"; + break; + default: + result = "Unknown"; + break; + } + + return result; +} diff --git a/app/SabrehavenServer/src/tools.h b/app/SabrehavenServer/src/tools.h new file mode 100644 index 0000000..f4c71f2 --- /dev/null +++ b/app/SabrehavenServer/src/tools.h @@ -0,0 +1,116 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 +#define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 + +#include +#include + +#include "position.h" +#include "const.h" +#include "enums.h" + +void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); + +std::string transformToSHA1(const std::string& input); +std::string generateToken(const std::string& key, uint32_t ticks); +uint8_t getLiquidColor(uint8_t type); + +void extractArticleAndName(std::string& data, std::string& article, std::string& name); +std::string pluralizeString(std::string str); +void replaceString(std::string& str, const std::string& sought, const std::string& replacement); +void trim_right(std::string& source, char t); +void trim_left(std::string& source, char t); +void toLowerCaseString(std::string& source); +std::string asLowerCaseString(std::string source); +std::string asUpperCaseString(std::string source); +std::string asCamelCaseString(std::string source); + +typedef std::vector StringVec; +typedef std::vector IntegerVec; + +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); +IntegerVec vectorAtoi(const StringVec& stringVector); +inline bool hasBitSet(uint32_t flag, uint32_t flags) { + return (flags & flag) != 0; +} + +inline bool IsDigit(char c) +{ + return ('0' <= c && c <= '9'); +} + +std::mt19937& getRandomGenerator(); +int32_t uniform_random(int32_t minNumber, int32_t maxNumber); +int32_t normal_random(int32_t minNumber, int32_t maxNumber); +bool boolean_random(double probability = 0.5); + +Direction getDirection(const std::string& string); +Position getNextPosition(Direction direction, Position pos); +Direction getDirectionTo(const Position& from, const Position& to); + +std::string getFirstLine(const std::string& str); + +std::string formatDate(time_t time); +std::string formatDateShort(time_t time); +std::string convertIPToString(uint32_t ip); + +void trimString(std::string& str); + +MagicEffectClasses getMagicEffect(const std::string& strValue); +ShootType_t getShootType(const std::string& strValue); +Ammo_t getAmmoType(const std::string& strValue); +WeaponAction_t getWeaponAction(const std::string& strValue); +CombatType_t getCombatType(const std::string& strValue); +Skulls_t getSkullType(const std::string& strValue); +FluidTypes_t getFluidType(const std::string& strValue); +std::string getCombatName(CombatType_t combatType); + +std::string getSkillName(uint8_t skillid); + +std::string ucfirst(std::string str); +std::string ucwords(std::string str); +bool booleanString(const std::string& str); + +std::string getWeaponName(WeaponType_t weaponType); + +size_t combatTypeToIndex(CombatType_t combatType); +CombatType_t indexToCombatType(size_t v); + +itemAttrTypes stringToItemAttribute(const std::string& str); + +const char* getReturnMessage(ReturnValue value); + +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret); + +std::string getClientVersionString(uint32_t version); +std::string getClientVersionString(ClientVersion_t version); + +inline int64_t OTSYS_TIME() +{ + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +inline int32_t OTSYS_TIME_MINUTES() +{ + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +#endif diff --git a/app/SabrehavenServer/src/town.h b/app/SabrehavenServer/src/town.h new file mode 100644 index 0000000..9e50720 --- /dev/null +++ b/app/SabrehavenServer/src/town.h @@ -0,0 +1,98 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 +#define FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 + +#include "position.h" + +class Town +{ + public: + explicit Town(uint32_t id) : id(id) {} + + const Position& getTemplePosition() const { + return templePosition; + } + const std::string& getName() const { + return name; + } + + void setTemplePos(Position pos) { + templePosition = pos; + } + void setName(std::string name) { + this->name = std::move(name); + } + uint32_t getID() const { + return id; + } + + private: + uint32_t id; + std::string name; + Position templePosition; +}; + +typedef std::map TownMap; + +class Towns +{ + public: + Towns() = default; + ~Towns() { + for (const auto& it : townMap) { + delete it.second; + } + } + + // non-copyable + Towns(const Towns&) = delete; + Towns& operator=(const Towns&) = delete; + + bool addTown(uint32_t townId, Town* town) { + return townMap.emplace(townId, town).second; + } + + Town* getTown(const std::string& townName) const { + for (const auto& it : townMap) { + if (strcasecmp(townName.c_str(), it.second->getName().c_str()) == 0) { + return it.second; + } + } + return nullptr; + } + + Town* getTown(uint32_t townId) const { + auto it = townMap.find(townId); + if (it == townMap.end()) { + return nullptr; + } + return it->second; + } + + const TownMap& getTowns() const { + return townMap; + } + + private: + TownMap townMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/vocation.cpp b/app/SabrehavenServer/src/vocation.cpp new file mode 100644 index 0000000..1732fc5 --- /dev/null +++ b/app/SabrehavenServer/src/vocation.cpp @@ -0,0 +1,200 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "vocation.h" + +#include "pugicast.h" +#include "tools.h" + +bool Vocations::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/vocations.xml"); + if (!result) { + printXMLError("Error - Vocations::loadFromXml", "data/XML/vocations.xml", result); + return false; + } + + for (auto vocationNode : doc.child("vocations").children()) { + pugi::xml_attribute attr; + if (!(attr = vocationNode.attribute("id"))) { + std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl; + continue; + } + + uint16_t id = pugi::cast(attr.value()); + + auto res = vocationsMap.emplace(std::piecewise_construct, + std::forward_as_tuple(id), std::forward_as_tuple(id)); + Vocation& voc = res.first->second; + + if (!(attr = vocationNode.attribute("flagid"))) { + std::cout << "[Warning - Vocations::loadFromXml] Missing vocation flag id" << std::endl; + continue; + } + + voc.flagid = pugi::cast(attr.value()); + + if ((attr = vocationNode.attribute("name"))) { + voc.name = attr.as_string(); + } + + if ((attr = vocationNode.attribute("description"))) { + voc.description = attr.as_string(); + } + + if ((attr = vocationNode.attribute("gaincap"))) { + voc.gainCap = pugi::cast(attr.value()) * 100; + } + + if ((attr = vocationNode.attribute("gainhp"))) { + voc.gainHP = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmana"))) { + voc.gainMana = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainhpticks"))) { + voc.gainHealthTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainhpamount"))) { + voc.gainHealthAmount = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmanaticks"))) { + voc.gainManaTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmanaamount"))) { + voc.gainManaAmount = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("manamultiplier"))) { + voc.manaMultiplier = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("attackspeed"))) { + voc.attackSpeed = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("basespeed"))) { + voc.baseSpeed = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("soulmax"))) { + voc.soulMax = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainsoulticks"))) { + voc.gainSoulTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("fromvoc"))) { + voc.fromVocation = pugi::cast(attr.value()); + } + + for (auto childNode : vocationNode.children()) { + if (strcasecmp(childNode.name(), "skill") == 0) { + pugi::xml_attribute skillIdAttribute = childNode.attribute("id"); + if (skillIdAttribute) { + uint16_t skill_id = pugi::cast(skillIdAttribute.value()); + if (skill_id <= SKILL_LAST) { + voc.skillMultipliers[skill_id] = pugi::cast(childNode.attribute("multiplier").value()); + } else { + std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skill_id << " for vocation: " << voc.id << std::endl; + } + } else { + std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; + } + } + } + } + return true; +} + +Vocation* Vocations::getVocation(uint16_t id) +{ + auto it = vocationsMap.find(id); + if (it == vocationsMap.end()) { + std::cout << "[Warning - Vocations::getVocation] Vocation " << id << " not found." << std::endl; + return nullptr; + } + return &it->second; +} + +int32_t Vocations::getVocationId(const std::string& name) const +{ + for (const auto& it : vocationsMap) { + if (strcasecmp(it.second.name.c_str(), name.c_str()) == 0) { + return it.first; + } + } + return -1; +} + +uint16_t Vocations::getPromotedVocation(uint16_t vocationId) const +{ + for (const auto& it : vocationsMap) { + if (it.second.fromVocation == vocationId && it.first != vocationId) { + return it.first; + } + } + return VOCATION_NONE; +} + +uint32_t Vocation::skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; + +uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) +{ + if (skill > SKILL_LAST) { + return 0; + } + + auto it = cacheSkill[skill].find(level); + if (it != cacheSkill[skill].end()) { + return it->second; + } + + uint64_t tries = static_cast(skillBase[skill] * std::pow(static_cast(skillMultipliers[skill]), level - 11)); + cacheSkill[skill][level] = tries; + return tries; +} + +uint64_t Vocation::getReqMana(uint32_t magLevel) +{ + auto it = cacheMana.find(magLevel); + if (it != cacheMana.end()) { + return it->second; + } + + uint64_t reqMana = static_cast(1600 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); + uint32_t modResult = reqMana % 20; + if (modResult < 10) { + reqMana -= modResult; + } else { + reqMana -= modResult + 20; + } + + cacheMana[magLevel] = reqMana; + return reqMana; +} diff --git a/app/SabrehavenServer/src/vocation.h b/app/SabrehavenServer/src/vocation.h new file mode 100644 index 0000000..b6bc48e --- /dev/null +++ b/app/SabrehavenServer/src/vocation.h @@ -0,0 +1,133 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D +#define FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D + +#include "enums.h" +#include "item.h" + +class Vocation +{ + public: + explicit Vocation(uint16_t id) : id(id) {} + + const std::string& getVocName() const { + return name; + } + const std::string& getVocDescription() const { + return description; + } + uint64_t getReqSkillTries(uint8_t skill, uint16_t level); + uint64_t getReqMana(uint32_t magLevel); + + uint16_t getId() const { + return id; + } + uint16_t getFlagId() const { + return flagid; + } + + uint32_t getHPGain() const { + return gainHP; + } + uint32_t getManaGain() const { + return gainMana; + } + uint32_t getCapGain() const { + return gainCap; + } + + uint32_t getManaGainTicks() const { + return gainManaTicks; + } + uint32_t getManaGainAmount() const { + return gainManaAmount; + } + uint32_t getHealthGainTicks() const { + return gainHealthTicks; + } + uint32_t getHealthGainAmount() const { + return gainHealthAmount; + } + + uint8_t getSoulMax() const { + return soulMax; + } + uint16_t getSoulGainTicks() const { + return gainSoulTicks; + } + + uint32_t getAttackSpeed() const { + return attackSpeed; + } + uint32_t getBaseSpeed() const { + return baseSpeed; + } + + uint32_t getFromVocation() const { + return fromVocation; + } + + protected: + friend class Vocations; + + std::map cacheMana; + std::map cacheSkill[SKILL_LAST + 1]; + + std::string name = "none"; + std::string description; + + float skillMultipliers[SKILL_LAST + 1] = {1.5f, 2.0f, 2.0f, 2.0f, 2.0f, 1.5f, 1.1f}; + float manaMultiplier = 4.0f; + + uint32_t gainHealthTicks = 6; + uint32_t gainHealthAmount = 1; + uint32_t gainManaTicks = 6; + uint32_t gainManaAmount = 1; + uint32_t gainCap = 500; + uint32_t gainMana = 5; + uint32_t gainHP = 5; + uint32_t fromVocation = VOCATION_NONE; + uint32_t attackSpeed = 1500; + uint32_t baseSpeed = 70; + uint16_t id; + uint16_t flagid; + + uint16_t gainSoulTicks = 120; + + uint8_t soulMax = 100; + + static uint32_t skillBase[SKILL_LAST + 1]; +}; + +class Vocations +{ + public: + bool loadFromXml(); + + Vocation* getVocation(uint16_t id); + int32_t getVocationId(const std::string& name) const; + uint16_t getPromotedVocation(uint16_t vocationId) const; + + private: + std::map vocationsMap; +}; + +#endif diff --git a/app/SabrehavenServer/src/waitlist.cpp b/app/SabrehavenServer/src/waitlist.cpp new file mode 100644 index 0000000..2743629 --- /dev/null +++ b/app/SabrehavenServer/src/waitlist.cpp @@ -0,0 +1,130 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "game.h" +#include "waitlist.h" + +extern ConfigManager g_config; +extern Game g_game; + +WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot) +{ + slot = 1; + for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; + } + ++slot; + } + + for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; + } + ++slot; + } + return waitList.end(); +} + +uint32_t WaitingList::getTime(uint32_t slot) +{ + if (slot < 5) { + return 5; + } else if (slot < 10) { + return 10; + } else if (slot < 20) { + return 20; + } else if (slot < 50) { + return 60; + } else { + return 120; + } +} + +uint32_t WaitingList::getTimeout(uint32_t slot) +{ + //timeout is set to 15 seconds longer than expected retry attempt + return getTime(slot) + 15; +} + +bool WaitingList::clientLogin(const Player* player) +{ + if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + return true; + } + + uint32_t maxPlayers = static_cast(g_config.getNumber(ConfigManager::MAX_PLAYERS)); + if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { + return true; + } + + WaitingList::cleanupList(priorityWaitList); + WaitingList::cleanupList(waitList); + + uint32_t slot; + + auto it = findClient(player, slot); + if (it != waitList.end()) { + if ((g_game.getPlayersOnline() + slot) <= maxPlayers) { + //should be able to login now + waitList.erase(it); + return true; + } + + //let them wait a bit longer + it->timeout = OTSYS_TIME() + (getTimeout(slot) * 1000); + return false; + } + + slot = priorityWaitList.size(); + if (player->isPremium()) { + priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + } else { + slot += waitList.size(); + waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + } + return false; +} + +uint32_t WaitingList::getClientSlot(const Player* player) +{ + uint32_t slot; + auto it = findClient(player, slot); + if (it == waitList.end()) { + return 0; + } + return slot; +} + +void WaitingList::cleanupList(WaitList& list) +{ + int64_t time = OTSYS_TIME(); + + auto it = list.begin(), end = list.end(); + while (it != end) { + if ((it->timeout - time) <= 0) { + it = list.erase(it); + } else { + ++it; + } + } +} diff --git a/app/SabrehavenServer/src/waitlist.h b/app/SabrehavenServer/src/waitlist.h new file mode 100644 index 0000000..77dd2c5 --- /dev/null +++ b/app/SabrehavenServer/src/waitlist.h @@ -0,0 +1,57 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 +#define FS_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 + +#include "player.h" + +struct Wait { + constexpr Wait(int64_t timeout, uint32_t playerGUID) : + timeout(timeout), playerGUID(playerGUID) {} + + int64_t timeout; + uint32_t playerGUID; +}; + +typedef std::list WaitList; +typedef WaitList::iterator WaitListIterator; + +class WaitingList +{ + public: + static WaitingList* getInstance() { + static WaitingList waitingList; + return &waitingList; + } + + bool clientLogin(const Player* player); + uint32_t getClientSlot(const Player* player); + static uint32_t getTime(uint32_t slot); + + protected: + WaitList priorityWaitList; + WaitList waitList; + + static uint32_t getTimeout(uint32_t slot); + WaitListIterator findClient(const Player* player, uint32_t& slot); + static void cleanupList(WaitList& list); +}; + +#endif diff --git a/app/SabrehavenServer/src/wildcardtree.cpp b/app/SabrehavenServer/src/wildcardtree.cpp new file mode 100644 index 0000000..fa5eef8 --- /dev/null +++ b/app/SabrehavenServer/src/wildcardtree.cpp @@ -0,0 +1,129 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "wildcardtree.h" + +WildcardTreeNode* WildcardTreeNode::getChild(char ch) +{ + auto it = children.find(ch); + if (it == children.end()) { + return nullptr; + } + return &it->second; +} + +const WildcardTreeNode* WildcardTreeNode::getChild(char ch) const +{ + auto it = children.find(ch); + if (it == children.end()) { + return nullptr; + } + return &it->second; +} + +WildcardTreeNode* WildcardTreeNode::addChild(char ch, bool breakpoint) +{ + WildcardTreeNode* child = getChild(ch); + if (child) { + if (breakpoint && !child->breakpoint) { + child->breakpoint = true; + } + } else { + auto pair = children.emplace(std::piecewise_construct, + std::forward_as_tuple(ch), std::forward_as_tuple(breakpoint)); + child = &pair.first->second; + } + return child; +} + +void WildcardTreeNode::insert(const std::string& str) +{ + WildcardTreeNode* cur = this; + + size_t length = str.length() - 1; + for (size_t pos = 0; pos < length; ++pos) { + cur = cur->addChild(str[pos], false); + } + + cur->addChild(str[length], true); +} + +void WildcardTreeNode::remove(const std::string& str) +{ + WildcardTreeNode* cur = this; + + std::stack path; + path.push(cur); + size_t len = str.length(); + for (size_t pos = 0; pos < len; ++pos) { + cur = cur->getChild(str[pos]); + if (!cur) { + return; + } + path.push(cur); + } + + cur->breakpoint = false; + + do { + cur = path.top(); + path.pop(); + + if (!cur->children.empty() || cur->breakpoint || path.empty()) { + break; + } + + cur = path.top(); + + auto it = cur->children.find(str[--len]); + if (it != cur->children.end()) { + cur->children.erase(it); + } + } while (true); +} + +ReturnValue WildcardTreeNode::findOne(const std::string& query, std::string& result) const +{ + const WildcardTreeNode* cur = this; + for (char pos : query) { + cur = cur->getChild(pos); + if (!cur) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + } + + result = query; + + do { + size_t size = cur->children.size(); + if (size == 0) { + return RETURNVALUE_NOERROR; + } else if (size > 1 || cur->breakpoint) { + return RETURNVALUE_NAMEISTOOAMBIGIOUS; + } + + auto it = cur->children.begin(); + result += it->first; + cur = &it->second; + } while (true); +} diff --git a/app/SabrehavenServer/src/wildcardtree.h b/app/SabrehavenServer/src/wildcardtree.h new file mode 100644 index 0000000..2d16981 --- /dev/null +++ b/app/SabrehavenServer/src/wildcardtree.h @@ -0,0 +1,49 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB +#define FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB + +#include "enums.h" + +class WildcardTreeNode +{ + public: + explicit WildcardTreeNode(bool breakpoint) : breakpoint(breakpoint) {} + WildcardTreeNode(WildcardTreeNode&& other) = default; + + // non-copyable + WildcardTreeNode(const WildcardTreeNode&) = delete; + WildcardTreeNode& operator=(const WildcardTreeNode&) = delete; + + WildcardTreeNode* getChild(char ch); + const WildcardTreeNode* getChild(char ch) const; + WildcardTreeNode* addChild(char ch, bool breakpoint); + + void insert(const std::string& str); + void remove(const std::string& str); + + ReturnValue findOne(const std::string& query, std::string& result) const; + + private: + std::map children; + bool breakpoint; +}; + +#endif diff --git a/app/SabrehavenServer/src/xtea.cpp b/app/SabrehavenServer/src/xtea.cpp new file mode 100644 index 0000000..1144ba6 --- /dev/null +++ b/app/SabrehavenServer/src/xtea.cpp @@ -0,0 +1,140 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "xtea.h" + +#include +#include + +namespace xtea { + +namespace { + +constexpr uint32_t delta = 0x9E3779B9; + +template +void XTEA_encrypt(uint8_t data[BLOCK_SIZE * 8], const key& k) +{ + alignas(16) uint32_t left[BLOCK_SIZE], right[BLOCK_SIZE]; + for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) { + left[i] = data[j] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] << 24u; + right[i] = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] << 24u; + } + + uint32_t sum = 0u; + for (auto i = 0u; i < 32; ++i) { + for (auto j = 0u; j < BLOCK_SIZE; ++j) { + left[j] += (((right[j] << 4) ^ (right[j] >> 5)) + right[j]) ^ (sum + k[sum & 3]); + } + sum += delta; + for (auto j = 0u; j < BLOCK_SIZE; ++j) { + right[j] += (((left[j] << 4) ^ (left[j] >> 5)) + left[j]) ^ (sum + k[(sum >> 11) & 3]); + } + } + + for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) { + data[j] = static_cast(left[i]); + data[j+1] = static_cast(left[i] >> 8u); + data[j+2] = static_cast(left[i] >> 16u); + data[j+3] = static_cast(left[i] >> 24u); + data[j+4] = static_cast(right[i]); + data[j+5] = static_cast(right[i] >> 8u); + data[j+6] = static_cast(right[i] >> 16u); + data[j+7] = static_cast(right[i] >> 24u); + } +} + +template +void XTEA_decrypt(uint8_t data[BLOCK_SIZE * 8], const key& k) +{ + alignas(16) uint32_t left[BLOCK_SIZE], right[BLOCK_SIZE]; + for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) { + left[i] = data[j] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] << 24u; + right[i] = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] << 24u; + } + + uint32_t sum = delta << 5; + for (auto i = 0u; i < 32; ++i) { + for (auto j = 0u; j < BLOCK_SIZE; ++j) { + right[j] -= (((left[j] << 4) ^ (left[j] >> 5)) + left[j]) ^ (sum + k[(sum >> 11) & 3]); + } + sum -= delta; + for (auto j = 0u; j < BLOCK_SIZE; ++j) { + left[j] -= (((right[j] << 4) ^ (right[j] >> 5)) + right[j]) ^ (sum + k[(sum) & 3]); + } + } + + for (auto i = 0u, j = 0u; i < BLOCK_SIZE; i += 1u, j += 8u) { + data[j] = static_cast(left[i]); + data[j+1] = static_cast(left[i] >> 8u); + data[j+2] = static_cast(left[i] >> 16u); + data[j+3] = static_cast(left[i] >> 24u); + data[j+4] = static_cast(right[i]); + data[j+5] = static_cast(right[i] >> 8u); + data[j+6] = static_cast(right[i] >> 16u); + data[j+7] = static_cast(right[i] >> 24u); + } +} + +constexpr auto InitialBlockSize = +#if defined(__AVX512F__) + 128u; +#elif defined(__AVX__) + 32u; +#elif defined(__SSE__) || defined(__ARM_FEATURE_SIMD32) + 8u; +#elif defined(__x86_64__) + 2u; +#else + 1u; +#endif + +template +struct XTEA { + static constexpr auto step = BlockSize * 8u; + + void operator()(uint8_t* input, size_t length, const key& k) const { + const auto blocks = (length & ~(step - 1)); + for (auto i = 0u; i < blocks; i += step) { + if (Encrypt) { + XTEA_encrypt(input + i, k); + } else { + XTEA_decrypt(input + i, k); + } + } + input += blocks; + length -= blocks; + + if (BlockSize != 1) { + XTEA()(input, length, k); + } + } +}; + +constexpr auto encrypt_v = XTEA(); +constexpr auto decrypt_v = XTEA(); + +} // anonymous namespace + +void encrypt(uint8_t* data, size_t length, const key& k) { encrypt_v(data, length, k); } +void decrypt(uint8_t* data, size_t length, const key& k) { decrypt_v(data, length, k); } + +} // namespace xtea diff --git a/app/SabrehavenServer/src/xtea.h b/app/SabrehavenServer/src/xtea.h new file mode 100644 index 0000000..6f8f555 --- /dev/null +++ b/app/SabrehavenServer/src/xtea.h @@ -0,0 +1,32 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef TFS_XTEA_H +#define TFS_XTEA_H + +namespace xtea { + +using key = std::array; + +void encrypt(uint8_t* data, size_t length, const key& k); +void decrypt(uint8_t* data, size_t length, const key& k); + +} // namespace xtea + +#endif // TFS_XTEA_H diff --git a/app/SabrehavenServer/vc14/arch32.props b/app/SabrehavenServer/vc14/arch32.props new file mode 100644 index 0000000..d2adc41 --- /dev/null +++ b/app/SabrehavenServer/vc14/arch32.props @@ -0,0 +1,13 @@ + + + + + + + + $(TFS_LIBS) + true + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/vc14/arch64.props b/app/SabrehavenServer/vc14/arch64.props new file mode 100644 index 0000000..6c086a5 --- /dev/null +++ b/app/SabrehavenServer/vc14/arch64.props @@ -0,0 +1,12 @@ + + + + + + + + $(TFS_LIBS64) + + + + \ No newline at end of file diff --git a/app/SabrehavenServer/vc14/debug.props b/app/SabrehavenServer/vc14/debug.props new file mode 100644 index 0000000..ab4fb7d --- /dev/null +++ b/app/SabrehavenServer/vc14/debug.props @@ -0,0 +1,22 @@ + + + + + + true + + + + false + false + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)\obj_d\ + + + $(TFS_LIBDEPS_D) + + + + + diff --git a/app/SabrehavenServer/vc14/release.props b/app/SabrehavenServer/vc14/release.props new file mode 100644 index 0000000..1a6520d --- /dev/null +++ b/app/SabrehavenServer/vc14/release.props @@ -0,0 +1,19 @@ + + + + + + false + + + + Full + $(IntDir)\obj_r\ + + + UseLinkTimeCodeGeneration + + + + + diff --git a/app/SabrehavenServer/vc14/settings.props b/app/SabrehavenServer/vc14/settings.props new file mode 100644 index 0000000..b7fc74a --- /dev/null +++ b/app/SabrehavenServer/vc14/settings.props @@ -0,0 +1,74 @@ + + + + + $(TFSSDKDir)\LuaJIT\ + $(TFSSDKDir)\mpir\ + $(TFSSDKDir)\mysql-connector-c\ + $(TFSSDKDir)\pugixml\ + _CRT_SECURE_NO_WARNINGS; + $(BOOST_ROOT);$(LUA_DIR)\include;$(GMP_DIR)\include;$(MYSQLC_DIR)\include;$(PUGIXML_DIR)\include; + $(BOOST_ROOT)\lib32-msvc-14.0;$(LUA_DIR)\lib;$(GMP_DIR)\lib;$(MYSQLC_DIR)\lib + $(BOOST_ROOT)\lib64-msvc-14.0;$(LUA_DIR)\lib64;$(GMP_DIR)\lib64;$(MYSQLC_DIR)\lib64 + lua51.lib;mpir.lib;libmysql.lib + lua51.lib;mpir.lib;libmysql.lib + + + false + + + + $(TFS_INCLUDES) + Level3 + true + true + Use + otpch.h + MultiThreadedDLL + + + $(TFS_LIBDEPS) + Default + + + $(PREPROCESSOR_DEFS) + + + + + $(LUA_DIR) + true + + + $(GMP_DIR) + true + + + $(MYSQLC_DIR) + true + + + $(PREPROCESSOR_DEFS) + true + + + $(TFS_INCLUDES) + true + + + $(TFS_LIBS) + true + + + $(TFS_LIBS64) + true + + + $(TFS_LIBDEPS) + true + + + $(TFS_LIBDEPS_D) + + + \ No newline at end of file diff --git a/app/SabrehavenServer/vc14/theforgottenserver.sln b/app/SabrehavenServer/vc14/theforgottenserver.sln new file mode 100644 index 0000000..4c37956 --- /dev/null +++ b/app/SabrehavenServer/vc14/theforgottenserver.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "theforgottenserver", "theforgottenserver.vcxproj", "{A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.ActiveCfg = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.Build.0 = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.ActiveCfg = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.Build.0 = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.ActiveCfg = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.Build.0 = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.ActiveCfg = Release|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/app/SabrehavenServer/vc14/theforgottenserver.vcxproj b/app/SabrehavenServer/vc14/theforgottenserver.vcxproj new file mode 100644 index 0000000..b1c4e61 --- /dev/null +++ b/app/SabrehavenServer/vc14/theforgottenserver.vcxproj @@ -0,0 +1,304 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Win32Proj + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E} + 10.0.17763.0 + + + + Application + true + v141 + + + Application + true + v141 + + + Application + false + v141 + + + Application + false + v141 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. + + + + _CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + Disabled + + + + MachineX86 + true + Console + + + + + + + _CONSOLE;__EXCEPTION_TRACER__;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + + + + + true + Console + + + + + + + + NDEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + + + + MachineX86 + true + Console + true + true + + + + + + + NDEBUG;_CONSOLE;__EXCEPTION_TRACER__;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + Level4 + + + + + true + Console + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + otpch.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/ZnoteAAC.tar.gz b/app/ZnoteAAC.tar.gz new file mode 100644 index 0000000..7f931df Binary files /dev/null and b/app/ZnoteAAC.tar.gz differ diff --git a/app/ZnoteAAC/.editorconfig b/app/ZnoteAAC/.editorconfig new file mode 100644 index 0000000..a654e89 --- /dev/null +++ b/app/ZnoteAAC/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{php,css,html,xml,lua,js}] +indent_style = tab +indent_size = 4 diff --git a/app/ZnoteAAC/.gitattributes b/app/ZnoteAAC/.gitattributes new file mode 100644 index 0000000..425c21f --- /dev/null +++ b/app/ZnoteAAC/.gitattributes @@ -0,0 +1,16 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Declare files that will always have LF line endings on checkout. +*.php text eol=lf +*.lua text eol=lf +*.html text eol=lf +*.css text eol=lf +*.js text eol=lf +*.xml text eol=lf + +*.sql text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary diff --git a/app/ZnoteAAC/.github/FUNDING.yml b/app/ZnoteAAC/.github/FUNDING.yml new file mode 100644 index 0000000..c71be90 --- /dev/null +++ b/app/ZnoteAAC/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: Znote diff --git a/app/ZnoteAAC/.gitignore b/app/ZnoteAAC/.gitignore new file mode 100644 index 0000000..e68cae6 --- /dev/null +++ b/app/ZnoteAAC/.gitignore @@ -0,0 +1,3 @@ +*.cache.php +engine/cache/* +.idea \ No newline at end of file diff --git a/app/ZnoteAAC/.htaccess b/app/ZnoteAAC/.htaccess new file mode 100644 index 0000000..5127602 --- /dev/null +++ b/app/ZnoteAAC/.htaccess @@ -0,0 +1,5 @@ +Options +FollowSymLinks +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ /characterprofile.php?name=$1 \ No newline at end of file diff --git a/app/ZnoteAAC/LICENSE b/app/ZnoteAAC/LICENSE new file mode 100644 index 0000000..230056f --- /dev/null +++ b/app/ZnoteAAC/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Stefan André Brannfjell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/Installation Instructions.txt new file mode 100644 index 0000000..4b60316 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/Installation Instructions.txt @@ -0,0 +1,4 @@ +Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder +-- Edit firstitems.lua with item IDs you want characters to start with on your server. + +Step 2: Restart OT server, and it should work. :) \ No newline at end of file diff --git a/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/firstitems.lua b/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/firstitems.lua new file mode 100644 index 0000000..0c0443a --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_02/creaturescript firstitems/firstitems.lua @@ -0,0 +1,77 @@ +function onLogin(cid) + local storage = 30055 -- storage value + + local sorcItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2190, -- Wand of vortex + 2511, -- Brass shield + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + local druidItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2511, -- Brass shield + 2182, -- Snakebite rod + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + local pallyItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2456, -- Bow + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + } + local kinaItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2511, -- Brass shield + 2412, -- Katana + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + + if getPlayerStorageValue(cid, storage) == -1 then + setPlayerStorageValue(cid, storage, 1) + if getPlayerVocation(cid) == 1 then + -- Sorcerer + for i = 1, table.getn(sorcItems), 1 do + doPlayerAddItem(cid, sorcItems[i], 1, false) + end + + elseif getPlayerVocation(cid) == 2 then + -- Druid + for i = 1, table.getn(druidItems), 1 do + doPlayerAddItem(cid, druidItems[i], 1, false) + end + + elseif getPlayerVocation(cid) == 3 then + -- Paladin + for i = 1, table.getn(pallyItems), 1 do + doPlayerAddItem(cid, pallyItems[i], 1, false) + end + -- 8 arrows + doPlayerAddItem(cid, 2544, 8, false) + + elseif getPlayerVocation(cid) == 4 then + -- Knight + for i = 1, table.getn(kinaItems), 1 do + doPlayerAddItem(cid, kinaItems[i], 1, false) + end + end + + -- Common for all + doPlayerAddItem(cid, 2674, 5, false) -- 5 apples + doPlayerAddItem(cid, 2120, 1, false) -- 1 rope + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/talkaction XML.txt b/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/talkaction XML.txt new file mode 100644 index 0000000..2fdffee --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/talkaction XML.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/znoteshop.lua b/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/znoteshop.lua new file mode 100644 index 0000000..361ff27 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_02/talkaction shopsystem/znoteshop.lua @@ -0,0 +1,103 @@ +-- Znote Shop v1.1 for Znote AAC on TFS 0.2.13+ Mystic Spirit. +function onSay(cid, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if getPlayerStorageValue(cid, storage) <= os.time() then + setPlayerStorageValue(cid, storage, os.time() + cooldown) + local accid = getAccountNumberByPlayerName(getCreatureName(cid)) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + -- Fetch order values + local q_id = result.getDataInt(orderQuery, "id") + local q_type = result.getDataInt(orderQuery, "type") + local q_itemid = result.getDataInt(orderQuery, "itemid") + local q_count = result.getDataInt(orderQuery, "count") + + local description = "Unknown or custom type" + if type_desc[q_type] ~= nil then + description = type_desc[q_type] + end + print("Processing type "..q_type..": ".. description) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get weight + local playerCap = getPlayerFreeCap(cid) + local itemweight = getItemWeight(q_itemid, q_count) + if playerCap >= itemweight then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + doPlayerAddItem(cid, q_itemid, q_count) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received ".. q_count .." "..getItemName(q_itemid).."(s)!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not canPlayerWearOutfit(cid, outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + doPlayerAddOutfit(cid,outfitId,q_count) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + -- Not supported on TFS 0.2 + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Not for TFS 0.2) + -- Type 7 is for Instant house purchase (Not for TFS 0.2) + -- So use type 8+ for custom stuff, like etc packages. + -- if q_type == 8 then + -- end + result.free(orderQuery) + if not served then + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no orders.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time()) + end + return false +end diff --git a/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/Installation Instructions.txt new file mode 100644 index 0000000..742ac3a --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/Installation Instructions.txt @@ -0,0 +1,10 @@ +Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder +-- Edit firstitems.lua with item IDs you want characters to start with on your server. + +Step 2: Edit the /data/creaturescripts/creaturescripts.XML file + - ADD: + +Step 3: Edit the /data/creaturescripts/scripts/login.lua file + - ADD: registerCreatureEvent(cid, "firstItems") + +Step 4: Restart OT server, and it should work. :) \ No newline at end of file diff --git a/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/firstitems.lua b/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/firstitems.lua new file mode 100644 index 0000000..0c0443a --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/creaturescript firstitems/firstitems.lua @@ -0,0 +1,77 @@ +function onLogin(cid) + local storage = 30055 -- storage value + + local sorcItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2190, -- Wand of vortex + 2511, -- Brass shield + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + local druidItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2511, -- Brass shield + 2182, -- Snakebite rod + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + local pallyItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2456, -- Bow + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + } + local kinaItems = { + 2460, -- Brass helmet + 2465, -- Brass armor + 2511, -- Brass shield + 2412, -- Katana + 2478, -- Brass legs + 2643, -- Leather boots + 1988, -- Brown backpack + 2050 -- torch + } + + if getPlayerStorageValue(cid, storage) == -1 then + setPlayerStorageValue(cid, storage, 1) + if getPlayerVocation(cid) == 1 then + -- Sorcerer + for i = 1, table.getn(sorcItems), 1 do + doPlayerAddItem(cid, sorcItems[i], 1, false) + end + + elseif getPlayerVocation(cid) == 2 then + -- Druid + for i = 1, table.getn(druidItems), 1 do + doPlayerAddItem(cid, druidItems[i], 1, false) + end + + elseif getPlayerVocation(cid) == 3 then + -- Paladin + for i = 1, table.getn(pallyItems), 1 do + doPlayerAddItem(cid, pallyItems[i], 1, false) + end + -- 8 arrows + doPlayerAddItem(cid, 2544, 8, false) + + elseif getPlayerVocation(cid) == 4 then + -- Knight + for i = 1, table.getn(kinaItems), 1 do + doPlayerAddItem(cid, kinaItems[i], 1, false) + end + end + + -- Common for all + doPlayerAddItem(cid, 2674, 5, false) -- 5 apples + doPlayerAddItem(cid, 2120, 1, false) -- 1 rope + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/Installation Instructions.txt new file mode 100644 index 0000000..f90be23 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/Installation Instructions.txt @@ -0,0 +1,7 @@ +1. Add below line to XML file: data/creaturescripts/creaturescripts.xml + + +2. Register event in login.lua: data/creaturescripts/scripts/login.lua +registerCreatureEvent(cid, "znote_syncoutfits") + +3. Place Lua file syncoutfit.lua in folder: data/creaturescripts/scripts/ \ No newline at end of file diff --git a/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/syncoutfit.lua b/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/syncoutfit.lua new file mode 100644 index 0000000..4192224 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/creaturescript sync outfits/syncoutfit.lua @@ -0,0 +1,39 @@ +-- Sync outfits that player own with Znote AAC +-- So its possible to see which full sets player +-- has in characterprofile.php + +znote_outfit_list = { + { -- Female (girl) outfits + 136,137,138,139,140,141,142,147,148, + 149,150,155,156,157,158,252,269,270, + 279,288,324,329,336,366,431,433,464, + 466,471,513,514,542,575,578,618,620, + 632,635,636,664,666,683,694,696,698, + 724,732,745,749,759,845,852,874,885, + 900 + }, + { -- Male (boy) outfits + 128,129,130,131,132,133,134,143,144, + 145,146,151,152,153,154,251,268,273, + 278,289,325,328,335,367,430,432,463, + 465,472,512,516,541,574,577,610,619, + 633,634,637,665,667,684,695,697,699, + 725,733,746,750,760,846,853,873,884, + 899 + } +} + +function onLogin(cid) + -- storage_value + 1000 storages (highest outfit id) must not be used in other script. + -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value + local storage_value = 10000 + -- Loop through outfits + for _, outfit in pairs(znote_outfit_list[getPlayerSex(cid)+1]) do + if canPlayerWearOutfit(cid,outfit,3) then + if getPlayerStorageValue(cid,storage_value + outfit) ~= 3 then + setPlayerStorageValue(cid,storage_value + outfit, 3) + end + end + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/Alternatives/znoteshop.lua b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/Alternatives/znoteshop.lua new file mode 100644 index 0000000..93ace81 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/Alternatives/znoteshop.lua @@ -0,0 +1,118 @@ +-- Znote Shop v1.1 for Znote AAC on TFS 0.3.6+ Crying Damson. [Alternative] +function onSay(cid, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if getPlayerStorageValue(cid, storage) <= os.time() then + setPlayerStorageValue(cid, storage, os.time() + cooldown) + local accid = getAccountNumberByPlayerName(getCreatureName(cid)) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getDataInt(orderQuery, "id") + local q_type = result.getDataInt(orderQuery, "type") + local q_itemid = result.getDataInt(orderQuery, "itemid") + local q_count = result.getDataInt(orderQuery, "count") + + local description = "Unknown or custom type" + if type_desc[q_type] ~= nil then + description = type_desc[q_type] + end + print("Processing type "..q_type..": ".. description) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get wheight + local playerCap = getPlayerFreeCap(cid) + local itemweight = getItemWeightById(q_itemid, q_count) + if playerCap >= itemweight then + local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + result.free(delete) + doPlayerAddItem(cid, q_itemid, q_count) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have recieved ".. q_count .." "..getItemNameById(q_itemid).."(s)!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not canPlayerWearOutfit(cid, outfitId, q_count) then + local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + result.free(delete) + doPlayerAddOutfit(cid,outfitId,q_count) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not getPlayerMount(cid, q_itemid) then -- Failed to find a proper hasMount 0.3 function? + local delete = db.storeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + result.free(delete) + doPlayerAddMount(cid, q_itemid) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- Type 7 is for Instant house purchase (Not for TFS 0.3) + -- So use type 8+ for custom stuff, like etc packages. + -- if q_type == 8 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no orders.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time()) + end + return false +end diff --git a/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/talkaction XML.txt b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/talkaction XML.txt new file mode 100644 index 0000000..d511abd --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/talkaction XML.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/znoteshop.lua b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/znoteshop.lua new file mode 100644 index 0000000..2179277 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_03/talkaction shopsystem/znoteshop.lua @@ -0,0 +1,128 @@ +-- Znote Shop v1.1 for Znote AAC on TFS 0.3.6+ Crying Damson. +function onSay(cid, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if getPlayerStorageValue(cid, storage) <= os.time() then + setPlayerStorageValue(cid, storage, os.time() + cooldown) + local accid = getAccountNumberByPlayerName(getCreatureName(cid)) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. getCreatureName(cid) .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. accid .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getDataInt(orderQuery, "id") + local q_type = result.getDataInt(orderQuery, "type") + local q_itemid = result.getDataInt(orderQuery, "itemid") + local q_count = result.getDataInt(orderQuery, "count") + + local description = "Unknown or custom type" + if type_desc[q_type] ~= nil then + description = type_desc[q_type] + end + print("Processing type "..q_type..": ".. description) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get weight + local playerCap = getPlayerFreeCap(cid) + local itemweight = getItemWeightById(q_itemid, q_count) + if playerCap >= itemweight and getTileInfo(getCreaturePosition(cid)).protection then + -- backpack check + local backpack = getPlayerSlotItem(cid, 3) + local gotItem = false + if(backpack and backpack.itemid > 0) then + local received = doAddContainerItem(getPlayerSlotItem(cid, 3).uid, q_itemid,q_count) + if(received ~= false) then + db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received ".. q_count .." "..getItemNameById(q_itemid).."(s)!") + gotItem = true + end + end + + if(not gotItem) then + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You have no available space in backpack to receive that item.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "Need more CAP and Need ProtectZone!") + end + end + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not canPlayerWearOutfit(cid, outfitId, q_count) then + db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + doPlayerAddOutfit(cid,outfitId,q_count) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not getPlayerMount(cid, q_itemid) then -- Failed to find a proper hasMount 0.3 function? + db.executeQuery("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + doPlayerAddMount(cid, q_itemid) + doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- Type 7 is for Instant house purchase (Not for TFS 0.3) + -- So use type 8+ for custom stuff, like etc packages. + -- if q_type == 8 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + + else + doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every "..cooldown.." seconds. Remaining cooldown: ".. getPlayerStorageValue(cid, storage) - os.time()) + end + return false +end diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/Installation Instructions.txt new file mode 100644 index 0000000..b4fcac8 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/Installation Instructions.txt @@ -0,0 +1,4 @@ +Step 1: Copy firstitems.lua to /data/creaturescripts/scripts/ folder +-- Edit firstitems.lua with item IDs you want characters to start with on your server. + +Step 2: Restart OT server, and it should work. :) diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/firstitems.lua b/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/firstitems.lua new file mode 100644 index 0000000..6721392 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript firstitems/firstitems.lua @@ -0,0 +1,114 @@ +-- With Rookgaard + +--[[ +local firstItems = {2050, 2382} -- torch and club + +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) -- coat + player:addItem(ITEM_BAG, 1) + player:addItem(2674, 1) -- red apple + end + return true +end +]]-- + +-- Without Rookgaard +local config = { + [1] = { -- Sorcerer + items = { + {2175, 1}, -- spellbook + {2190, 1}, -- wand of vortex + {8819, 1}, -- magician's robe + {8820, 1}, -- mage hat + {2468, 1}, -- studded legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7620, 1} -- mana potion + } + }, + [2] = { -- Druid + items = { + {2175, 1}, -- spellbook + {2182, 1}, -- snakebite rod + {8819, 1}, -- magician's robe + {8820, 1}, -- mage hat + {2468, 1}, -- studded legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7620, 1} -- mana potion + } + }, + [3] = { -- Paladin + items = { + {2525, 1}, -- dwarven shield + {2389, 5}, -- 5 spears + {2660, 1}, -- ranger's cloak + {8923, 1}, -- ranger legs + {2643, 1}, -- leather boots + {2661, 1}, -- scarf + {2480, 1} -- legion helmet + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7618, 1}, -- health potion + {2456, 1}, -- bow + {2544, 50} -- 50 arrows + } + }, + [4] = { -- Knight + items = { + {2525, 1}, -- dwarven shield + {8601, 1}, -- steel axe + {2465, 1}, -- brass armor + {2460, 1}, -- brass helmet + {2478, 1}, -- brass legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {8602, 1}, -- jagged sword + {2439, 1}, -- daramanian mace + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7618, 1} -- health potion + } + } +} + +function onLogin(player) + local targetVocation = config[player:getVocation():getId()] + if not targetVocation then + return true + end + + if player:getLastLoginSaved() ~= 0 then + return true + end + + for i = 1, #targetVocation.items do + player:addItem(targetVocation.items[i][1], targetVocation.items[i][2]) + end + + local backpack = player:addItem(1988) + if not backpack then + return true + end + + for i = 1, #targetVocation.container do + backpack:addItem(targetVocation.container[i][1], targetVocation.container[i][2]) + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/Installation Instructions.txt new file mode 100644 index 0000000..c5ad6e9 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/Installation Instructions.txt @@ -0,0 +1,3 @@ +Step 1: Replace the one that comes with tfs with this one + +Step 2: Restart OT server, and it should work. :) diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/playerdeath.lua b/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/playerdeath.lua new file mode 100644 index 0000000..2fd63fa --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript playerdeath/playerdeath.lua @@ -0,0 +1,122 @@ +local deathListEnabled = true +local maxDeathRecords = 5 + +local function sendWarStatus(guildId, enemyGuildId, warId, playerName, killerName) + local guild, enemyGuild = Guild(guildId), Guild(enemyGuildId) + if not guild or not enemyGuild then + return + end + + local resultId = db.storeQuery("SELECT `guild_wars`.`id`, (SELECT `limit` FROM `znote_guild_wars` WHERE `znote_guild_wars`.`id` = `guild_wars`.`id`) AS `limit`, (SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild1`) guild1_kills, (SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild2`) guild2_kills FROM `guild_wars` WHERE (`guild1` = " .. guildId .. " OR `guild2` = " .. guildId .. ") AND `status` = 1 AND `id` = " .. warId) + if resultId then + + local guild1_kills = result.getNumber(resultId, "guild1_kills") + local guild2_kills = result.getNumber(resultId, "guild2_kills") + local limit = result.getNumber(resultId, "limit") + result.free(resultId) + + local members = guild:getMembersOnline() + for i = 1, #members do + members[i]:sendChannelMessage("", string.format("%s was killed by %s. The new score is %d:%d frags (limit: %d)", playerName, killerName, guild1_kills, guild2_kills, limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end + + local enemyMembers = enemyGuild:getMembersOnline() + for i = 1, #enemyMembers do + enemyMembers[i]:sendChannelMessage("", string.format("%s was killed by %s. The new score is %d:%d frags (limit: %d)", playerName, killerName, guild1_kills, guild2_kills, limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end + + if guild1_kills >= limit or guild2_kills >= limit then + db.query("UPDATE `guild_wars` SET `status` = 4, `ended` = " .. os.time() .. " WHERE `status` = 1 AND `id` = " .. warId) + Game.broadcastMessage(string.format("%s has just won the war against %s.", guild:getName(), enemyGuild:getName()), MESSAGE_EVENT_ADVANCE) + end + end +end + +function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local playerId = player:getId() + if nextUseStaminaTime[playerId] then + nextUseStaminaTime[playerId] = nil + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") + if not deathListEnabled then + return + end + + local byPlayer = 0 + local killerName + if killer then + if killer:isPlayer() then + byPlayer = 1 + else + local master = killer:getMaster() + if master and master ~= killer and master:isPlayer() then + killer = master + byPlayer = 1 + end + end + killerName = killer:getName() + else + killerName = "field item" + end + + local byPlayerMostDamage = 0 + local mostDamageKillerName + if mostDamageKiller then + if mostDamageKiller:isPlayer() then + byPlayerMostDamage = 1 + else + local master = mostDamageKiller:getMaster() + if master and master ~= mostDamageKiller and master:isPlayer() then + mostDamageKiller = master + byPlayerMostDamage = 1 + end + end + mostDamageName = mostDamageKiller:getName() + else + mostDamageName = "field item" + end + + local playerGuid = player:getGuid() + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. byPlayer .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (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 + local tmpResultId = resultId + while tmpResultId ~= false do + tmpResultId = result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId ~= false then + result.free(resultId) + end + + local limit = deathRecords - maxDeathRecords + if limit > 0 then + db.asyncQuery("DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. " ORDER BY `time` LIMIT " .. limit) + end + + if byPlayer == 1 then + local targetGuild = player:getGuild() + targetGuild = targetGuild and targetGuild:getId() or 0 + if targetGuild ~= 0 then + local killerGuild = killer:getGuild() + killerGuild = killerGuild and killerGuild:getId() or 0 + if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(playerId, killer:getId()) then + local warId = false + resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "))") + if resultId ~= false then + warId = result.getNumber(resultId, "id") + result.free(resultId) + end + + if warId ~= false then + local playerName = player:getName() + db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(playerName) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ")") + addEvent(sendWarStatus, 1000, killerGuild, targetGuild, warId, playerName, killerName) + end + end + end + end +end diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/Installation Instructions.txt new file mode 100644 index 0000000..96f80a1 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/Installation Instructions.txt @@ -0,0 +1,4 @@ +1. Add below line to XML file: data/creaturescripts/creaturescripts.xml + + +2. Place Lua file syncoutfit.lua in folder: data/creaturescripts/scripts/ diff --git a/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/syncoutfit.lua b/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/syncoutfit.lua new file mode 100644 index 0000000..563f6f5 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/creaturescript sync outfits/syncoutfit.lua @@ -0,0 +1,46 @@ +-- Sync outfits that player own with Znote AAC +-- So its possible to see which full sets player +-- has in characterprofile.php + +znote_outfit_list = { + { -- Female outfits + 136, 137, 138, 139, 140, 141, 142, 147, 148, + 149, 150, 155, 156, 157, 158, 252, 269, 270, + 279, 288, 324, 329, 336, 366, 431, 433, 464, + 466, 471, 513, 514, 542, 575, 578, 618, 620, + 632, 635, 636, 664, 666, 683, 694, 696, 698, + 724, 732, 745, 749, 759, 845, 852, 874, 885, + 900, 973, 975, 1020, 1024, 1043, 1050, 1057, + 1070, 1095, 1103, 1128, 1147, 1162, 1174, + 1187, 1203, 1205, 1207, 1211, 1246, 1244, + 1252, 1271, 1280, 1283, 1289, 1293, 1332 + }, + { -- Male outfits + 128, 129, 130, 131, 132, 133, 134, 143, 144, + 145, 146, 151, 152, 153, 154, 251, 268, 273, + 278, 289, 325, 328, 335, 367, 430, 432, 463, + 465, 472, 512, 516, 541, 574, 577, 610, 619, + 633, 634, 637, 665, 667, 684, 695, 697, 699, + 725, 733, 746, 750, 760, 846, 853, 873, 884, + 899, 908, 931, 955, 957, 962, 964, 966, 968, + 970, 972, 974, 1021, 1023, 1042, 1051, 1056, + 1069, 1094, 1102, 1127, 1146, 1161, 1173, + 1186, 1202, 1204, 1206, 1210, 1245, 1243, + 1251, 1270, 1279, 1282, 1288, 1292, 1331 + } +} + +function onLogin(player) + -- storage_value + 1000 storages (highest outfit id) must not be used in other script. + -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value + local storage_value = 10000 + -- Loop through outfits + for _, outfit in pairs(znote_outfit_list[player:getSex() + 1]) do + if player:hasOutfit(outfit,3) then + if player:getStorageValue(storage_value + outfit) ~= 3 then + player:setStorageValue(storage_value + outfit, 3) + end + end + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_10/globalevent powergamers/powergamers.lua b/app/ZnoteAAC/Lua/TFS_10/globalevent powergamers/powergamers.lua new file mode 100644 index 0000000..f63f761 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/globalevent powergamers/powergamers.lua @@ -0,0 +1,64 @@ +-- getEternalStorage and setEternalStorage +-- can be added to data/global.lua if you want to use eternal storage for another purpose than this. +-- Regular TFS global storage values get reset every time server reboots. This does not. +local function getEternalStorage(key, parser) + local value = result.getString(db.storeQuery("SELECT `value` FROM `znote_global_storage` WHERE `key` = ".. key .. ";"), "value") + if not value then + if parser then + return false + else + return -1 + end + end + result.free(value) + return tonumber(value) or value +end + +local function setEternalStorage(key, value) + if getEternalStorage(key, true) then + db.query("UPDATE `znote_global_storage` SET `value` = '".. value .. "' WHERE `key` = ".. key .. ";") + else + db.query("INSERT INTO `znote_global_storage` (`key`, `value`) VALUES (".. key ..", ".. value ..");") + end + return true +end + +-- SQL Query to execute: -- +--[[ +ALTER TABLE `znote_players` ADD `exphist_lastexp` BIGINT NOT NULL DEFAULT '0', +ADD `exphist1` BIGINT NOT NULL DEFAULT '0', +ADD `exphist2` BIGINT NOT NULL DEFAULT '0', +ADD `exphist3` BIGINT NOT NULL DEFAULT '0', +ADD `exphist4` BIGINT NOT NULL DEFAULT '0', +ADD `exphist5` BIGINT NOT NULL DEFAULT '0', +ADD `exphist6` BIGINT NOT NULL DEFAULT '0', +ADD `exphist7` BIGINT NOT NULL DEFAULT '0', +ADD `onlinetimetoday` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime1` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime2` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime3` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime4` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime5` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime6` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime7` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetimeall` INT UNSIGNED NOT NULL DEFAULT '0'; +]]-- + +-- after that execute: -- +--[[ +UPDATE `znote_players` AS `z` INNER JOIN `players` AS `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist_lastexp`=`p`.`experience`; +]]-- + +-- TFS 1.X (data/globalevents.xml) +-- +-- + +function onThink(interval, lastExecution, thinkInterval) + if tonumber(os.date("%d")) ~= getEternalStorage(23856) then + setEternalStorage(23856, (tonumber(os.date("%d")))) + db.query("UPDATE `znote_players` SET `onlinetime7`=`onlinetime6`, `onlinetime6`=`onlinetime5`, `onlinetime5`=`onlinetime4`, `onlinetime4`=`onlinetime3`, `onlinetime3`=`onlinetime2`, `onlinetime2`=`onlinetime1`, `onlinetime1`=`onlinetimetoday`, `onlinetimetoday`=0;") + db.query("UPDATE `znote_players` `z` INNER JOIN `players` `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist7`=`z`.`exphist6`, `z`.`exphist6`=`z`.`exphist5`, `z`.`exphist5`=`z`.`exphist4`, `z`.`exphist4`=`z`.`exphist3`, `z`.`exphist3`=`z`.`exphist2`, `z`.`exphist2`=`z`.`exphist1`, `z`.`exphist1`=`p`.`experience`-`z`.`exphist_lastexp`, `z`.`exphist_lastexp`=`p`.`experience`;") + end + db.query("UPDATE `znote_players` SET `onlinetimetoday` = `onlinetimetoday` + 60, `onlinetimeall` = `onlinetimeall` + 60 WHERE `player_id` IN (SELECT `player_id` FROM `players_online` WHERE `players_online`.`player_id` = `znote_players`.`player_id`)") + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_10/globalevent shopsystem/znoteshop.lua b/app/ZnoteAAC/Lua/TFS_10/globalevent shopsystem/znoteshop.lua new file mode 100644 index 0000000..a98bc2c --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/globalevent shopsystem/znoteshop.lua @@ -0,0 +1,160 @@ +-- +-- Znote Auto Shop v2.1 for Znote AAC on TFS 1.2+ +function onThink(interval, lastExecution) + local shopTypes = {1,5,7} + -- If game support mount orders + if Game.getClientVersion().min >= 870 then + table.insert(shopTypes, 6); + end + local orderQuery = db.storeQuery([[ + SELECT + MIN(`po`.`player_id`) AS `player_id`, + `shop`.`id`, + `shop`.`type`, + `shop`.`itemid`, + `shop`.`count` + FROM `players_online` AS `po` + INNER JOIN `players` AS `p` + ON `po`.`player_id` = `p`.`id` + INNER JOIN `znote_shop_orders` AS `shop` + ON `p`.`account_id` = `shop`.`account_id` + WHERE `shop`.`type` IN(]] .. table.concat(shopTypes, ",") .. [[) + GROUP BY `shop`.`id` + ]]) + -- Detect if we got any results + if orderQuery ~= false then + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + repeat + local player_id = result.getNumber(orderQuery, 'player_id') + local orderId = result.getNumber(orderQuery, 'id') + local orderType = result.getNumber(orderQuery, 'type') + local orderItemId = result.getNumber(orderQuery, 'itemid') + local orderCount = result.getNumber(orderQuery, 'count') + local served = false + + local player = Player(player_id) + if player ~= nil then + + local description = "Unknown or custom type" + if type_desc[orderType] ~= nil then + description = type_desc[orderType] + end + print("Processing type "..orderType..": ".. description) + print("Processing shop order for: [".. player:getName() .."] type "..orderType..": ".. description) + + local tile = Tile(player:getPosition()) + if tile ~= nil and tile:hasFlag(TILESTATE_PROTECTIONZONE) then + -- ORDER TYPE 1 (Regular item shop products) + if orderType == 1 then + served = true + local itemType = ItemType(orderItemId) + -- Get weight + if player:getFreeCapacity() >= itemType:getWeight(orderCount) then + local backpack = player:getSlotItem(CONST_SLOT_BACKPACK) + -- variable = (condition) and (return if true) or (return if false) + local needslots = itemType:isStackable() and math.floor(orderCount / 100) + 1 or orderCount + if backpack ~= nil and backpack:getEmptySlots(false) >= needslots then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addItem(orderItemId, orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. "!") + print("Process complete. [".. player:getName() .."] has received " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + else -- not enough slots + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Your main backpack is full. You need to free up "..needslots.." available slots to get " .. orderCount .. " " .. ItemType(orderItemId):getName() .. "!") + print("Process canceled. [".. player:getName() .."] need more space in his backpack to get " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + end + else -- not enough cap + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You need more CAP to carry this order!") + print("Process canceled. [".. player:getName() .."] need more cap to carry " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if orderType == 5 then + served = true + + local itemid = orderItemId + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, orderCount) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + print("Process complete. [".. player:getName() .."] has received outfit: ["..outfitId.."] with addon: ["..orderCount.."]") + else -- Already has outfit + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + print("Process canceled. [".. player:getName() .."] already have outfit: ["..outfitId.."] with addon: ["..orderCount.."].") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if orderType == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(orderItemId) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addMount(orderItemId) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + print("Process complete. [".. player:getName() .."] has received mount: ["..orderItemId.."]") + else -- Already has mount + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + print("Process canceled. [".. player:getName() .."] already have mount: ["..orderItemId.."].") + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if orderType == 7 then + served = true + local house = House(orderItemId) + -- Logged in player is not necessarily the player that bough the house. So we need to load player from db. + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..orderCount.." LIMIT 1") + if buyerQuery ~= false then + local buyerName = result.getString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + house:setOwnerGuid(orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has received house: ["..house:getName().."]") + else + print("Process canceled. Failed to load house with ID: "..orderItemId) + end + else + print("Process canceled. Failed to load player with ID: "..orderCount) + end + end + + if not served then -- If this order hasn't been processed yet (missing type handling?) + print("Znote shop: Type ["..orderType.."] not properly processed. Missing Lua code?") + end + else -- Not in protection zone + player:sendTextMessage(MESSAGE_INFO_DESCR, 'You have a pending shop order, please enter protection zone.') + print("Skipped one shop order. Reason: Player: [".. player:getName() .."] is not inside protection zone.") + end + else -- player not logged in + print("Skipped one shop order. Reason: Player with id [".. player_id .."] is not online.") + end + + until not result.next(orderQuery) + result.free(orderQuery) + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/Installation Instructions.txt b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/Installation Instructions.txt new file mode 100644 index 0000000..b847dae --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/Installation Instructions.txt @@ -0,0 +1,3 @@ +Step 1: Put script on data/script folder (edit content if necessary) + +Step 2: Restart OT server, and it should work. :) diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items.lua new file mode 100644 index 0000000..754224f --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items.lua @@ -0,0 +1,99 @@ +local creatureevent = CreatureEvent("FirstItems") + +local config = { + [1] = { -- Sorcerer + items = { + {2175, 1}, -- spellbook + {2190, 1}, -- wand of vortex + {8819, 1}, -- magician's robe + {8820, 1}, -- mage hat + {2468, 1}, -- studded legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7620, 1} -- mana potion + } + }, + [2] = { -- Druid + items = { + {2175, 1}, -- spellbook + {2182, 1}, -- snakebite rod + {8819, 1}, -- magician's robe + {8820, 1}, -- mage hat + {2468, 1}, -- studded legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7620, 1} -- mana potion + } + }, + [3] = { -- Paladin + items = { + {2525, 1}, -- dwarven shield + {2389, 5}, -- 5 spears + {2660, 1}, -- ranger's cloak + {8923, 1}, -- ranger legs + {2643, 1}, -- leather boots + {2661, 1}, -- scarf + {2480, 1} -- legion helmet + }, + container = { + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7618, 1}, -- health potion + {2456, 1}, -- bow + {2544, 50} -- 50 arrows + } + }, + [4] = { -- Knight + items = { + {2525, 1}, -- dwarven shield + {8601, 1}, -- steel axe + {2465, 1}, -- brass armor + {2460, 1}, -- brass helmet + {2478, 1}, -- brass legs + {2643, 1}, -- leather boots + {2661, 1} -- scarf + }, + container = { + {8602, 1}, -- jagged sword + {2439, 1}, -- daramanian mace + {2120, 1}, -- rope + {2554, 1}, -- shovel + {7618, 1} -- health potion + } + } +} + +function creatureevent.onLogin(player) + local targetVocation = config[player:getVocation():getId()] + if not targetVocation then + return true + end + + if player:getLastLoginSaved() ~= 0 then + return true + end + + for i = 1, #targetVocation.items do + player:addItem(targetVocation.items[i][1], targetVocation.items[i][2]) + end + + local backpack = player:addItem(1988) -- backpack + if not backpack then + return true + end + + for i = 1, #targetVocation.container do + backpack:addItem(targetVocation.container[i][1], targetVocation.container[i][2]) + end + return true +end + +creatureevent:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items_rook.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items_rook.lua new file mode 100644 index 0000000..54d5642 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/first_items_rook.lua @@ -0,0 +1,17 @@ +local creatureevent = CreatureEvent("FirstItemsRook") + +local firstItems = {2050, 2382} -- torch and club + +function creatureevent.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) -- coat + player:addItem(ITEM_BAG, 1) + player:addItem(2674, 1) -- red apple + end + return true +end + +creatureevent:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/playerdeath.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/playerdeath.lua new file mode 100644 index 0000000..0ec8bf4 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/playerdeath.lua @@ -0,0 +1,135 @@ +local deathListEnabled = true +local maxDeathRecords = 5 + +local function sendWarStatus(guildId, enemyGuildId, warId, playerName, killerName) + local guild, enemyGuild = Guild(guildId), Guild(enemyGuildId) + if not guild or not enemyGuild then + return + end + + local resultId = db.storeQuery("SELECT `guild_wars`.`id`, (SELECT `limit` FROM `znote_guild_wars` WHERE `znote_guild_wars`.`id` = `guild_wars`.`id`) AS `limit`, (SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild1`) guild1_kills, (SELECT COUNT(1) FROM `guildwar_kills` WHERE `guildwar_kills`.`warid` = `guild_wars`.`id` AND `guildwar_kills`.`killerguild` = `guild_wars`.`guild2`) guild2_kills FROM `guild_wars` WHERE (`guild1` = " .. guildId .. " OR `guild2` = " .. guildId .. ") AND `status` = 1 AND `id` = " .. warId) + if resultId then + + local guild1_kills = result.getNumber(resultId, "guild1_kills") + local guild2_kills = result.getNumber(resultId, "guild2_kills") + local limit = result.getNumber(resultId, "limit") + result.free(resultId) + + local members = guild:getMembersOnline() + for i = 1, #members do + members[i]:sendChannelMessage("", string.format("%s was killed by %s. The new score is %d:%d frags (limit: %d)", playerName, killerName, guild1_kills, guild2_kills, limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end + + local enemyMembers = enemyGuild:getMembersOnline() + for i = 1, #enemyMembers do + enemyMembers[i]:sendChannelMessage("", string.format("%s was killed by %s. The new score is %d:%d frags (limit: %d)", playerName, killerName, guild1_kills, guild2_kills, limit), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD) + end + + if guild1_kills >= limit or guild2_kills >= limit then + db.query("UPDATE `guild_wars` SET `status` = 4, `ended` = " .. os.time() .. " WHERE `status` = 1 AND `id` = " .. warId) + Game.broadcastMessage(string.format("%s has just won the war against %s.", guild:getName(), enemyGuild:getName()), MESSAGE_EVENT_ADVANCE) + end + end +end + +local creatureevent = CreatureEvent("PlayerDeath") + +function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local playerId = player:getId() + if nextUseStaminaTime[playerId] then + nextUseStaminaTime[playerId] = nil + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") + if not deathListEnabled then + return + end + + local byPlayer = 0 + local killerName + if killer then + if killer:isPlayer() then + byPlayer = 1 + else + local master = killer:getMaster() + if master and master ~= killer and master:isPlayer() then + killer = master + byPlayer = 1 + end + end + killerName = killer:getName() + else + killerName = "field item" + end + + local byPlayerMostDamage = 0 + local mostDamageKillerName + if mostDamageKiller then + if mostDamageKiller:isPlayer() then + byPlayerMostDamage = 1 + else + local master = mostDamageKiller:getMaster() + if master and master ~= mostDamageKiller and master:isPlayer() then + mostDamageKiller = master + byPlayerMostDamage = 1 + end + end + mostDamageName = mostDamageKiller:getName() + else + mostDamageName = "field item" + end + + local playerGuid = player:getGuid() + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. byPlayer .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (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 + local tmpResultId = resultId + while tmpResultId ~= false do + tmpResultId = result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId ~= false then + result.free(resultId) + end + + local limit = deathRecords - maxDeathRecords + if limit > 0 then + db.asyncQuery("DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. " ORDER BY `time` LIMIT " .. limit) + end + + if byPlayer == 1 then + local targetGuild = player:getGuild() + targetGuild = targetGuild and targetGuild:getId() or 0 + if targetGuild ~= 0 then + local killerGuild = killer:getGuild() + killerGuild = killerGuild and killerGuild:getId() or 0 + if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(playerId, killer:getId()) then + local warId = false + resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "))") + if resultId ~= false then + warId = result.getNumber(resultId, "id") + result.free(resultId) + end + + if warId ~= false then + local playerName = player:getName() + db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(playerName) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ")") + addEvent(sendWarStatus, 1000, killerGuild, targetGuild, warId, playerName, killerName) + end + end + end + end +end + +creatureevent:register() + +local creatureeventLogin = CreatureEvent("creatureeventLogin") + +function creatureeventLogin.onLogin(player) + player:registerEvent("PlayerDeath") + return true +end + +creatureeventLogin:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/powergamers.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/powergamers.lua new file mode 100644 index 0000000..8bad04a --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/powergamers.lua @@ -0,0 +1,65 @@ +-- getEternalStorage and setEternalStorage +-- can be added to data/global.lua if you want to use eternal storage for another purpose than this. +-- Regular TFS global storage values get reset every time server reboots. This does not. +local function getEternalStorage(key, parser) + local value = result.getString(db.storeQuery("SELECT `value` FROM `znote_global_storage` WHERE `key` = ".. key .. ";"), "value") + if not value then + if parser then + return false + else + return -1 + end + end + result.free(value) + return tonumber(value) or value +end + +local function setEternalStorage(key, value) + if getEternalStorage(key, true) then + db.query("UPDATE `znote_global_storage` SET `value` = '".. value .. "' WHERE `key` = ".. key .. ";") + else + db.query("INSERT INTO `znote_global_storage` (`key`, `value`) VALUES (".. key ..", ".. value ..");") + end + return true +end + +-- SQL Query to execute: -- +--[[ +ALTER TABLE `znote_players` ADD `exphist_lastexp` BIGINT NOT NULL DEFAULT '0', +ADD `exphist1` BIGINT NOT NULL DEFAULT '0', +ADD `exphist2` BIGINT NOT NULL DEFAULT '0', +ADD `exphist3` BIGINT NOT NULL DEFAULT '0', +ADD `exphist4` BIGINT NOT NULL DEFAULT '0', +ADD `exphist5` BIGINT NOT NULL DEFAULT '0', +ADD `exphist6` BIGINT NOT NULL DEFAULT '0', +ADD `exphist7` BIGINT NOT NULL DEFAULT '0', +ADD `onlinetimetoday` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime1` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime2` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime3` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime4` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime5` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime6` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetime7` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', +ADD `onlinetimeall` INT UNSIGNED NOT NULL DEFAULT '0'; +]]-- + +-- after that execute: -- +--[[ +UPDATE `znote_players` AS `z` INNER JOIN `players` AS `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist_lastexp`=`p`.`experience`; +]]-- + +local globalevent = GlobalEvent("PowerGamers") + +function globalevent.onThink(...) + if tonumber(os.date("%d")) ~= getEternalStorage(23856) then + setEternalStorage(23856, (tonumber(os.date("%d")))) + db.query("UPDATE `znote_players` SET `onlinetime7`=`onlinetime6`, `onlinetime6`=`onlinetime5`, `onlinetime5`=`onlinetime4`, `onlinetime4`=`onlinetime3`, `onlinetime3`=`onlinetime2`, `onlinetime2`=`onlinetime1`, `onlinetime1`=`onlinetimetoday`, `onlinetimetoday`=0;") + db.query("UPDATE `znote_players` `z` INNER JOIN `players` `p` ON `p`.`id`=`z`.`player_id` SET `z`.`exphist7`=`z`.`exphist6`, `z`.`exphist6`=`z`.`exphist5`, `z`.`exphist5`=`z`.`exphist4`, `z`.`exphist4`=`z`.`exphist3`, `z`.`exphist3`=`z`.`exphist2`, `z`.`exphist2`=`z`.`exphist1`, `z`.`exphist1`=`p`.`experience`-`z`.`exphist_lastexp`, `z`.`exphist_lastexp`=`p`.`experience`;") + end + db.query("UPDATE `znote_players` SET `onlinetimetoday` = `onlinetimetoday` + 60, `onlinetimeall` = `onlinetimeall` + 60 WHERE `player_id` IN (SELECT `player_id` FROM `players_online` WHERE `players_online`.`player_id` = `znote_players`.`player_id`)") + return true +end + +globalevent:interval(60000) +globalevent:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/report_talkaction.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/report_talkaction.lua new file mode 100644 index 0000000..ddf2912 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/report_talkaction.lua @@ -0,0 +1,21 @@ +local talkaction = TalkAction("!report") + +function talkaction.onSay(player) + local storage = 6708 -- You can change the storage if its already in use + local delaytime = 30 -- Exhaust In Seconds. + if param == '' then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, "Command param required.") + return true + end + if player:getStorageValue(storage) <= os.time() then + player:sendTextMessage(MESSAGE_INFO_DESCR, "Your report has been received successfully!") + db.query("INSERT INTO `znote_player_reports` (`id` ,`name` ,`posx` ,`posy` ,`posz` ,`report_description` ,`date`)VALUES (NULL , " .. db.escapeString(player:getName()) .. ", '" .. player:getPosition().x .. "', '" .. player:getPosition().y .. "', '" .. player:getPosition().z .. "', " .. db.escapeString(param) .. ", '" .. os.time() .. "')") + player:setStorageValue(storage, os.time() + delaytime) + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You have to wait " .. player:getStorageValue(storage) - os.time() .. " seconds to report again.") + end + return true +end + +talkaction:separator(" ") +talkaction:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_globalevent.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_globalevent.lua new file mode 100644 index 0000000..821aa7d --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_globalevent.lua @@ -0,0 +1,163 @@ +local globalevent = GlobalEvent("ShopSystemGlobal") + +function globalevent.onThink(...) + local shopTypes = {1,5,7} + -- If game support mount orders + if Game.getClientVersion().min >= 870 then + table.insert(shopTypes, 6); + end + local orderQuery = db.storeQuery([[ + SELECT + MIN(`po`.`player_id`) AS `player_id`, + `shop`.`id`, + `shop`.`type`, + `shop`.`itemid`, + `shop`.`count` + FROM `players_online` AS `po` + INNER JOIN `players` AS `p` + ON `po`.`player_id` = `p`.`id` + INNER JOIN `znote_shop_orders` AS `shop` + ON `p`.`account_id` = `shop`.`account_id` + WHERE `shop`.`type` IN(]] .. table.concat(shopTypes, ",") .. [[) + GROUP BY `shop`.`id` + ]]) + -- Detect if we got any results + if orderQuery ~= false then + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + repeat + local player_id = result.getNumber(orderQuery, 'player_id') + local orderId = result.getNumber(orderQuery, 'id') + local orderType = result.getNumber(orderQuery, 'type') + local orderItemId = result.getNumber(orderQuery, 'itemid') + local orderCount = result.getNumber(orderQuery, 'count') + local served = false + + local player = Player(player_id) + if player ~= nil then + + local description = "Unknown or custom type" + if type_desc[orderType] ~= nil then + description = type_desc[orderType] + end + print("Processing type "..orderType..": ".. description) + print("Processing shop order for: [".. player:getName() .."] type "..orderType..": ".. description) + + local tile = Tile(player:getPosition()) + if tile ~= nil and tile:hasFlag(TILESTATE_PROTECTIONZONE) then + -- ORDER TYPE 1 (Regular item shop products) + if orderType == 1 then + served = true + local itemType = ItemType(orderItemId) + -- Get weight + if player:getFreeCapacity() >= itemType:getWeight(orderCount) then + local backpack = player:getSlotItem(CONST_SLOT_BACKPACK) + -- variable = (condition) and (return if true) or (return if false) + local needslots = itemType:isStackable() and math.floor(orderCount / 100) + 1 or orderCount + if backpack ~= nil and backpack:getEmptySlots(false) >= needslots then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addItem(orderItemId, orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. "!") + print("Process complete. [".. player:getName() .."] has received " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + else -- not enough slots + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Your main backpack is full. You need to free up "..needslots.." available slots to get " .. orderCount .. " " .. ItemType(orderItemId):getName() .. "!") + print("Process canceled. [".. player:getName() .."] need more space in his backpack to get " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + end + else -- not enough cap + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You need more CAP to carry this order!") + print("Process canceled. [".. player:getName() .."] need more cap to carry " .. orderCount .. "x " .. ItemType(orderItemId):getName() .. ".") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if orderType == 5 then + served = true + + local itemid = orderItemId + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, orderCount) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + print("Process complete. [".. player:getName() .."] has received outfit: ["..outfitId.."] with addon: ["..orderCount.."]") + else -- Already has outfit + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + print("Process canceled. [".. player:getName() .."] already have outfit: ["..outfitId.."] with addon: ["..orderCount.."].") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if orderType == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(orderItemId) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + player:addMount(orderItemId) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + print("Process complete. [".. player:getName() .."] has received mount: ["..orderItemId.."]") + else -- Already has mount + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + print("Process canceled. [".. player:getName() .."] already have mount: ["..orderItemId.."].") + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if orderType == 7 then + served = true + local house = House(orderItemId) + -- Logged in player is not necessarily the player that bough the house. So we need to load player from db. + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..orderCount.." LIMIT 1") + if buyerQuery ~= false then + local buyerName = result.getString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + house:setOwnerGuid(orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has received house: ["..house:getName().."]") + else + print("Process canceled. Failed to load house with ID: "..orderItemId) + end + else + print("Process canceled. Failed to load player with ID: "..orderCount) + end + end + + if not served then -- If this order hasn't been processed yet (missing type handling?) + print("Znote shop: Type ["..orderType.."] not properly processed. Missing Lua code?") + end + else -- Not in protection zone + player:sendTextMessage(MESSAGE_INFO_DESCR, 'You have a pending shop order, please enter protection zone.') + print("Skipped one shop order. Reason: Player: [".. player:getName() .."] is not inside protection zone.") + end + else -- player not logged in + print("Skipped one shop order. Reason: Player with id [".. player_id .."] is not online.") + end + + until not result.next(orderQuery) + result.free(orderQuery) + end + return true +end + +globalevent:interval(30000) +globalevent:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_talkaction.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_talkaction.lua new file mode 100644 index 0000000..cdbe44d --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/shopsystem_talkaction.lua @@ -0,0 +1,137 @@ +local talkaction = TalkAction("!shop") + +function talkaction.onSay(player) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if player:getStorageValue(storage) <= os.time() then + player:setStorageValue(storage, os.time() + cooldown) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. player:getName() .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getNumber(orderQuery, "id") + local q_type = result.getNumber(orderQuery, "type") + local q_itemid = result.getNumber(orderQuery, "itemid") + local q_count = result.getNumber(orderQuery, "count") + + local description = "Unknown or custom type" + if type_desc[q_type] ~= nil then + description = type_desc[q_type] + end + print("Processing type "..q_type..": ".. description) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get weight + if player:getFreeCapacity() >= ItemType(q_itemid):getWeight(q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addItem(q_itemid, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " x " .. ItemType(q_itemid):getName() .. "!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + if Game.getClientVersion().min >= 870 then + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(q_itemid) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addMount(q_itemid) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if q_type == 7 then + served = true + local house = House(q_itemid) + -- Logged in player is not necessarily the player that bough the house. So we need to load player from db. + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..q_count.." LIMIT 1") + if buyerQuery ~= false then + local buyerName = result.getString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + house:setOwnerGuid(q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has received house: ["..house:getName().."]") + end + end + end + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- Type 7 is for Instant house purchase (Already coded here) + -- So use type 8+ for custom stuff, like etc packages. + -- if q_type == 8 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every " .. cooldown .. " seconds. Remaining cooldown: " .. player:getStorageValue(storage) - os.time()) + end + return false +end + +talkaction:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/sync_outfit.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/sync_outfit.lua new file mode 100644 index 0000000..236b246 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/sync_outfit.lua @@ -0,0 +1,50 @@ +local creatureevent = CreatureEvent("SincOutfit") + +-- Sync outfits that player own with Znote AAC +-- So its possible to see which full sets player +-- has in characterprofile.php + +znote_outfit_list = { + { -- Female outfits + 136, 137, 138, 139, 140, 141, 142, 147, 148, + 149, 150, 155, 156, 157, 158, 252, 269, 270, + 279, 288, 324, 329, 336, 366, 431, 433, 464, + 466, 471, 513, 514, 542, 575, 578, 618, 620, + 632, 635, 636, 664, 666, 683, 694, 696, 698, + 724, 732, 745, 749, 759, 845, 852, 874, 885, + 900, 973, 975, 1020, 1024, 1043, 1050, 1057, + 1070, 1095, 1103, 1128, 1147, 1162, 1174, + 1187, 1203, 1205, 1207, 1211, 1246, 1244, + 1252, 1271, 1280, 1283, 1289, 1293, 1332 + }, + { -- Male outfits + 128, 129, 130, 131, 132, 133, 134, 143, 144, + 145, 146, 151, 152, 153, 154, 251, 268, 273, + 278, 289, 325, 328, 335, 367, 430, 432, 463, + 465, 472, 512, 516, 541, 574, 577, 610, 619, + 633, 634, 637, 665, 667, 684, 695, 697, 699, + 725, 733, 746, 750, 760, 846, 853, 873, 884, + 899, 908, 931, 955, 957, 962, 964, 966, 968, + 970, 972, 974, 1021, 1023, 1042, 1051, 1056, + 1069, 1094, 1102, 1127, 1146, 1161, 1173, + 1186, 1202, 1204, 1206, 1210, 1245, 1243, + 1251, 1270, 1279, 1282, 1288, 1292, 1331 + } +} + +function creatureevent.onLogin(player) + -- storage_value + 1000 storages (highest outfit id) must not be used in other script. + -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value + local storage_value = 10000 + -- Loop through outfits + for _, outfit in pairs(znote_outfit_list[player:getSex() + 1]) do + if player:hasOutfit(outfit,3) then + if player:getStorageValue(storage_value + outfit) ~= 3 then + player:setStorageValue(storage_value + outfit, 3) + end + end + end + return true +end + +creatureevent:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/revscriptsys/znote_login.lua b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/znote_login.lua new file mode 100644 index 0000000..53e5cea --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/revscriptsys/znote_login.lua @@ -0,0 +1,68 @@ +-- Znote LoginWebService (version 1) for protocol 11, 12+ +-- Move file to this location: data/scripts/znote_login.lua +-- And restart OT server, it should auto load script. +-- Requires updated version of Znote AAC. (18. June 2020) +-- This script will help Znote AAC connect players to this game server. + +local znote_loginWebService = GlobalEvent("znote_loginWebService") +function znote_loginWebService.onStartup() + print(" ") + print("=============================") + print("= Znote AAC loginWebService =") + print("=============================") + local configLua = { + ["SERVER_NAME"] = configManager.getString(configKeys.SERVER_NAME), + ["IP"] = configManager.getString(configKeys.IP), + ["GAME_PORT"] = configManager.getNumber(configKeys.GAME_PORT) + } + local configSQL = { + ["SERVER_NAME"] = false, + ["IP"] = false, + ["GAME_PORT"] = false + } + local webStorage = db.storeQuery([[ + SELECT + `key`, + `value` + FROM `znote_global_storage` + WHERE `key` IN('SERVER_NAME', 'IP', 'GAME_PORT') + ]]) + if webStorage ~= false then + repeat + local key = result.getString(webStorage, 'key') + local value = result.getString(webStorage, 'value') + configSQL[key] = value + until not result.next(webStorage) + result.free(webStorage) + end + local inserts = {} + if configSQL.SERVER_NAME == false then + table.insert(inserts, "('SERVER_NAME',".. db.escapeString(configLua.SERVER_NAME) ..")") + elseif configSQL.SERVER_NAME ~= configLua.SERVER_NAME then + db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.SERVER_NAME) .." WHERE `key`='SERVER_NAME';") + print("= Updated [SERVER_NAME] FROM [" .. configSQL.SERVER_NAME .. "] to [" .. configLua.SERVER_NAME .. "]") + end + if configSQL.IP == false then + table.insert(inserts, "('IP',".. db.escapeString(configLua.IP) ..")") + elseif configSQL.IP ~= configLua.IP then + db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.IP) .." WHERE `key`='IP';") + print("= Updated [IP] FROM [" .. configSQL.IP .. "] to [" .. configLua.IP .. "]") + end + if configSQL.GAME_PORT == false then + table.insert(inserts, "('GAME_PORT',".. db.escapeString(configLua.GAME_PORT) ..")") + elseif configSQL.GAME_PORT ~= tostring(configLua.GAME_PORT) then + db.query("UPDATE `znote_global_storage` SET `value`=".. db.escapeString(configLua.GAME_PORT) .." WHERE `key`='GAME_PORT';") + print("= Updated [GAME_PORT] FROM [" .. configSQL.GAME_PORT .. "] to [" .. configLua.GAME_PORT .. "]") + end + if #inserts > 0 then + db.query("INSERT INTO `znote_global_storage` (`key`,`value`) VALUES "..table.concat(inserts,',')..";") + print("= Fixed " .. #inserts .. " missing configurations.") + end + print("=============================") + print("= SERVER_NAME: " .. configLua.SERVER_NAME) + print("= IP: " .. configLua.IP) + print("= GAME_PORT: " .. configLua.GAME_PORT) + print("=============================") + print(" ") +end +znote_loginWebService:register() diff --git a/app/ZnoteAAC/Lua/TFS_10/talkaction report system/adminreport.lua b/app/ZnoteAAC/Lua/TFS_10/talkaction report system/adminreport.lua new file mode 100644 index 0000000..e1f6863 --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/talkaction report system/adminreport.lua @@ -0,0 +1,18 @@ +-- +-- Coded by Dark ShaoOz, modified by Znote +function onSay(player, words, param) + local storage = 6708 -- You can change the storage if its already in use + local delaytime = 30 -- Exhaust In Seconds. + if param == '' then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, "Command param required.") + return true + end + if player:getStorageValue(storage) <= os.time() then + player:sendTextMessage(MESSAGE_INFO_DESCR, "Your report has been received successfully!") + db.query("INSERT INTO `znote_player_reports` (`id` ,`name` ,`posx` ,`posy` ,`posz` ,`report_description` ,`date`)VALUES (NULL , " .. db.escapeString(player:getName()) .. ", '" .. player:getPosition().x .. "', '" .. player:getPosition().y .. "', '" .. player:getPosition().z .. "', " .. db.escapeString(param) .. ", '" .. os.time() .. "')") + player:setStorageValue(storage, os.time() + delaytime) + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You have to wait " .. player:getStorageValue(storage) - os.time() .. " seconds to report again.") + end + return true +end diff --git a/app/ZnoteAAC/Lua/TFS_10/talkaction shopsystem/znoteshop.lua b/app/ZnoteAAC/Lua/TFS_10/talkaction shopsystem/znoteshop.lua new file mode 100644 index 0000000..8f3981b --- /dev/null +++ b/app/ZnoteAAC/Lua/TFS_10/talkaction shopsystem/znoteshop.lua @@ -0,0 +1,135 @@ +-- +-- Znote Shop v1.1 for Znote AAC on TFS 1.2+ +function onSay(player, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if player:getStorageValue(storage) <= os.time() then + player:setStorageValue(storage, os.time() + cooldown) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. player:getName() .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getNumber(orderQuery, "id") + local q_type = result.getNumber(orderQuery, "type") + local q_itemid = result.getNumber(orderQuery, "itemid") + local q_count = result.getNumber(orderQuery, "count") + + local description = "Unknown or custom type" + if type_desc[q_type] ~= nil then + description = type_desc[q_type] + end + print("Processing type "..q_type..": ".. description) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get weight + if player:getFreeCapacity() >= ItemType(q_itemid):getWeight(q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addItem(q_itemid, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " x " .. ItemType(q_itemid):getName() .. "!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + if Game.getClientVersion().min >= 870 then + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(q_itemid) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addMount(q_itemid) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if q_type == 7 then + served = true + local house = House(q_itemid) + -- Logged in player is not necessarily the player that bough the house. So we need to load player from db. + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..q_count.." LIMIT 1") + if buyerQuery ~= false then + local buyerName = result.getString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + house:setOwnerGuid(q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has received house: ["..house:getName().."]") + end + end + end + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- Type 7 is for Instant house purchase (Already coded here) + -- So use type 8+ for custom stuff, like etc packages. + -- if q_type == 8 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every " .. cooldown .. " seconds. Remaining cooldown: " .. player:getStorageValue(storage) - os.time()) + end + return false +end diff --git a/app/ZnoteAAC/README.md b/app/ZnoteAAC/README.md new file mode 100644 index 0000000..1f1e242 --- /dev/null +++ b/app/ZnoteAAC/README.md @@ -0,0 +1,168 @@ +ZnoteAAC +======== +[![CodeFactor](https://www.codefactor.io/repository/github/znote/znoteaac/badge)](https://www.codefactor.io/repository/github/znote/znoteaac) +### What is Znote AAC? + +Znote AAC is a full-fledged website used together with an Open Tibia(OT) server. +It aims to be super easy to install and compatible with all the popular OT distributions. +It is created in PHP with a simple custom procedural framework. + +### Where do I download? + +We use github to distribute our versions, stable are tagged as releases, while development is the latest commit. +* [Stable](https://github.com/Znote/ZnoteAAC/releases) +* [Development](https://github.com/Znote/ZnoteAAC/archive/master.zip) + +### Requirements +* PHP Version 5.6 or higher. Mostly tested on 5.6 and 7.4. Most web stacks ships with this as default these days. + +### Optionals +* For email registration verification and account recovery: [PHPMailer](https://github.com/PHPMailer/PHPMailer/releases) Version 6.x, extracted and renamed to just "PHPMailer" in Znote AAC directory. +* PHP extension curl for PHPMailer, paypal and google reCaptcha services. +* PHP extension openssl for google reCaptcha services. +* PHP extension gd for guild logos. + +### Installation instructions + +1: Extract the .zip file to your web directory (Example: C:\UniServ\www\ ) +Without modifying config.php, enter the website and wait for mysql connection error. +This will show you the rest of the instructions as well as the mysql schema. + +2: Edit config.php and: +- modify $config['ServerEngine'] with correct TFS version you are running. (TFS_02, TFS_03, TFS_10, OTHIRE). +- modify $config['page_admin_access'] with your admin account username(s). + +3: Before inserting correct SQL connection details, visit the website ( http://127.0.0.1/ ), it will generate a mysql schema you should import to your OT servers database. + +4: Follow the steps on the website and import the SQL schema for Znote AAC, and edit config.php with correct mysql details. + +5: IF you have existing database from active OT server, enter the folder called "special" and convert the database for Znote AAC support ( http://127.0.0.1/special/ ) + +6: Enjoy Znote AAC. You can look around [HERE](https://otland.net/forums/website-applications.118/) for plugins and resources to Znote AAC, for instance various free templates to use. + +7: Please note that you need PHP cURL enabled to make Paypal payments work. + +8: You may need to change directory access rights of /engine/cache to allow writing. + +### Features: +Znote AAC is very rich feature wise, here is an attempt at summarizing what we offer. + +#### Server distribution compatibility: +- [Znote AAC 1.6](https://github.com/Znote/ZnoteAAC/releases/tag/1.6) + - OTHire + - TFS 0.2 + - TFS 0.3/4 + - TFS 1.3 + - Distributions based on these (such as OTX). +- Znote AAC 2.0 [v2 dev branch](https://github.com/Znote/ZnoteAAC/tree/v2) + - TFS 1.4 + - OTservBR-Global + +#### General +- Server wide latest death list +- Server wide latest kills list +- Server information with PvP settings, skill rates, experience stages (parses config.lua and stages.xml file) +- Spells page with vocation filters (parses spells.xml file) +- Item list showing equippable items (parses items.xml file) + +#### Account & login: +- Basic account registration +- Change password and email +- reCaptcha antibot(spam) system +- Email verification & lost account interface +- Two-factor authentication support +- Hide characters from character list +- Support helpdesk (tickets) + +#### Create character: +- Supports custom vocations, starting skills, available towns +- Character firstitems through provided Lua script +- Soft character deletion + +#### House: +- Houses list with towns filter +- House bidding +- Direct house purchase with shop points + +#### Character profile +- General information such as name, vocation, level, guild membership etc... +- Obtained achievement list +- Player comments +- Death list +- Quest progression +- Character list +- EQ shower, skills, full outfits + +#### Guilds +- Configurable level and account type restrictions to create guild +- Create and disband guilds +- Invite and revoke players to guild +- Change name of guild positions +- Add nickname to guild members +- Guild forum board accessible only for guild members & admin. +- Upload guild image +- Guild description +- Invite, accept and cancel war declarations +- View ongoing guild wars + +#### Item market +- Want to buy list +- Want to sell list +- Item search +- Compare item offer with other similar offers, as well as transaction history + +#### Downloads +- Page with download links to client version and IP changer +- Tutorial on how to connect to server + +#### Achievement system +- List of all achievements and character obtained achievements in their profile. + +#### Highscores +- Vocation & skill type filters + +#### Buy shop points / digital currency +- PayPal payment gateway +- PayGol (SMS) payment gateway +- PagSeguro payment gateway + +#### Shop system +- Items +- Premium days +- Change character gender +- Change character name +- Outfits +- Mounts +- Custom offer types. (basic Lua knowledge required) + +#### Forum +- Create custom discussion boards +- Level restriction to post +- Player outfit as avatars +- Player position +- Guildboards +- Feedback board where all threads are only visible for admins. +- Hide thread, close thread, stick thread +- Forum search + +#### Cache system +- Offload SQL load and CPU usage by loading treated data from a flatfile instead of raw SQL queries. + +#### Administration +- Delete character +- Ban character and/or account +- Change password of account +- Give character in-game position +- Give shop points to character +- Teleport a player or all players to home town, specific town or specific position. +- Edit level and skills of player +- View in-game bug reports and feedback on forum +- Overview of shop transactions and their status +- Moderate user submitted images to the gallery +- Create news with a feature rich text editor +- Add changelogs +- Load and update server and spells information +- Helpdesk + +### TODO List: +* Check [Milestones](https://github.com/Znote/ZnoteAAC/milestones) diff --git a/app/ZnoteAAC/achievements.php b/app/ZnoteAAC/achievements.php new file mode 100644 index 0000000..d74e637 --- /dev/null +++ b/app/ZnoteAAC/achievements.php @@ -0,0 +1,62 @@ + +

Achievements on

+
+ + + + + + + + + + + $achName) { + // Set defaults + if (!isset($achName['secret'])) $achName['secret'] = false; + if (!isset($achName['img'])) $achName['img'] = 'https://i.imgur.com/ZqWp1TE.png'; + + if (($achName['points'] >= 1) and ($achName['points'] <= 3) and (!$achName['img'])) { + echo ''; + + } elseif (($achName['points'] >= 4) and ($achName['points'] <= 6) and (!$achName['img'])) { + echo ''; + + } elseif (($achName['points'] >= 7) and ($achName['points'] <= 9) and (!$achName['img'])) { + echo ''; + + } elseif (($achName['points'] >= 10) and (!$achName['img'])) { + echo ''; + + } else { + echo ''; + } + echo ''; + echo ''; + if ($achName['secret'] == true) { + echo ''; + echo ''; + } else { + echo ''; + } + echo ''; +} +?> +
GradeNameDescriptionSecretPoints


' .$achName[0]. '' .$achName[1]. ''. $achName['points'] .''. $achName['points'] .'
+
+ + diff --git a/app/ZnoteAAC/admin.php b/app/ZnoteAAC/admin.php new file mode 100644 index 0000000..ac7fea2 --- /dev/null +++ b/app/ZnoteAAC/admin.php @@ -0,0 +1,348 @@ + $znote_account['points'], + 'New:' => $points, + 'Total:' => ($znote_account['points'] + $points) + ), + false, + "Points calculation:"); + $points += $znote_account['points']; + mysql_update("UPDATE `znote_accounts` SET `points`='$points' WHERE `account_id`='". $account['account_id'] ."';"); + } + + // Set character position + if (empty($_POST['position_name']) === false && empty($_POST['position_type']) === false) { + if (user_character_exist($_POST['position_name'])) { + if (array_key_exists($_POST['position_type'], $config['ingame_positions'])) { + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'TFS_10' || $config['ServerEngine'] == 'OTHIRE') { + set_ingame_position($_POST['position_name'], $_POST['position_type']); + } else if ($config['ServerEngine'] == 'TFS_03') { + set_ingame_position03($_POST['position_name'], $_POST['position_type']); + } + $pos = 'Undefined'; + foreach ($config['ingame_positions'] as $key=>$value) { + if ($key == $_POST['position_type']) { + $pos = $value; + } + } + $errors[] = 'Character '. hhb_tohtml(getValue($_POST['position_name'])) .' recieved the ingame position: '. hhb_tohtml($pos) .'.'; + } + } else { + $errors[] = 'Character '. hhb_tohtml(getValue($_POST['position_name'])) .' does not exist.'; + } + } + + // Teleport Player + if (isset($_POST['from']) && in_array($_POST['from'], ['all', 'only'])) { + $from = $_POST['from']; + if ($from === 'only') { + if (empty($_POST['player_name']) || !user_character_exist($_POST['player_name'])) { + $errors[] = 'Character '. hhb_tohtml(getValue($_POST['player_name'])) .' does not exist.'; + } + } + + if (!sizeof($errors)) { + $to = $_POST['to']; + $teleportQuery = 'UPDATE `players` SET '; + + if ($to == 'home') { + $teleportQuery .= '`posx` = 0, `posy` = 0, `posz` = 0 '; + } else if ($to == 'town') { + $teleportQuery .= '`posx` = 0, `posy` = 0, `posz` = 0, `town_id` = ' . (int) getValue($_POST['town']) . ' '; + } else if ($to == 'xyz') { + $teleportQuery .= '`posx` = ' . (int) getValue($_POST['x']) . ', `posy` = ' . (int) getValue($_POST['y']) . ', `posz` = ' . (int) getValue($_POST['z']) . ' '; + } + + if ($from === 'only') { + $teleportQuery .= ' WHERE `name` = \'' . getValue($_POST['player_name']). '\''; + } + + mysql_update($teleportQuery); + } + } +// If empty post +} + +// Display whatever output we figure out to add +if (empty($errors) === false){ + echo ''; + echo output_errors($errors); + echo ''; +} +// end +?> +

Admin Page.

+

+"; +echo "Last cached on: ". hhb_tohtml(getClock($basic['cached'], true)) .".
"; +?> +

+
    +
  • + Permanently delete/erase character from database: +
    + + +
    +
  • +
  • + Ban character and/or account: +
    + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + +
    + Ban reason: + +
    + Violation comment: (max 60 cols). + + +
    +
    +
  • +
  • + Reset password to the account of character name: +
    + + + + +
    +
  • +
  • + Set character name to position: + + ERROR: You forgot to add (Senior Tutor) rank in config.php! + +
    + + + + +
    +
  • +
  • + Give shop points to character: +
    + + + + +
    +
  • +
  • + Teleport Player +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Type: + +
    Player
    To + +
    Town + +
    Position + + + +
    +
    +
  • +
+
+ + 0) + $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24; + if (isset($duration['hour'])) { + if (($duration['hour'] - (int)$duration['hour']) > 0) + $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60; + if (isset($duration['minute'])) { + if (($duration['minute'] - (int)$duration['minute']) > 0) + $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60; + } + } + $tmp = array(); + foreach ($duration as $type => $value) { + if ($value >= 1) { + $pluralType = ((int)$value === 1) ? $type : $type . 's'; + if ($type !== 'second') $tmp[] = (int)$value . " $pluralType"; + else $tmp[] = (int)$value . " $pluralType"; + } + } + return implode(', ', $tmp); +} +// start + +// Passive check to see if bid period has expired and someone won a deal +$time = time(); +$expired_auctions = mysql_select_multi(" + SELECT `id` + FROM `znote_auction_player` + WHERE `sold` = 0 + AND `time_end` < {$time} + AND `bidder_account_id` > 0 +"); +//data_dump($expired_auctions, $this_account_id, "expired_auctions"); +if ($expired_auctions !== false) { + $soldIds = array(); + foreach ($expired_auctions as $a) { + $soldIds[] = $a['id']; + } + if (!empty($soldIds)) { + mysql_update(" + UPDATE `znote_auction_player` + SET `sold`=1 + WHERE `id` IN(".implode(',', $soldIds).") + LIMIT ".COUNT($soldIds)."; + "); + } +} +// end passive check +// Pending auctions +$pending = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`id` AS `player_id`, + `p`.`name`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`claimed` = 0 + AND `za`.`sold` = 1 + ORDER BY `za`.`time_end` desc +"); +// ongoing auctions +$ongoing = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`sold` = 0 + ORDER BY `za`.`time_end` desc; +"); +// Completed auctions +$completed = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`id` AS `player_id`, + `p`.`name`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `za`.`claimed` = 1 + ORDER BY `za`.`time_end` desc +"); +?> +

Character Auction History

+

Let players sell, buy and bid on characters. +
Creates a deeper shop economy, encourages players to spend more money in shop for points. +
Pay to win/progress mechanic, but also lets people who can barely afford points to gain it +
by leveling characters to sell. It can also discourages illegal/risky third-party account +
services. Since players can buy officially & support the server, dodgy competitors have to sell for cheaper. +
Without admin interference this is organic to each individual community economy inflation.

+ +

Pending orders to be claimed

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlayerLevelVocationPriceBid
Added:Ended:
+ + +

Ongoing auctions

+ + + + + + + + + + + + + + + + + + + + + + +
LevelVocationDetailsPriceBidAddedType
VIEW $character['time_end']) ? true : false; + echo getClock($character['time_begin'], true); + ?> + ('.toDuration(($character['time_end'] - time())).')'; ?>
+ + +

Completed auctions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlayerLevelVocationPriceBid
Added:Ended:
+ diff --git a/app/ZnoteAAC/admin_gallery.php b/app/ZnoteAAC/admin_gallery.php new file mode 100644 index 0000000..5d36eaa --- /dev/null +++ b/app/ZnoteAAC/admin_gallery.php @@ -0,0 +1,146 @@ +setContent($data); + $cache->save(); +} + +?>

Images in need of moderation:

+ + + + + + + + + + +

+ <?php echo $image['title']; ?> +
+ ", $descr); + ?> +

+
+ All good, no new images to moderate.'; + +?>

Public Images:

+ + + + + + + + + + +

+ <?php echo $image['title']; ?> +
+ ", $descr); + ?> +

+
+ There are currently no public images.'; + +?>

Deleted Images:

+ + + + + + + + + + +

+ + + +

+ <?php echo $image['title']; ?> +
+ ", $descr); + ?> +

+
+ There are currently no deleted images.'; +// end +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/admin_helpdesk.php b/app/ZnoteAAC/admin_helpdesk.php new file mode 100644 index 0000000..fc41a43 --- /dev/null +++ b/app/ZnoteAAC/admin_helpdesk.php @@ -0,0 +1,147 @@ + 0) ? (int)$_GET['view'] : false; +if ($view !== false){ + if (!empty($_POST['reply_text'])) { + sanitize($_POST['reply_text']); + + // Save ticket reply on database + $query = array( + 'tid' => $view, + 'username'=> getValue($_POST['username']), + 'message' => getValue($_POST['reply_text']), + 'created' => time(), + ); + $fields = '`'. implode('`, `', array_keys($query)) .'`'; + $data = '\''. implode('\', \'', $query) .'\''; + + mysql_insert("INSERT INTO `znote_tickets_replies` ($fields) VALUES ($data)"); + mysql_update("UPDATE `znote_tickets` SET `status`='Staff-Reply' WHERE `id`='$view' LIMIT 1;"); + + } else if (!empty($_POST['admin_ticket_close'])) { + $ticketId = (int) $_POST['admin_ticket_id']; + mysql_update("UPDATE `znote_tickets` SET `status` = 'CLOSED' WHERE `id` ='$ticketId' LIMIT 1;"); + + } else if (!empty($_POST['admin_ticket_open'])) { + $ticketId = (int) $_POST['admin_ticket_id']; + mysql_update("UPDATE `znote_tickets` SET `status` = 'Open' WHERE `id` ='$ticketId' LIMIT 1;"); + + } else if (!empty($_POST['admin_ticket_delete'])) { + $ticketId = (int) $_POST['admin_ticket_id']; + mysql_delete("DELETE FROM `znote_tickets` WHERE `id`='$ticketId' LIMIT 1;"); + header("Location: admin_helpdesk.php"); + } + + $ticketData = mysql_select_single("SELECT * FROM znote_tickets WHERE id='$view' LIMIT 1;"); + ?> +

View Ticket #

+ + + + + + + +
+ + - Created by: + +
+

+
+ + + + + + + + +
+ + - Posted by: + +
+

+
+ + + + + + + + +
+
+ + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+ + +

Latest Tickets

+ + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + ?> +
ID:Subject:Creation:Status:
'. $ticket['id'] .''. $ticket['subject'] .''. getClock($ticket['creation'], true) .''. $ticket['status'] .'
+ \ No newline at end of file diff --git a/app/ZnoteAAC/admin_news.php b/app/ZnoteAAC/admin_news.php new file mode 100644 index 0000000..0041002 --- /dev/null +++ b/app/ZnoteAAC/admin_news.php @@ -0,0 +1,152 @@ +News deleted!'; + mysql_delete("DELETE FROM `znote_news` WHERE `id`='$id';"); + $cache = new Cache('engine/cache/news'); + $news = fetchAllNews(); + $cache->setContent($news); + $cache->save(); + } + // Add news + if ($action === 'a') { + // fetch data + $char_array = user_character_list($user_data['id']); + ?> + + + +
+ + Select character: + [youtube]wK0w0x62PjA[/youtube]
+
+ +
+ + ERROR: NO GMs or Tutors on this account!"; + } + // Insert news + if ($action === 'i') { + echo 'News created successfully!'; + list($charid, $title, $text) = array((int)$_POST['selected_char'], mysql_znote_escape_string($_POST['title']), mysql_znote_escape_string($_POST['text'])); + $date = time(); + mysql_insert("INSERT INTO `znote_news` (`title`, `text`, `date`, `pid`) VALUES ('$title', '$text', '$date', '$charid');"); + // Reload the cache. + $cache = new Cache('engine/cache/news'); + $news = fetchAllNews(); + $cache->setContent($news); + $cache->save(); + } + // Save + if ($action === 's') { + echo 'News successfully updated!'; + list($title, $text) = array(mysql_znote_escape_string($_POST['title']), mysql_znote_escape_string($_POST['text'])); + mysql_update("UPDATE `znote_news` SET `title`='$title',`text`='$text' WHERE `id`='$id';"); + $cache = new Cache('engine/cache/news'); + $news = fetchAllNews(); + $cache->setContent($news); + $cache->save(); + } + // Edit + if ($action === 'e') { + $news = fetchAllNews(); + $edit = array(); + foreach ($news as $n) if ($n['id'] == $id) $edit = $n; + ?> + + +
+ +
+
+ +
+
+

+ [b]Bold Text[/b]
+ [size=5]Size 5 text[/size]
+ [img]Direct Image Link[/img]
+ [center]Cented Text[/center]
+ [link]https://youtube.com/[/link]
+ [link=https://youtube.com/]Click to View youtube[/link]
+ [color=GREEN]Green Text![/color]
+ [*]* Noted text [/*] +

+ +

News admin panel

+
+ + +
+ + + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + } + ?> +
DateByTitleEditDelete
'. getClock($n['date'], true) .''. $n['name'] .''. $n['title'] .''; + // edit + ?> +
+ + +
+ '; + echo '
'; + // delete + ?> +
+ + +
+ '; + echo '
+ diff --git a/app/ZnoteAAC/admin_reports.php b/app/ZnoteAAC/admin_reports.php new file mode 100644 index 0000000..0e6671c --- /dev/null +++ b/app/ZnoteAAC/admin_reports.php @@ -0,0 +1,243 @@ + 'Reported', + 1 => 'To-Do List', + 2 => 'Confirmed bug', + 3 => 'Invalid', + 4 => 'Rejected', + 5 => 'Fixed' +); +// Which status IDs should give option to add to changelog? +$statusChangeLog = array(0,5); + +// Autohide rows that have these status IDs: +$hideStatus = array(3, 4, 5); + +// Fetch data from SQL +$reportsData = mysql_select_multi('SELECT id, name, posx, posy, posz, report_description, date, status FROM znote_player_reports ORDER BY id DESC;'); +// If SQL data is not empty +if ($reportsData !== false) { + // Order reports array by ID for easy reference later on. + $reports = array(); + for ($i = 0; $i < count($reportsData); $i++) + foreach ($statusTypes as $key => $value) + if ($key == $reportsData[$i]['status']) + $reports[$key][$reportsData[$i]['id']] = $reportsData[$i]; +} + +// POST logic (Update report and give player points) +if (!empty($_POST)) { + // Fetch POST data + $playerName = getValue($_POST['playerName']); + $status = getValue($_POST['status']); + $price = getValue($_POST['price']); + $customPoints = getValue($_POST['customPoints']); + $reportId = getValue($_POST['id']); + + $changelogReportId = (int)$_POST['changelogReportId']; + $changelogValue = &$_POST['changelogValue']; + $changelogText = getValue($_POST['changelogText']); + $changelogStatus = ($changelogReportId !== false && $changelogValue === '2' && $changelogText !== false) ? true : false; + + if ($customPoints !== false) $price = (int)($price + $customPoints); + + // Update SQL + mysql_update("UPDATE `znote_player_reports` SET `status`='$status' WHERE `id`='$reportId' LIMIT 1;"); + echo "

Report status updated to ".$statusTypes[(int)$status] ."!

"; + // Update local array representation + foreach ($reports as $sid => $sa) + foreach ($sa as $rid => $ra) + if ($reportId == $rid) { + $reports[$status][$reportId] = $reports[$sid][$rid]; + $reports[$status][$reportId]['status'] = $status; + unset($reports[$sid][$rid]); + } + + // If we should do anything with changelog: + if ($changelogStatus) { + $time = time(); + // Check if changelog exist (`id`, `text`, `time`, `report_id`, `status`) + $changelog = mysql_select_single("SELECT * FROM `znote_changelog` WHERE `report_id`='$changelogReportId' LIMIT 1;"); + // If changelog exist + $updatechangelog = false; + if ($changelog !== false) { + // Update it + mysql_update("UPDATE `znote_changelog` SET `text`='$changelogText', `time`='$time' WHERE `id`='".$changelog['id']."' LIMIT 1;"); + echo "

Changelog message updated!

"; + $updatechangelog = true; + } else { + // Create it + mysql_insert("INSERT INTO `znote_changelog` (`text`, `time`, `report_id`, `status`) + VALUES ('$changelogText', '$time', '$changelogReportId', '$status');"); + echo "

Changelog message created!

"; + $updatechangelog = true; + } + if ($updatechangelog) { + // Cache changelog + $cache = new Cache('engine/cache/changelog'); + $cache->setContent(mysql_select_multi("SELECT `id`, `text`, `time`, `report_id`, `status` FROM `znote_changelog` ORDER BY `id` DESC;")); + $cache->save(); + } + + } + // If we should give user price + if ($price > 0) { + $account = mysql_select_single("SELECT `a`.`id`, `a`.`email` FROM `accounts` AS `a` + INNER JOIN `players` AS `p` ON `p`.`account_id` = `a`.`id` + WHERE `p`.`name` = '$playerName' LIMIT 1;"); + + if ($account !== false) { + // transaction log + mysql_insert("INSERT INTO `znote_paypal` VALUES ('', '$reportId', 'report@admin.".$user_data['name']." to ".$account['email']."', '".$account['id']."', '0', '".$price."')"); + // Process payment + $data = mysql_select_single("SELECT `points` AS `old_points` FROM `znote_accounts` WHERE `account_id`='".$account['id']."';"); + // Give points to user + $new_points = $data['old_points'] + $price; + mysql_update("UPDATE `znote_accounts` SET `points`='$new_points' WHERE `account_id`='".$account['id']."'"); + + // Remind GM that he sent points to character + echo "".$playerName." has been granted ".$price." points for his reports."; + } + } + +// GET logic (Edit report data and specify how many [if any] points to give to user) +} elseif (!empty($_GET)) { + // Fetch GET data + $action = getValue($_GET['action']); + $playerName = getValue($_GET['name']); + $reportId = getValue($_GET['id']); + + // Fetch the report we intend to modify + foreach ($reports as $sid => $sa) + foreach ($sa as $rid => $ra) + if ($rid == $reportId) + $report = $reports[$sid][$reportId]; + + // Create HTML form + ?> +
+
+ Player: + + +
Set status: +
+ Give user points: + +
+ +
+ + Add / update changelog message?
+ + +
+ +
+
+ +
+ $statusArray) { + ?> +

(Visible)

+ + + + + + + + $report) { + ?> + + + + + + +
InfoDescription
+ Report ID: # +
Name: +
Position: +
Reported: +
Status: . Edit +
+
+ No reports submitted."; +?> + + + diff --git a/app/ZnoteAAC/admin_shop.php b/app/ZnoteAAC/admin_shop.php new file mode 100644 index 0000000..f295146 --- /dev/null +++ b/app/ZnoteAAC/admin_shop.php @@ -0,0 +1,70 @@ + 'Item', 2 => 'Premium Days', 3 => 'Gender Change', 4 => 'Name Change', 5 => 'Outfits', 6 =>'Mounts'); +$items = getItemList(); +?> +

Shop Logs

+ +

Pending Orders

+

These are pending orders, like items bought, but not received or used yet.

+ + + + + + + + + + + + + + + + + + + + + +
IdAccountTypeItemCountDate
+ + 'Item', 2 => 'Premium Days', 3 => 'Gender Change', 4 => 'Name Change', 5 => 'Outfit', 6 =>'Mount', 7 =>'Custom'); +?> +

Order History

+

This list contains all transactions bought in the shop.

+ + + + + + + + + + + + + + + + + + + + + + + +
IdAccountTypeItemCountpointsDate
+ diff --git a/app/ZnoteAAC/admin_skills.php b/app/ZnoteAAC/admin_skills.php new file mode 100644 index 0000000..6e7785e --- /dev/null +++ b/app/ZnoteAAC/admin_skills.php @@ -0,0 +1,189 @@ + 0) { + $pid = (int)$_POST['pid']; + if ($config['ServerEngine'] != 'TFS_10') $status = user_is_online($pid); + else $status = user_is_online_10($pid); + + if (!$status) { + // New player level + $level = (int)$_POST['level']; + + // Fetch stat gain for vocation + $statgain = $config['vocations_gain'][(int)$_POST['vocation']]; + $playercnf = $config['player']; + + /* + if ((int)$_POST['vocation'] !== 0) { + // Fetch base level and stats: + $baselevel = $config['level']; + $basehealth = $config['health']; + $basemana = $config['mana']; + $basecap = $config['cap']; + } else { // No vocation stats + // Fetch base level and stats: + $baselevel = $config['nvlevel']; + $basehealth = $config['nvHealth']; + $basemana = $config['nvMana']; + $basecap = $config['nvCap']; + } + */ + + $LevelsFromBase = $level - $playercnf['base']['level']; + $newhp = $playercnf['base']['health'] + ($statgain['hp'] * $LevelsFromBase); + $newmp = $playercnf['base']['mana'] + ($statgain['mp'] * $LevelsFromBase); + $newcap = $playercnf['base']['cap'] + ($statgain['cap'] * $LevelsFromBase); + + // Calibrate hp/mana/cap + if ($config['ServerEngine'] != 'TFS_10') { +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['fist'] ."' WHERE `player_id`='$pid' AND `skillid`='0' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['club'] ."' WHERE `player_id`='$pid' AND `skillid`='1' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['sword'] ."' WHERE `player_id`='$pid' AND `skillid`='2' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['axe'] ."' WHERE `player_id`='$pid' AND `skillid`='3' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['dist'] ."' WHERE `player_id`='$pid' AND `skillid`='4' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['shield'] ."' WHERE `player_id`='$pid' AND `skillid`='5' LIMIT 1;"); +mysql_update("UPDATE `player_skills` SET `value`='". (int)$_POST['fish'] ."' WHERE `player_id`='$pid' AND `skillid`='6' LIMIT 1;"); +mysql_update("UPDATE `players` SET `maglevel`='". (int)$_POST['magic'] ."' WHERE `id`='$pid' LIMIT 1;"); +mysql_update("UPDATE `players` SET `vocation`='". (int)$_POST['vocation'] ."' WHERE `id`='$pid' LIMIT 1;"); +mysql_update("UPDATE `players` SET `level`='". $level ."' WHERE `id`='$pid' LIMIT 1;"); +mysql_update("UPDATE `players` SET `experience`='". level_to_experience($level) ."' WHERE `id`='$pid' LIMIT 1;"); +// Update HP/mana/cap accordingly to level & vocation +mysql_update("UPDATE `players` SET `health`='". $newhp ."', `healthmax`='". $newhp ."', `mana`='". $newmp ."', `manamax`='". $newmp ."', `cap`='". $newcap ."' WHERE `id`='$pid' LIMIT 1;"); + } else { + mysql_update("UPDATE `players` SET `health`='". $newhp ."', `healthmax`='". $newhp ."', `mana`='". $newmp ."', `manamax`='". $newmp ."', `cap`='". $newcap ."', `vocation`='". (int)$_POST['vocation'] ."', `skill_fist`='". (int)$_POST['fist'] ."', `skill_club`='". (int)$_POST['club'] ."', `skill_sword`='". (int)$_POST['sword'] ."', `skill_axe`='". (int)$_POST['axe'] ."', `skill_dist`='". (int)$_POST['dist'] ."', `skill_shielding`='". (int)$_POST['shield'] ."', `skill_fishing`='". (int)$_POST['fish'] ."', `maglevel`='". (int)$_POST['magic'] ."', `level`='". $level ."', `experience`='". level_to_experience($level) ."' WHERE `id`='$pid' LIMIT 1;"); + } +?> +

Player skills updated!

+ + Player must be offline! + $player['maglevel']); + $skills[] = array('value' => $player['level']); + $skills[] = array('value' => $player['vocation']); + } else { + $player = mysql_select_single("SELECT `skill_fist`, `skill_club`, `skill_sword`, `skill_axe`, `skill_dist`, `skill_shielding`, `skill_fishing`, `maglevel`, `level`, `vocation` FROM `players` WHERE `id`='$pid' LIMIT 1;"); + $skills = array( + 0 => array('value' => $player['skill_fist']), + 1 => array('value' => $player['skill_club']), + 2 => array('value' => $player['skill_sword']), + 3 => array('value' => $player['skill_axe']), + 4 => array('value' => $player['skill_dist']), + 5 => array('value' => $player['skill_shielding']), + 6 => array('value' => $player['skill_fishing']), + 7 => array('value' => $player['maglevel']), + 8 => array('value' => $player['level']), + 9 => array('value' => $player['vocation']) + ); + } + + //data_dump($skills, false, "Player skills"); + } else $name = false; +} + +?> +
"> + + + + + + + + + + + + +
Player skills administration
+ > +

+ Vocation:
+ +

+ Fist fighting:
+ value=""> +

+ Club fighting:
+ value=""> +

+ Sword fighting:
+ value=""> +

+ Axe fighting:
+ value=""> +

+
+ Dist fighting:
+ value=""> +

+ Shield fighting:
+ value=""> +

+ Fish fighting:
+ value=""> +

+ Level:
+ value=""> +

+ Magic level:
+ value=""> +

+
+ + + + + +
+ Reset fields / search new character +
+ diff --git a/app/ZnoteAAC/adminempty.php b/app/ZnoteAAC/adminempty.php new file mode 100644 index 0000000..7ec1b34 --- /dev/null +++ b/app/ZnoteAAC/adminempty.php @@ -0,0 +1,9 @@ + diff --git a/app/ZnoteAAC/api/api.php b/app/ZnoteAAC/api/api.php new file mode 100644 index 0000000..277463a --- /dev/null +++ b/app/ZnoteAAC/api/api.php @@ -0,0 +1,47 @@ + array( + 'znote' => $version, + 'ot' => $config['ServerEngine'] + ), +); + +if (isset($moduleVersion)) $response['version']['module'] = $moduleVersion; + +function UseClass($name = false, $module = false, $path = false) { + if ($name !== false) { + if (!is_array($name)) { + if (!$module) $module = $name; + if (!$path) require_once "modules/base/{$module}/class/{$name}.php"; + else require_once "{$path}/{$name}.php"; + } else { + foreach ($name as $class) { + if (!$module) $module = $class; + if (!$path) require_once "modules/base/{$module}/class/{$class}.php"; + else require_once "{$path}/{$class}.php"; + } + } + } else die('Error in function UseClass: class parameter is false.'); +} + +function SendResponse($response) { + global $config; + if ($config['api']['debug'] || isset($_GET['debug'])) data_dump($response, false, "Response (debug mode)"); + else echo json_encode($response); +} +?> \ No newline at end of file diff --git a/app/ZnoteAAC/api/index.php b/app/ZnoteAAC/api/index.php new file mode 100644 index 0000000..aded8f2 --- /dev/null +++ b/app/ZnoteAAC/api/index.php @@ -0,0 +1,55 @@ + 'test.php' +); + +$iterator = new DirectoryIterator($directory); +foreach($iterator as $entity) { + if($entity->isDot()) + continue; + $iterator = new DirectoryIterator($entity->getPathname()); + foreach($iterator as $entity) { + if($entity->isFile()) { + $file_extension = pathinfo($entity->getFilename(), PATHINFO_EXTENSION); + if ($file_extension == 'php') { + $path = explode('/', $entity->getPathname()); + if (count($path) === 1) $path = explode('\\', $entity->getPathname()); + $plugins[$path[1]] = $path[2]; + } + } + } +} + +$response['modules'] = $plugins; +$response['data']['title'] = $config['site_title']; +$response['data']['slogan'] = $config['site_title_context']; +$response['data']['time'] = getClock(time(), false, true); +$response['data']['time_formatted'] = getClock(time(), true, true); + +// Account count +$accounts = mysql_select_single("SELECT COUNT('id') AS `count` FROM `accounts`;"); +$response['data']['accounts'] = ($accounts !== false) ? (int)$accounts['count'] : 0; +// Player count +$players = mysql_select_single("SELECT COUNT('id') AS `count` FROM `players`;"); +$response['data']['players'] = ($players !== false) ? (int)$players['count'] : 0; +// online player count +if ($config['ServerEngine'] != 'TFS_10') { + $online = mysql_select_single("SELECT COUNT('id') AS `count`, COUNT(DISTINCT `lastip`) AS `unique` FROM `players` WHERE `online`='1';"); +} else { + $online = mysql_select_single("SELECT COUNT(`o`.`player_id`) AS `count`, COUNT(DISTINCT `p`.`lastip`) AS `unique` FROM `players_online` AS `o` INNER JOIN `players` AS `p` ON `o`.`player_id` = `p`.`id`;"); +} +$response['data']['online'] = ($online !== false) ? (int)$online['count'] : 0; +$response['data']['online_unique_ip'] = ($online !== false) ? (int)$online['unique'] : 0; +$response['data']['client'] = $config['client']; +$response['data']['port'] = $config['port']; +$response['data']['guildwar'] = $config['guildwar_enabled']; +$response['data']['forum'] = $config['forum']['enabled']; + +SendResponse($response); +?> \ No newline at end of file diff --git a/app/ZnoteAAC/api/module.php b/app/ZnoteAAC/api/module.php new file mode 100644 index 0000000..1713af8 --- /dev/null +++ b/app/ZnoteAAC/api/module.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/api/modules/base/player/class/player.php b/app/ZnoteAAC/api/modules/base/player/class/player.php new file mode 100644 index 0000000..2a44936 --- /dev/null +++ b/app/ZnoteAAC/api/modules/base/player/class/player.php @@ -0,0 +1,376 @@ + null, + 'name' => null, + 'world_id' => null, + 'group_id' => null, + 'account_id' => null, + 'level' => null, + 'vocation' => null, + 'health' => null, + 'healthmax' => null, + 'experience' => null, + 'lookbody' => null, + 'lookfeet' => null, + 'lookhead' => null, + 'looklegs' => null, + 'looktype' => null, + 'lookaddons' => null, + 'maglevel' => null, + 'mana' => null, + 'manamax' => null, + 'manaspent' => null, + 'soul' => null, + 'town_id' => null, + 'posx' => null, + 'posy' => null, + 'posz' => null, + 'conditions' => null, + 'cap' => null, + 'sex' => null, + 'lastlogin' => null, + 'lastip' => null, + 'save' => null, + 'skull' => null, + 'skulltime' => null, + 'rank_id' => null, + 'guildnick' => null, + 'lastlogout' => null, + 'blessings' => null, + 'balance' => null, + 'stamina' => null, + 'direction' => null, + 'loss_experience' => null, + 'loss_mana' => null, + 'loss_skills' => null, + 'loss_containers' => null, + 'loss_items' => null, + 'premend' => null, + 'online' => null, + 'marriage' => null, + 'promotion' => null, + 'deleted' => null, + 'description' => null, + 'onlinetime' => null, + 'deletion' => null, + 'offlinetraining_time' => null, + 'offlinetraining_skill' => null, + 'skill_fist' => null, + 'skill_fist_tries' => null, + 'skill_club' => null, + 'skill_club_tries' => null, + 'skill_sword' => null, + 'skill_sword_tries' => null, + 'skill_axe' => null, + 'skill_axe_tries' => null, + 'skill_dist' => null, + 'skill_dist_tries' => null, + 'skill_shielding' => null, + 'skill_shielding_tries' => null, + 'skill_fishing' => null, + 'skill_fishing_tries' => null, + ); + protected $_znotedata = array( + 'comment' => null, + 'created' => null, + 'hide_char' => null, + ); + protected $_name_id = false; + protected $_querylog = array(); + protected $_errors = array(); + + public function __construct($name_id_array, $fields = false, $query = true) { + + if (!is_array($name_id_array)) $this->_name_id = $name_id_array; + + if ($name_id_array !== false) { + // Fetch player by name or id + if (is_string($name_id_array) || is_integer($name_id_array)) { + if ($query) { + $this->update($this->mysql_select($name_id_array, $fields)); + } + } + + // Load these player data. + if (is_array($name_id_array)) { + if (isset($name_id_array['id'])) $this->_name_id = $name_id_array['id']; + elseif (isset($name_id_array['name'])) $this->_name_id = $name_id_array['name']; + + $this->update($name_id_array); + } + } else die("Player construct takes arguments: string or id for fetch, array for load."); + } + + /** + * Return all player data, or the fields specified in param $fields. + * + * @param array $fields + * @access public + * @return mixed (array 'field' => 'value', or false (bool)) + **/ + public function fetch($fields = false) { + if (is_string($fields)) $fields = array($fields); + // Return all data that is not null. + if (!$fields) { + $returndata = array(); + foreach ($this->_playerdata as $field => $value) { + if (!is_null($value)) $returndata[$field] = $value; + } + foreach ($this->_znotedata as $field => $value) { + if (!is_null($value)) $returndata[$field] = $value; + } + return $returndata; + + } else { + // The return array + $returndata = array(); + + // Array containing null fields, we need to fetch these from db later on. + $missingValues = array(); + + // Populate the two above arrays + foreach ($fields as $field) { + + if (array_key_exists($field, $this->_playerdata)) { + if (is_null($this->_playerdata[$field])) $missingValues[] = $field; + else $returndata[$field] = $this->_playerdata[$field]; + + } elseif (array_key_exists($field, $this->_znotedata)) { + if (is_null($this->_znotedata[$field])) $missingValues[] = $field; + else $returndata[$field] = $this->_znotedata[$field]; + } + } + + // See if we are missing any values + if (!empty($missingValues)) { + // Query for this data + $data = $this->mysql_select($this->_name_id, $missingValues); + // Update this object + $this->update($data); + foreach ($data as $field => $value) { + $returndata[$field] = $value; + } + } + return $returndata; + } + return false; + } + + /** + * Update player data. + * + * @param array $fields + * @access public + * @return mixed (array, boolean) + **/ + public function update($data) { + if (is_array($data) && !empty($data)) { + foreach ($data as $field => $value) { + + if (array_key_exists($field, $this->_playerdata)) { + $this->_playerdata[$field] = $value; + + } elseif (array_key_exists($field, $this->_znotedata)) { + $this->_znotedata[$field] = $value; + } + } + return true; + } + return false; + } + + public function getErrors() { + return (!empty($this->_errors)) ? $this->_errors : false; + } + public function dumpErrors() { + if ($this->getErrors() !== false) + data_dump($this->getErrors(), false, "Errors detected in player class:"); + } + + /** + * Select player data from mysql. + * + * @param mixed (int, string) $name_id, array $fields + * @access private + * @return mixed (array, boolean) + **/ + private function mysql_select($name_id, $fields = false) { + $table = 'players'; + $znote_table = 'znote_players'; + $znote_fields = array(); + + // Dynamic fields logic + switch (gettype($fields)) { + case 'boolean': + $field_elements = '*'; + $znote_fields = array('comment', 'created', 'hide_char'); + break; + + case 'string': + $fields = array($fields); + + case 'array': + // Get rid of fields related to znote_ + foreach ($fields as $key => $field) { + if (!array_key_exists($field, $this->_playerdata)) { + $znote_fields[] = $field; + unset($fields[$key]); + } + } + + //Since we use for loop later, we need to reindex the array if we unset something. + if (!empty($znote_fields)) $fields = array_values($fields); + + // Add 'id' field if its not already there. + if (!in_array('id', $fields)) $fields[] = 'id'; + + // Loop through every field and generate the sql string + for ($i = 0; $i < count($fields); $i++) { + if ($i === 0) $field_elements = "`". getValue($fields[$i]) ."`"; + else $field_elements .= ", `". getValue($fields[$i]) ."`"; + } + break; + } + + // Value logic + if (is_integer($name_id)) { + $name_id = (int)$name_id; + $where = "`id` = '{$name_id}'"; + } else { + $name_id = getValue($name_id); + $where = "`name` = '{$name_id}'"; + } + + $query = "SELECT {$field_elements} FROM `{$table}` WHERE {$where} LIMIT 1;"; + + // Log query to player object + $this->_querylog[] = $query; + // Fetch from players table + $data = mysql_select_single($query); + if (isset($data['conditions'])) unset($data['conditions']); + + // Fetch from znote_players table if neccesary + if (!empty($znote_fields)) { + // Loop through every field and generate the sql string + for ($i = 0; $i < count($znote_fields); $i++) { + if ($i === 0) $field_elements = "`". getValue($znote_fields[$i]) ."`"; + else $field_elements .= ", `". getValue($znote_fields[$i]) ."`"; + } + + $query = "SELECT {$field_elements} FROM `{$znote_table}` WHERE `player_id`='".$data['id']."' LIMIT 1;"; + $this->_querylog[] = $query; + $zdata = mysql_select_single($query); + foreach ($zdata as $field => $value) $data[$field] = $value; + } + return $data; + } + + /** + * Create player. + * + * @param none + * @access public + * @return bool $status + **/ + public function create() { + // If player already have an id, the player already exist. + if (is_null($this->_playerdata['id']) && is_string($this->_playerdata['name'])) { + + // Confirm player does not exist + $name = format_character_name($this->_playerdata['name']); + $name = validate_name($name); + $name = sanitize($name); + $exist = mysql_select_single("SELECT `id` FROM `players` WHERE `name`='{$name}' LIMIT 1;"); + if ($exist !== false) { + $this->errors[] = "A player with the name [{$name}] already exist."; + return false; + } + $config = fullConfig(); + + if (user_character_exist($_POST['name']) !== false) { + $errors[] = 'Sorry, that character name already exist.'; + } + if (!preg_match("/^[a-zA-Z_ ]+$/", $_POST['name'])) { + $errors[] = 'Your name may only contain a-z, A-Z and spaces.'; + } + if (strlen($_POST['name']) < $config['minL'] || strlen($_POST['name']) > $config['maxL']) { + $errors[] = 'Your character name must be between ' . $config['minL'] . ' - ' . $config['maxL'] . ' characters long.'; + } + // name restriction + $resname = explode(" ", $_POST['name']); + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } + else if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + // Validate vocation id + if (!in_array((int)$_POST['selected_vocation'], $config['available_vocations'])) { + $errors[] = 'Permission Denied. Wrong vocation.'; + } + // Validate town id + if (!in_array((int)$_POST['selected_town'], $config['available_towns'])) { + $errors[] = 'Permission Denied. Wrong town.'; + } + // Validate gender id + if (!in_array((int)$_POST['selected_gender'], array(0, 1))) { + $errors[] = 'Permission Denied. Wrong gender.'; + } + if (vocation_id_to_name($_POST['selected_vocation']) === false) { + $errors[] = 'Failed to recognize that vocation, does it exist?'; + } + if (town_id_to_name($_POST['selected_town']) === false) { + $errors[] = 'Failed to recognize that town, does it exist?'; + } + if (gender_exist($_POST['selected_gender']) === false) { + $errors[] = 'Failed to recognize that gender, does it exist?'; + } + // Char count + $char_count = user_character_list_count($session_user_id); + if ($char_count >= $config['max_characters']) { + $errors[] = 'Your account is not allowed to have more than '. $config['max_characters'] .' characters.'; + } + if (validate_ip(getIP()) === false && $config['validate_IP'] === true) { + $errors[] = 'Failed to recognize your IP address. (Not a valid IPv4 address).'; + } + + echo "create player"; + // Make sure all neccesary values are set + //Register + $character_data = array( + 'name' => format_character_name($_POST['name']), + 'account_id'=> $session_user_id, + 'vocation' => $_POST['selected_vocation'], + 'town_id' => $_POST['selected_town'], + 'sex' => $_POST['selected_gender'], + 'lastip' => getIPLong(), + 'created' => time() + ); + + array_walk($character_data, 'array_sanitize'); + $cnf = fullConfig(); + + if ($character_data['sex'] == 1) { + $outfit_type = $cnf['maleOutfitId']; + } else { + $outfit_type = $cnf['femaleOutfitId']; + } + // Create the player + + } else { + echo "Player already exist."; + return false; + } + } +} + +/* +$this->_file = $file . self::EXT; +$this->setExpiration(config('cache_lifespan')); +$this->_lifespan = $span; +*/ diff --git a/app/ZnoteAAC/api/modules/base/player/test.php b/app/ZnoteAAC/api/modules/base/player/test.php new file mode 100644 index 0000000..49c9517 --- /dev/null +++ b/app/ZnoteAAC/api/modules/base/player/test.php @@ -0,0 +1,13 @@ +fetch('name'); +$response['test'] = $player->fetch('level'); + + +SendResponse($response); +?> \ No newline at end of file diff --git a/app/ZnoteAAC/api/modules/highscores/topExperience.php b/app/ZnoteAAC/api/modules/highscores/topExperience.php new file mode 100644 index 0000000..792e037 --- /dev/null +++ b/app/ZnoteAAC/api/modules/highscores/topExperience.php @@ -0,0 +1,21 @@ + 0) ? (int)getValue($_GET['rows']) : 10; + +// Show which configuration is used +$response['config']['rows'] = $rows; + +// Fetch top 10 players +$players = mysql_select_multi("SELECT `p`.`name`, `p`.`level`, `p`.`experience`, `p`.`vocation`, `p`.`lastlogin`, `z`.`created` FROM `players` AS `p` INNER JOIN `znote_players` AS `z` ON `p`.`id` = `z`.`player_id` WHERE `p`.`group_id`<'2' ORDER BY `p`.`experience` DESC LIMIT $rows;"); +for ($i = 0; $i < count($players); $i++) { + $players[$i]['vocation_name'] = $config['vocations'][$players[$i]['vocation']]; +} +$response['data']['players'] = $players; + + +SendResponse($response); +?> \ No newline at end of file diff --git a/app/ZnoteAAC/api/modules/samples/blank.php b/app/ZnoteAAC/api/modules/samples/blank.php new file mode 100644 index 0000000..112f5ef --- /dev/null +++ b/app/ZnoteAAC/api/modules/samples/blank.php @@ -0,0 +1,44 @@ +hasExpired()) { + $players = mysql_select_multi("SELECT `name`, `level`, `experience` FROM `players` ORDER BY `experience` DESC LIMIT 5;"); + + $cache->setContent($players); + $cache->save(); + } else { + $players = $cache->load(); + } + + -Functions found in general.php + :When fetching GET or POST from parameters, ALWAYS use getValue($value) + :Etc if you want to fetch character name from url, do it like this: + $playername = getValue($_GET['name']); + if ($playername !== false) { + // $playername either contains player name, or false if failed to fetch name from GET. + } + :getValue is often used in 3 ways: Fetch GET and POST values, or sanitize/secure any value you wish. + :Check ZnoteAAC\engine\function\general.php for full list of available functions. +*/ + +// Save the results of previous logic to the response +$response['data']['title'] = "The fabulous blank page!"; + +// Send the response through JSON API +SendResponse($response); +?> diff --git a/app/ZnoteAAC/api/modules/towns/getTownNames.php b/app/ZnoteAAC/api/modules/towns/getTownNames.php new file mode 100644 index 0000000..186e620 --- /dev/null +++ b/app/ZnoteAAC/api/modules/towns/getTownNames.php @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/auctionChar.php b/app/ZnoteAAC/auctionChar.php new file mode 100644 index 0000000..b6213aa --- /dev/null +++ b/app/ZnoteAAC/auctionChar.php @@ -0,0 +1,958 @@ + 0) + $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24; + if (isset($duration['hour'])) { + if (($duration['hour'] - (int)$duration['hour']) > 0) + $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60; + if (isset($duration['minute'])) { + if (($duration['minute'] - (int)$duration['minute']) > 0) + $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60; + } + } + $tmp = array(); + foreach ($duration as $type => $value) { + if ($value >= 1) { + $pluralType = ((int)$value === 1) ? $type : $type . 's'; + if ($type !== 'second') $tmp[] = (int)$value . " $pluralType"; + else $tmp[] = (int)$value . " $pluralType"; + } + } + return implode(', ', $tmp); +} +?> +

Character auction

+Character shop auction system is currently only available for ServerEngine TFS_10.

"; + include 'layout/overall/footer.php'; + die(); + } + if ((int)$auction['storage_account_id'] === (int)$this_account_id) { + echo "

The storage account cannot use the character auction.

"; + include 'layout/overall/footer.php'; + die(); + } + $step = $auction['step']; + $step_duration = $auction['step_duration']; + $actions = array( + 'list', // list all available players in auction + 'view', // view a specific player + 'create', // select which character to add and initial price + 'add', // add character to list + 'bid', // Bid or buy a specific player + 'refund', // Refund a player you added back to your account + 'claim' // Claim a character you won through purchase or bid + ); + + // Default action is list, but $_GET or $_POST will override it. + $action = 'list'; + // Load selected string from actions array based on input, strict whitelist validation + if (isset( $_GET['action']) && in_array( $_GET['action'], $actions)) { + $action = $actions[array_search( $_GET['action'], $actions, true)]; + } + if (isset($_POST['action']) && in_array($_POST['action'], $actions)) { + $action = $actions[array_search($_POST['action'], $actions, true)]; + } + + // Passive check to see if bid period has expired and someone won a deal + $time = time(); + $expired_auctions = mysql_select_multi(" + SELECT + `id`, + `original_account_id`, + (`bid`+`deposit`) as `points` + FROM `znote_auction_player` + WHERE `sold` = 0 + AND `time_end` < {$time} + AND `bidder_account_id` > 0 + "); + //data_dump($expired_auctions, $this_account_id, "expired_auctions"); + if ($expired_auctions !== false) { + $soldIds = array(); + foreach ($expired_auctions as $a) { + $soldIds[] = $a['id']; + } + if (!empty($soldIds)) { + mysql_update(" + UPDATE `znote_auction_player` + SET `sold` = 1 + WHERE `id` IN(".implode(',', $soldIds).") + LIMIT ".COUNT($soldIds)."; + "); + // Transfer points to seller account + foreach ($expired_auctions as $a) { + mysql_update(" + UPDATE `znote_accounts` + SET `points` = (`points`+{$a['points']}) + WHERE `account_id` = {$a['original_account_id']}; + "); + } + } + } + // end passive check + + // If we bid or buy a character + // silently continues to list if buy, back to view if bid + if ($action === 'bid') { + //data_dump($_POST, false, "Bid or buying:"); + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + $price = (isset($_POST['price']) && (int)$_POST['price'] > 0) ? (int)$_POST['price'] : false; + + $action = 'list'; + if ($zaid !== false && $price !== false) { + // The account of the buyer, if he can afford what he is trying to pay + $account = mysql_select_single(" + SELECT + `a`.`id`, + `za`.`points` + FROM `accounts` a + INNER JOIN `znote_accounts` za + ON `a`.`id` = `za`.`account_id` + WHERE `a`.`id`= {$this_account_id} + AND `za`.`points` >= {$price} + LIMIT 1; + "); + //data_dump($account, false, "Buyer account:"); + + // The character to buy, presuming it isn't sold, buyer isn't the owner, buyer can afford it + if ($account !== false) { + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `za`.`original_account_id`, + `za`.`bidder_account_id`, + `za`.`time_begin`, + `za`.`time_end`, + `za`.`price`, + `za`.`bid`, + `za`.`deposit`, + `za`.`sold` + FROM `znote_auction_player` za + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 0 + AND `za`.`original_account_id` != {$this_account_id} + AND `za`.`price` <= {$price} + AND `za`.`bid`+{$step} <= {$price} + LIMIT 1 + "); + //data_dump($character, false, "Character to buy:"); + + if ($character !== false) { + // If auction already have a previous bidder, refund him his points + if ($character['bid'] > 0 && $character['bidder_account_id'] > 0) { + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`+{$character['bid']} + WHERE `account_id` = {$character['bidder_account_id']} + LIMIT 1; + "); + // If previous bidder is not you, increase bidding period by 1 hour + // (Extending bid war to give bidding competitor a chance to retaliate) + if ((int)$character['bidder_account_id'] !== (int)$account['id']) { + mysql_update(" + UPDATE `znote_auction_player` + SET `time_end` = `time_end`+{$step_duration} + WHERE `id` = {$character['zaid']} + LIMIT 1; + "); + } + } + // Remove points from buyer + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`-{$price} + WHERE `account_id` = {$account['id']} + LIMIT 1; + "); + // Update auction, and set new bidder data + $time = time(); + mysql_update(" + UPDATE `znote_auction_player` + SET + `bidder_account_id` = {$account['id']}, + `bid` = {$price}, + `sold` = CASE WHEN {$time} >= `time_end` THEN 1 ELSE 0 END + WHERE `id` = {$character['zaid']} + LIMIT 1; + "); + // If character is sold, give points to seller + if (time() >= $character['time_end']) { + mysql_update(" + UPDATE `znote_accounts` + SET `points` = (`points`+{$character['deposit']}+{$price}) + WHERE `account_id` = {$character['original_account_id']} + LIMIT 1; + "); + } else { + // If character is not sold, this is a bidding war, we want to send user back to view. + $action = 'view'; + } + // Note: Transferring character to the new account etc happens later in $action = 'claim' + } + } + } + } + + // See a specific character in auction, + // silently fallback to list if he doesn't exist or is already sold + if ($action === 'view') { // View a character in the auction + if (!isset($zaid)) { + $zaid = (isset($_GET['zaid']) && (int)$_GET['zaid'] > 0) ? (int)$_GET['zaid'] : false; + } + if ($zaid !== false) { + // Retrieve basic character information + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `za`.`original_account_id`, + `za`.`bidder_account_id`, + `za`.`time_begin`, + `za`.`time_end`, + CASE WHEN `za`.`price` > `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid`+{$step} + END AS `price`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN 1 + ELSE 0 + END AS `own`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `p`.`name` + ELSE '' + END AS `name`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `za`.`bid` + ELSE 0 + END AS `bid`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `za`.`deposit` + ELSE 0 + END AS `deposit`, + `p`.`vocation`, + `p`.`level`, + `p`.`balance`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons`, + `p`.`maglevel` AS `magic`, + `p`.`skill_fist` AS `fist`, + `p`.`skill_club` AS `club`, + `p`.`skill_sword` AS `sword`, + `p`.`skill_axe` AS `axe`, + `p`.`skill_dist` AS `dist`, + `p`.`skill_shielding` AS `shielding`, + `p`.`skill_fishing` AS `fishing` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 0 + LIMIT 1; + "); + //data_dump($character, false, "Character info"); + + if (is_array($character) && !empty($character)) { + // If the end of the bid is in the future, the bid is currently ongoing + $bidding_period = ((int)$character['time_end']+1 > time()) ? true : false; + $player_items = mysql_select_multi(" + SELECT `itemtype`, SUM(`count`) AS `count` + FROM `player_items` + WHERE `player_id` = {$character['player_id']} + GROUP BY `itemtype` + ORDER BY MIN(`pid`) ASC + "); + $depot_items = mysql_select_multi(" + SELECT `itemtype`, SUM(`count`) AS `count` + FROM `player_depotitems` + WHERE `player_id` = {$character['player_id']} + GROUP BY `itemtype` + ORDER BY MIN(`pid`) ASC + "); + $account = mysql_select_single(" + SELECT `points` + FROM `znote_accounts` + WHERE `account_id` = {$this_account_id} + AND `points` >= {$character['price']} + LIMIT 1; + "); + ?> +

Detailed character information. Go back to list.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
LevelVocationImageBankPrice
+ img + points
+

Remaining bid period: .

+
+ + +

You have shop points remaining.

+ + +

So far so good! +
You currently have the highest bid at: +

+

If nobody bids higher than you, this character will be yours in: +
. +

+ +
+ + + > + + + + +
+ + +

So far so good! +
You currently have the highest bid at: +

+

If nobody bids higher than you, this character will be yours in: +
. +

+ +

You cannot afford to buy this character.

+ + +

You are the seller of this character. +
Name: +
Price: +
Bid: +
Deposit: + +

The bidding period has ended, you can wait until someone decides to instantly buy it, or you can reclaim your character to your account.

+
+ + + +
+ +

The bidding period will last for . After this period, you can reclaim your character if nobody has bid on it.

+ +

+ + + + + + + + + + + + +
Character skills:
magic
fist
club
sword
axe
dist
shielding
fishing
+ + + + + + + + + + + + + + + + + + + +
Player items:
ImageItemCount
" alt="Item Image">
+ + + + + + + + + + + + + + + + + + + +
Depot items:
ImageItemCount
" alt="Item Image">
+ 0) ? (int)$_POST['pid'] : false; + $cost = (isset($_POST['cost']) && (int)$_POST['cost'] > 0) ? (int)$_POST['cost'] : false; + $deposit = (int)$cost * ($auction['deposit'] / 100); + $password = SHA1($_POST['password']); + + // Verify values + $status = false; + $account = false; + if ($pid > 0 && $cost >= $auction['lowestPrice']) { + $account = mysql_select_single(" + SELECT `a`.`id`, `a`.`password`, `za`.`points` + FROM `accounts` a + INNER JOIN `znote_accounts` za + ON `a`.`id` = `za`.`account_id` + WHERE `a`.`id`= {$this_account_id} + AND `a`.`password`='{$password}' + AND `za`.`points` >= {$deposit} + LIMIT 1 + ;"); + if (isset($account['password']) && $account['password'] === $password) { + // Check if player exist, is offline and not already in auction + // And is not a tutor or a GM+. + $player = mysql_select_single(" + SELECT `p`.`id`, `p`.`name`, + CASE + WHEN `po`.`player_id` IS NULL + THEN 0 + ELSE 1 + END AS `online`, + CASE + WHEN `za`.`player_id` IS NULL + THEN 0 + ELSE 1 + END AS `alreadyInAuction` + FROM `players` p + LEFT JOIN `players_online` po + ON `p`.`id` = `po`.`player_id` + LEFT JOIN `znote_auction_player` za + ON `p`.`id` = `za`.`player_id` + AND `p`.`account_id` = `za`.`original_account_id` + AND `za`.`claimed` = 0 + WHERE `p`.`id` = {$pid} + AND `p`.`account_id` = {$this_account_id} + AND `p`.`group_id` = 1 + LIMIT 1 + ;"); + // Verify storage account ID exist + $storage_account = mysql_select_single(" + SELECT `id` + FROM `accounts` + WHERE `id`={$auction['storage_account_id']} + LIMIT 1; + "); + if ($storage_account === false) { + data_dump($auction, false, "Configured storage_account_id in config.php does not exist!"); + } else { + if (isset($player['online']) && $player['online'] == 0) { + if (isset($player['alreadyInAuction']) && $player['alreadyInAuction'] == 0) { + $status = true; + } + } + } + } + } + if ($status) { + $time_begin = time(); + $time_end = $time_begin + ($auction['biddingDuration']); + // Insert row to znote_auction_player + mysql_insert(" + INSERT INTO `znote_auction_player` ( + `player_id`, + `original_account_id`, + `bidder_account_id`, + `time_begin`, + `time_end`, + `price`, + `bid`, + `deposit`, + `sold`, + `claimed` + ) VALUES ( + {$pid}, + {$this_account_id}, + 0, + {$time_begin}, + {$time_end}, + {$cost}, + 0, + {$deposit}, + 0, + 0 + ); + "); + // Move player to storage account + mysql_update(" + UPDATE `players` + SET `account_id` = {$auction['storage_account_id']} + WHERE `id` = {$pid} + LIMIT 1; + "); + // Hide character from public character list (in pidprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 1 + WHERE `player_id` = {$pid} + LIMIT 1; + "); + // Remove deposit from account + $afterDeposit = $account['points'] - $deposit; + mysql_update(" + UPDATE `znote_accounts` + SET `points` = {$afterDeposit} + WHERE `account_id` = {$account['id']} + LIMIT 1; + "); + } + $action = 'list'; + } + + // If we are refunding a player back to its original owner + // silently continues to list + if ($action === 'refund') { + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + //data_dump($_POST, false, "POST"); + if ($zaid !== false) { + $time = time(); + // If original account is the one trying to get it back, + // and bidding period is over, + // and its not labeled as sold + // and nobody has bid on it + $character = mysql_select_single(" + SELECT `player_id` + FROM `znote_auction_player` + WHERE `id`= {$zaid} + AND `original_account_id` = {$this_account_id} + AND `time_end` <= {$time} + AND `bidder_account_id` = 0 + AND `bid` = 0 + AND `sold` = 0 + LIMIT 1 + "); + //data_dump($character, false, "Character"); + if ($character !== false) { + // Move character to buyer account and give it a new name + mysql_update(" + UPDATE `players` + SET `account_id` = {$this_account_id} + WHERE `id` = {$character['player_id']} + LIMIT 1; + "); + // Set label to sold + mysql_update(" + UPDATE `znote_auction_player` + SET `sold` = 1 + WHERE `id`= {$zaid} + LIMIT 1; + "); + // Show character in public character list (in characterprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 0 + WHERE `player_id` = {$character['player_id']} + LIMIT 1; + "); + } + } + $action = 'list'; + } + + // If we are claiming a character + // If validation fails then explain why, but then head over to list regardless of status + if ($action === 'claim') { + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + $name = (isset($_POST['name']) && !empty($_POST['name'])) ? getValue($_POST['name']) : false; + $errors = array(); + //data_dump($_POST, $name, "Post data:"); + if ($zaid === false) { + $errors[] = 'We are unable to find this auction order.'; + } + if ((int)$auction['storage_account_id'] === $this_account_id) { + $errors[] = 'Silly you! You cannot claim characters with the storage account configured in
$config[\'shop_auction\'][\'storage_account_id\']
because you already have those characters in your account! :P'; + if ($is_admin) { + $errors[] = "ADMIN: The storage account in config.php should not be the same as the admin account."; + } + } + if ($name === false) { + $errors[] = 'Please give the character a name.'; + } else { + // begin name validation + $name = validate_name($name); + if (user_character_exist($name) !== false) { + $errors[] = 'Sorry, that character name already exist.'; + } + if (!preg_match("/^[a-zA-Z_ ]+$/", $name)) { + $errors[] = 'Your name may only contain a-z, A-Z and spaces.'; + } + if (strlen($name) < $config['minL'] || strlen($name) > $config['maxL']) { + $errors[] = 'Your character name must be between ' . $config['minL'] . ' - ' . $config['maxL'] . ' characters long.'; + } + // name restriction + $resname = explode(" ", $name); + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } + else if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + $name = format_character_name($name); + // end name validation + if (empty($errors)) { + // Make sure you have access to claim this zaid character. + // And that you haven't already claimed it. + // And that the character isn't online... + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `p`.`account_id` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + LEFT JOIN `players_online` po + ON `p`.`id` = `po`.`player_id` + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 1 + AND `p`.`account_id` != {$this_account_id} + AND `za`.`bidder_account_id` = {$this_account_id} + AND `po`.`player_id` IS NULL + "); + //data_dump($character, false, "Character"); + if ($character !== false) { + // Set character to claimed + mysql_update(" + UPDATE `znote_auction_player` + SET `claimed`='1' + WHERE `id` = {$character['zaid']} + "); + // Move character to buyer account and give it a new name + mysql_update(" + UPDATE `players` + SET `name` = '{$name}', + `account_id` = {$this_account_id} + WHERE `id` = {$character['player_id']} + LIMIT 1; + "); + // Show character in public character list (in characterprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 0 + WHERE `player_id` = {$character['player_id']} + LIMIT 1; + "); + // Remove character from other players VIP lists + mysql_delete(" + DELETE FROM `account_viplist` + WHERE `player_id` = {$character['player_id']} + "); + // Remove the character deathlist + mysql_delete(" + DELETE FROM `player_deaths` + WHERE `player_id` = {$character['player_id']} + "); + } else { + $errors[] = "You either don't have access to claim this character, or you have already claimed it, or this character isn't sold yet, or we were unable to find this auction order."; + if ($is_admin) { + $errors[] = "ADMIN: ... Or character is online."; + } + } + } + } + if (!empty($errors)) { + //data_dump($errors, false, "Errors:"); + ?> + + + + + + $error): ?> + + + + + +
#Issues occurred while claiming your name
+ `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid` + END AS `price`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`claimed` = 0 + AND `za`.`sold` = 1 + AND `za`.`bidder_account_id` = {$this_account_id} + ORDER BY `p`.`level` desc + "); + //data_dump($pending, false, "Pending characters:"); + if ($pending !== false) { + ?> +

Congratulations!

+

You have 1) ? 'characters' : 'a character'; ?> ready to claim!

+ + + + + + + + + + + + + + + + + + + + +
LevelVocationDetailsPrice
VIEW
+ img + +

Hello master, what should my new name be?

+
+ + + + +
+
+ +

Ongoing auctions:

+ `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid`+{$step} + END AS `price`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`sold` = 0 + ORDER BY `p`.`level` desc; + "); + //data_dump($characters, false, "List characters"); + if ($is_admin) { + ?> +

Admin: Character auction history

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
LevelVocationImageDetailsPriceAddedType
+ img + VIEW $character['time_end']) ? true : false; + echo getClock($character['time_begin'], true); + ?> + ('.toDuration(($character['time_end'] - time())).')'; ?>
+ +

Add a character to the auction.

+ = {$auction['lowestLevel']} + AND `a`.`points` >= $minToCreate + ;"); + //data_dump($own_characters, false, "own_chars"); + + if (is_array($own_characters) && !empty($own_characters)) { + $max = ($own_characters[0]['points'] / $auction['deposit']) * 100; + ?> +

Go back to list.

+
+ +

Character: (Must be offline)

+ +

Shop points: +
Your current points: +
Minimum: +
deposit: % +
Your maximum: +

+

Deposit information: +
To ensure you as the seller is a legitimate account, and to encourage fair prices you have to temporarily invest % of the selling price as a deposit. +

+

Once the auction has completed, the deposit fee will be refunded back to your account.

+

If you wish to reclaim your character, you can do it after the bidding period if nobody has placed an offer on it. But if you do this you will not get the deposit back. It is therefore advisable that you create a good and appealing offer to our community.

+

Sell price:

+ +
+

Verify with your password:

+ +
+ +
+ +

Go back to list.

+

Your account does not follow the required rules to sell characters. +
1. Minimum level: +
2. Minimum already earned shop points: +
3. Eligible characters must be offline. +

+ Character shop auctioning system is disabled.

"; +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/blank.php b/app/ZnoteAAC/blank.php new file mode 100644 index 0000000..5ef6229 --- /dev/null +++ b/app/ZnoteAAC/blank.php @@ -0,0 +1,6 @@ + + +

Blank

+

This is a blank sample page.

+ + \ No newline at end of file diff --git a/app/ZnoteAAC/buypoints.php b/app/ZnoteAAC/buypoints.php new file mode 100644 index 0000000..89ff6f7 --- /dev/null +++ b/app/ZnoteAAC/buypoints.php @@ -0,0 +1,97 @@ + + +

Buy Points

+

Buy points using Paypal:

+ + + + + + + + + + $points) { + echo ''; + echo ''; + echo ''; + if ($paypal['showBonus']) echo ''; + ?> + + '; + } + ?> +
Price:Points:Bonus:Action:
'. $price .'('. $paypal['currency'] .')'. $points .''. calculate_discount(($paypal['points_per_currency'] * $price), $points) .' bonus +
+ + + + + + + + + + + + + + + + +
+
+ + + +

Buy points using Pagseguro:

+
+ + + + + + + + + + +
+
+ + + + +

Buy points using Paygol:

+ +

+
+ + + + + + + + +
+Buy Points system disabled.

Sorry, this functionality is disabled.

'; +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/changelog.php b/app/ZnoteAAC/changelog.php new file mode 100644 index 0000000..a44759d --- /dev/null +++ b/app/ZnoteAAC/changelog.php @@ -0,0 +1,115 @@ +Changelog message deleted!"; + $updateCache = true; + } + } else { + if ($status) { + // POST update + if ($changelogId > 0) { + mysql_update("UPDATE `znote_changelog` SET `text`='$changelogText' WHERE `id`='$changelogId' LIMIT 1;"); + echo "

Changelog message updated!

"; + $updateCache = true; + } else { + // POST create + $time = time(); + mysql_insert("INSERT INTO `znote_changelog` (`text`, `time`, `report_id`, `status`) VALUES ('$changelogText', '$time', '0', '35');"); + echo "

Changelog message created!

"; + $updateCache = true; + } + } + } + if ($action === 2) { + $old = mysql_select_single("SELECT `text` FROM `znote_changelog` WHERE `id`='$changelogId' LIMIT 1;"); + } + // HTML to create or update + ?> +

Add or update changelog

+
+ +
+ +
+ + +

Changelog

+useMemory(false); +if ($updateCache === true) { + $changelogs = mysql_select_multi("SELECT `id`, `text`, `time`, `report_id`, `status` FROM `znote_changelog` ORDER BY `id` DESC;"); + + $cache->setContent($changelogs); + $cache->save(); +} else { + $changelogs = $cache->load(); +} +if (isset($changelogs) && !empty($changelogs) && $changelogs !== false) { + ?> + + + + Delete"; + } + ?> + + + + + + + + + + +
ChangelogsUpdate

+
+ + + +
+
+
+ + + +
+
+ +

Currently no change logs submitted.

+ diff --git a/app/ZnoteAAC/changepassword.php b/app/ZnoteAAC/changepassword.php new file mode 100644 index 0000000..d0b194c --- /dev/null +++ b/app/ZnoteAAC/changepassword.php @@ -0,0 +1,91 @@ +$value) { + if (empty($value) && in_array($key, $required_fields) === true) { + $errors[] = 'You need to fill in all fields.'; + break 1; + } + } + + $pass_data = user_data($session_user_id, 'password'); + //$pass_data['password']; + // $_POST[''] + + // .3 compatibility + if ($config['ServerEngine'] == 'TFS_03' && $config['salt'] === true) { + $salt = user_data($session_user_id, 'salt'); + } + if (sha1($_POST['current_password']) === $pass_data['password'] || $config['ServerEngine'] == 'TFS_03' && $config['salt'] === true && sha1($salt['salt'].$_POST['current_password']) === $pass_data['password']) { + if (trim($_POST['new_password']) !== trim($_POST['new_password_again'])) { + $errors[] = 'Your new passwords do not match.'; + } else if (strlen($_POST['new_password']) < 6) { + $errors[] = 'Your new passwords must be at least 6 characters.'; + } else if (strlen($_POST['new_password']) > 100) { + $errors[] = 'Your new passwords must be less than 100 characters.'; + } + } else { + $errors[] = 'Your current password is incorrect.'; + } +} + +include 'layout/overall/header.php'; ?> + +

Change Password:

+ +You will need to login again with the new password.'; + session_destroy(); + header("refresh:2;url=index.php"); + exit(); +} else { + if (empty($_POST) === false && empty($errors) === true) { + //Posted the form without errors + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'TFS_10' || $config['ServerEngine'] == 'OTHIRE') { + user_change_password($session_user_id, $_POST['new_password']); + } else if ($config['ServerEngine'] == 'TFS_03') { + user_change_password03($session_user_id, $_POST['new_password']); + } + header('Location: changepassword.php?success'); + } else if (empty($errors) === false){ + echo ''; + echo output_errors($errors); + echo ''; + } + ?> + +
+
    +
  • + Current password:
    + +
  • +
  • + New password:
    + +
  • +
  • + New password again:
    + +
  • + +
  • + +
  • +
+
+ diff --git a/app/ZnoteAAC/characterprofile.php b/app/ZnoteAAC/characterprofile.php new file mode 100644 index 0000000..2d11a1c --- /dev/null +++ b/app/ZnoteAAC/characterprofile.php @@ -0,0 +1,978 @@ + 0) { + $guild_exist = true; + $guild = get_player_guild_data($user_id); + $guild_name = get_guild_name($guild['guild_id']); + } + ?> + + + + + + + + + + + + 1): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0): ?> + + + + + + + 'townid', + 'TFS_03' => 'town' + // Default: town_id + ); + $column_town_id = (isset($column_town_id[$config['ServerEngine']])) + ? $column_town_id[$config['ServerEngine']] + : 'town_id'; + + $houses = mysql_select_multi(" + SELECT `id`, `owner`, `name`, `{$column_town_id}` AS `town_id` + FROM `houses` + WHERE `owner` = {$user_id}; + "); + + if ($houses !== false) { + foreach ($houses as $h): ?> + + + + + + + + + + + + + + + + + + + + 0) { + $bar_mana = (int)($bar_length * ($playerstats['mana'] / $playerstats['manamax'])); + } + else { + $bar_mana = 100; + } + + $outfit_server = $config['show_outfits']['imageServer']; + $outfit_storage = $config['EQ_shower']['storage_value']; + + $male_outfits = array( + [128,129,130,131,132], + [133,134,143,144,145], + [146,151,152,153,154], + [251,268,273,278,289], + [325,328,335,367,430], + [432,463,465,472,512], + //516,541,574,577,610,619,633,634,637,665,667,684,695,697,699,725,733,746,750,760,846,853,873,884,899 + ); + + $female_outfits = array( + [136,137,138,139,140], + [141,142,147,148,149], + [150,155,156,157,158], + [252,269,270,279,288], + [324,329,336,366,431], + [433,464,466,471,513], + //514,542,575,578,618,620,632,635,636,664,666,683,694,696,698,724,732,745,749,759,845,852,874,885,900 + ); + + $featured_outfits = ($profile_data['sex'] == 1) ? $male_outfits : $female_outfits; + $outfit_list = array(); + $outfit_rows = COUNT($featured_outfits); + $outfit_columns = COUNT($featured_outfits[0]); + + foreach ($featured_outfits as $row) { + if (COUNT($row) > $outfit_columns) { + $outfit_columns = COUNT($row); + } + foreach ($row as $column) { + $outfit_list[] = $column; + } + } + + $highest_outfit_id = MAX($outfit_list); + $outfit_storage_max = $outfit_storage + $highest_outfit_id + 1; + + $player_outfits = array(); + $storage_sql = mysql_select_multi(" + SELECT `key`, `value` + FROM `player_storage` + WHERE `player_id`={$user_id} + AND `key` > {$outfit_storage} + AND `key` < {$outfit_storage_max} + "); + if ($storage_sql !== false && !empty($storage_sql)) { + foreach ($storage_sql as $row) { + $player_outfits[$row['key']] = $row['value']; + } + } + + $aquired_outfits = array(); + foreach ($outfit_list as $outfit_id) { + $outfit_key = $outfit_storage + $outfit_id; + if (isset($player_outfits[$outfit_key]) && $player_outfits[$outfit_key] == 3) { + $aquired_outfits[$outfit_id] = true; + } + } + ?> + + + + + +
+ +
+ img +
+ 0): + ?> +
+ +
+ +
+

+
Position
Flagged for deletion by owner after .
Sex
Level
Vocation
Guild of
Last Login
Achievement Points
House
Status
Created
+
+ + +
+ +
+
+ +
+ "> +
+ + Cap:
+ +
+ img +
+ +
+ + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+ +
+ + + +
+ + + + + + + + + + + + + + +
Comment:
',$profile_znote_data['comment']); ?>
+ + + + 'Show', + 'hide' => 'Hide' + ); + if ($achievements !== false): ?> +

Achievements:

+ + + + + + + + + + + + + + + + + + + +
NameDescriptionPoints
+ + + + + + + + + + + + + + ".$d['killed_by']."" + : $d['killed_by']; + + ?> + + + + + + + + + '. $value['killed_by'] .''; + } else { + $value['killed_by'] = 'monster: '. $value['killed_by'] .'.'; + } + ?> + + + + + + + + + = 1) { + $namedata = user_character_data((int)$value[3], 'name'); + if ($namedata !== false) { + $value[3] = $namedata['name']; + $value[3] = 'player: '. $value[3] .''; + } else { + $value[3] = 'deleted player.'; + } + } else { + $value[3] = user_get_killer_m_name(user_get_kid($value['id'])); + if ($value[3] === false) { + $value[3] = 'deleted player.'; + } + } + ?> + + + + + + + + + + +
Death List
+ (unjustified)"; + } + $mostdmg = ($d['mostdamage_by'] !== $d['killed_by']) ? true : false; + if ($mostdmg) { + $mostdmg = ($d['mostdamage_is_player']) + ? "".$d['mostdamage_by']."" + : $d['mostdamage_by']; + + echo "
and by $mostdmg."; + + if ($d['mostdamage_unjustified']) { + echo " (unjustified)"; + } + } else { + echo " (soloed)"; + } + ?> +
This player has never died.
This player has never died.
This player has never died.
+ + + + Quest progression + + + + + + + + + + + + + + +
Quest:progression:
+ % +
+
+ + + + + +
  • + Other visible characters on this account:
    + + + + + + + + + + + + + + + + + +
    Name:Level:Vocation:Last login:Status:
    +
  • + + + +

    Address:

    + + diff --git a/app/ZnoteAAC/config.countries.php b/app/ZnoteAAC/config.countries.php new file mode 100644 index 0000000..6c239f6 --- /dev/null +++ b/app/ZnoteAAC/config.countries.php @@ -0,0 +1,249 @@ + 'Afghanistan', + 'al' => 'Albania', + 'dz' => 'Algeria', + 'as' => 'American Samoa', + 'ad' => 'Andorra', + 'ao' => 'Angola', + 'ai' => 'Anguilla', + 'aq' => 'Antarctica', + 'ag' => 'Antigua and Barbuda', + 'ar' => 'Argentina', + 'am' => 'Armenia', + 'aw' => 'Aruba', + 'au' => 'Australia', + 'at' => 'Austria', + 'az' => 'Azerbaijan', + 'bs' => 'Bahamas', + 'bh' => 'Bahrain', + 'bd' => 'Bangladesh', + 'bb' => 'Barbados', + 'by' => 'Belarus', + 'be' => 'Belgium', + 'bz' => 'Belize', + 'bj' => 'Benin', + 'bm' => 'Bermuda', + 'bt' => 'Bhutan', + 'bo' => 'Bolivia', + 'ba' => 'Bosnia and Herzegovina', + 'bw' => 'Botswana', + 'bv' => 'Bouvet Island', + 'br' => 'Brazil', + 'io' => 'British Indian Ocean Territory', + 'bn' => 'Brunei Darussalam', + 'bg' => 'Bulgaria', + 'bf' => 'Burkina Faso', + 'bi' => 'Burundi', + 'kh' => 'Cambodia', + 'cm' => 'Cameroon', + 'ca' => 'Canada', + 'cv' => 'Cape Verde', + 'ky' => 'Cayman Islands', + 'cf' => 'Central African Republic', + 'td' => 'Chad', + 'cl' => 'Chile', + 'cn' => 'China', + 'cx' => 'Christmas Island', + 'cc' => 'Cocos (Keeling) Islands', + 'co' => 'Colombia', + 'km' => 'Comoros', + 'cg' => 'Congo', + 'cd' => 'Congo, the Democratic Republic of the', + 'ck' => 'Cook Islands', + 'cr' => 'Costa Rica', + 'ci' => 'Cote D\'Ivoire', + 'hr' => 'Croatia', + 'cu' => 'Cuba', + 'cy' => 'Cyprus', + 'cz' => 'Czech Republic', + 'dk' => 'Denmark', + 'dj' => 'Djibouti', + 'dm' => 'Dominica', + 'do' => 'Dominican Republic', + 'ec' => 'Ecuador', + 'eg' => 'Egypt', + 'sv' => 'El Salvador', + 'gq' => 'Equatorial Guinea', + 'er' => 'Eritrea', + 'ee' => 'Estonia', + 'et' => 'Ethiopia', + 'fk' => 'Falkland Islands (Malvinas)', + 'fo' => 'Faroe Islands', + 'fj' => 'Fiji', + 'fi' => 'Finland', + 'fr' => 'France', + 'gf' => 'French Guiana', + 'pf' => 'French Polynesia', + 'tf' => 'French Southern Territories', + 'ga' => 'Gabon', + 'gm' => 'Gambia', + 'ge' => 'Georgia', + 'de' => 'Germany', + 'gh' => 'Ghana', + 'gi' => 'Gibraltar', + 'gr' => 'Greece', + 'gl' => 'Greenland', + 'gd' => 'Grenada', + 'gp' => 'Guadeloupe', + 'gu' => 'Guam', + 'gt' => 'Guatemala', + 'gn' => 'Guinea', + 'gw' => 'Guinea-Bissau', + 'gy' => 'Guyana', + 'ht' => 'Haiti', + 'hm' => 'Heard Island and Mcdonald Islands', + 'va' => 'Holy See (Vatican City State)', + 'hn' => 'Honduras', + 'hk' => 'Hong Kong', + 'hu' => 'Hungary', + 'is' => 'Iceland', + 'in' => 'India', + 'id' => 'Indonesia', + 'ir' => 'Iran, Islamic Republic of', + 'iq' => 'Iraq', + 'ie' => 'Ireland', + 'il' => 'Israel', + 'it' => 'Italy', + 'jm' => 'Jamaica', + 'jp' => 'Japan', + 'jo' => 'Jordan', + 'kz' => 'Kazakhstan', + 'ke' => 'Kenya', + 'ki' => 'Kiribati', + 'kp' => 'Korea, Democratic People\'s Republic of', + 'kr' => 'Korea, Republic of', + 'kw' => 'Kuwait', + 'kg' => 'Kyrgyzstan', + 'la' => 'Lao People\'s Democratic Republic', + 'lv' => 'Latvia', + 'lb' => 'Lebanon', + 'ls' => 'Lesotho', + 'lr' => 'Liberia', + 'ly' => 'Libyan Arab Jamahiriya', + 'li' => 'Liechtenstein', + 'lt' => 'Lithuania', + 'lu' => 'Luxembourg', + 'mo' => 'Macao', + 'mk' => 'Macedonia, the Former Yugoslav Republic of', + 'mg' => 'Madagascar', + 'mw' => 'Malawi', + 'my' => 'Malaysia', + 'mv' => 'Maldives', + 'ml' => 'Mali', + 'mt' => 'Malta', + 'mh' => 'Marshall Islands', + 'mq' => 'Martinique', + 'mr' => 'Mauritania', + 'mu' => 'Mauritius', + 'yt' => 'Mayotte', + 'mx' => 'Mexico', + 'fm' => 'Micronesia, Federated States of', + 'md' => 'Moldova, Republic of', + 'mc' => 'Monaco', + 'mn' => 'Mongolia', + 'ms' => 'Montserrat', + 'ma' => 'Morocco', + 'mz' => 'Mozambique', + 'mm' => 'Myanmar', + 'na' => 'Namibia', + 'nr' => 'Nauru', + 'np' => 'Nepal', + 'nl' => 'Netherlands', + 'an' => 'Netherlands Antilles', + 'nc' => 'New Caledonia', + 'nz' => 'New Zealand', + 'ni' => 'Nicaragua', + 'ne' => 'Niger', + 'ng' => 'Nigeria', + 'nu' => 'Niue', + 'nf' => 'Norfolk Island', + 'mp' => 'Northern Mariana Islands', + 'no' => 'Norway', + 'om' => 'Oman', + 'pk' => 'Pakistan', + 'pw' => 'Palau', + 'ps' => 'Palestinian Territory, Occupied', + 'pa' => 'Panama', + 'pg' => 'Papua New Guinea', + 'py' => 'Paraguay', + 'pe' => 'Peru', + 'ph' => 'Philippines', + 'pn' => 'Pitcairn', + 'pl' => 'Poland', + 'pt' => 'Portugal', + 'pr' => 'Puerto Rico', + 'qa' => 'Qatar', + 're' => 'Reunion', + 'ro' => 'Romania', + 'ru' => 'Russian Federation', + 'rw' => 'Rwanda', + 'sh' => 'Saint Helena', + 'kn' => 'Saint Kitts and Nevis', + 'lc' => 'Saint Lucia', + 'pm' => 'Saint Pierre and Miquelon', + 'vc' => 'Saint Vincent and the Grenadines', + 'ws' => 'Samoa', + 'sm' => 'San Marino', + 'st' => 'Sao Tome and Principe', + 'sa' => 'Saudi Arabia', + 'sn' => 'Senegal', + 'cs' => 'Serbia and Montenegro', + 'sc' => 'Seychelles', + 'sl' => 'Sierra Leone', + 'sg' => 'Singapore', + 'sk' => 'Slovakia', + 'si' => 'Slovenia', + 'sb' => 'Solomon Islands', + 'so' => 'Somalia', + 'za' => 'South Africa', + 'gs' => 'South Georgia and the South Sandwich Islands', + 'es' => 'Spain', + 'lk' => 'Sri Lanka', + 'sd' => 'Sudan', + 'sr' => 'Suriname', + 'sj' => 'Svalbard and Jan Mayen', + 'sz' => 'Swaziland', + 'se' => 'Sweden', + 'ch' => 'Switzerland', + 'sy' => 'Syrian Arab Republic', + 'tw' => 'Taiwan, Province of China', + 'tj' => 'Tajikistan', + 'tz' => 'Tanzania, United Republic of', + 'th' => 'Thailand', + 'tl' => 'Timor-Leste', + 'tg' => 'Togo', + 'tk' => 'Tokelau', + 'to' => 'Tonga', + 'tt' => 'Trinidad and Tobago', + 'tn' => 'Tunisia', + 'tr' => 'Turkey', + 'tm' => 'Turkmenistan', + 'tc' => 'Turks and Caicos Islands', + 'tv' => 'Tuvalu', + 'ug' => 'Uganda', + 'ua' => 'Ukraine', + 'ae' => 'United Arab Emirates', + 'gb' => 'United Kingdom', + 'us' => 'United States', + 'um' => 'United States Minor Outlying Islands', + 'uy' => 'Uruguay', + 'uz' => 'Uzbekistan', + 'vu' => 'Vanuatu', + 've' => 'Venezuela', + 'vn' => 'Viet Nam', + 'vg' => 'Virgin Islands, British', + 'vi' => 'Virgin Islands, U.s.', + 'wf' => 'Wallis and Futuna', + 'eh' => 'Western Sahara', + 'ye' => 'Yemen', + 'zm' => 'Zambia', + 'zw' => 'Zimbabwe' +); +?> diff --git a/app/ZnoteAAC/config.php b/app/ZnoteAAC/config.php new file mode 100644 index 0000000..ae9538e --- /dev/null +++ b/app/ZnoteAAC/config.php @@ -0,0 +1,1084 @@ + array( + 'First Dragon', // name + 'Rumours say that you will never forget your first Dragon', // description + 'points' => '1', // points + 'img' => 'https://i.imgur.com/Nk2XDge.gif', // direct image link or path, ex: 'images/dragon.png' + ), + 35001 => array( + 'Uniwheel', + 'You\'re probably one of the very few people with this classic and unique ride, hope it doesn\'t break anytime soon.', // description + 'points' => '1', + 'img' => 'https://i.imgur.com/0GYRgGj.gif', // direct image link or path, ex: 'images/uniwheel.png' + 'secret' => true + ), + 30001 => array( + 'Allow Cookies?', + 'With a perfectly harmless smile you fooled all of those wicecrackers into eating your exploding cookies. Consider a boy or girl scout outfit next time to make the trick even better.', + 'points' => '10', // 1-3 points (1 star), 4-6 points (2 stars), 7-9 points(3 stars), 10 points => (4 stars) + 'secret' => true // show "secret" badge + ), + 30002 => array( + 'Backpack Tourist', + '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.', + 'points' => '7' + ), + 30003 => array( + 'Bearhugger', + '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.', + 'points' => '4' + ), + 30004 => array( + 'Bone Brother', + '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.', + 'points' => '1' + ), + 30005 => array( + 'Chorister', + 'Lalalala... you now know the cult\'s hymn sung in Liberty Bay by heart. Not that hard, considering that it mainly consists of two notes and repetitive lyrics.', + 'points' => '1' + ), + 30006 => array( + 'Fountain of Life', + '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.', + 'points' => '1', + 'secret' => true + ), + 30007 => array( + 'Here, Fishy Fishy!', + 'Ah, the smell of the sea! Standing at the shore and casting a line is one of your favourite activities. For you, fishing is relaxing - and at the same time, providing easy food. Perfect!', + 'points' => '1' + ), + 30008 => array( + 'Honorary Barbarian', + '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.', + 'points' => '1' + ), + 30009 => array( + 'Huntsman', + '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!', + 'points' => '2' + ), + 300010 => array( + 'Just in Time', + '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.', + 'points' => '1' + ), + 30011 => array( + 'Matchmaker', + '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!', + 'points' => '1', + 'secret' => true + ), + 30012 => array( + 'Nightmare Knight', + '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.', + 'points' => '1', + 'secret' => true + ), + 30013 => array( + 'Party Animal', + '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!', + 'points' => '1', + 'secret' => true + ), + 30014 => array( + 'Secret Agent', + '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.', + 'points' => '1', + 'secret' => true + ), + 30015 => array( + 'Talented Dancer', + '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!', + 'points' => '1' + ), + 30016 => array( + 'Territorial', + '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.', + 'points' => '1' + ), + 30017 => array( + 'Worm Whacker', + 'Weehee! Whack those worms! You sure know how to handle a big hammer.', + 'points' => '1', + 'secret' => true + ), + 30018 => array( + 'Allowance Collector', + 'You certainly have your ways when it comes to acquiring money. Many of them are pink and paved with broken fragments of porcelain.', + 'points' => '1' + ), + 30019 => array( + 'Amateur Actor', + '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.', + 'points' => '2' + ), + 30020 => array( + 'Animal Activist', + '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.', + 'points' => '2', + 'secret' => true + ), + ); + + // TFS 1.x powergamers and top online + // Before enabling powergamers, make sure that you have added Lua files and added the SQL columns to your server db. + // files can be found at Lua folder. + $config['powergamers'] = array( + 'enabled' => true, // Enable or disable page + 'limit' => 20, // Number of players that it will show. + ); + + $config['toponline'] = array( + 'enabled' => true, // Enable or disable page + 'limit' => 20, // Number of players that it will show. + ); + + // Vocation IDs, names and which vocation ID they got promoted from + $config['vocations'] = array( + 0 => array( + 'name' => 'No vocation', + 'fromVoc' => false + ), + 1 => array( + 'name' => 'Sorcerer', + 'fromVoc' => false + ), + 2 => array( + 'name' => 'Druid', + 'fromVoc' => false + ), + 3 => array( + 'name' => 'Paladin', + 'fromVoc' => false + ), + 4 => array( + 'name' => 'Knight', + 'fromVoc' => false + ), + 5 => array( + 'name' => 'Master Sorcerer', + 'fromVoc' => 1 + ), + 6 => array( + 'name' => 'Elder Druid', + 'fromVoc' => 2 + ), + 7 => array( + 'name' => 'Royal Paladin', + 'fromVoc' => 3 + ), + 8 => array( + 'name' => 'Elite Knight', + 'fromVoc' => 4 + ) + ); + + /* Vocation stat gains per level + - Ordered by vocation ID + - Currently used for admin_skills page. */ + $config['vocations_gain'] = array( + 0 => array( + 'hp' => 5, + 'mp' => 5, + 'cap' => 10 + ), + 1 => array( + 'hp' => 5, + 'mp' => 30, + 'cap' => 10 + ), + 2 => array( + 'hp' => 5, + 'mp' => 30, + 'cap' => 10 + ), + 3 => array( + 'hp' => 10, + 'mp' => 15, + 'cap' => 20 + ), + 4 => array( + 'hp' => 15, + 'mp' => 5, + 'cap' => 25 + ), + 5 => array( + 'hp' => 5, + 'mp' => 30, + 'cap' => 10 + ), + 6 => array( + 'hp' => 5, + 'mp' => 30, + 'cap' => 10 + ), + 7 => array( + 'hp' => 10, + 'mp' => 15, + 'cap' => 20 + ), + 8 => array( + 'hp' => 15, + 'mp' => 5, + 'cap' => 25 + ), + ); + // Town ids and names: (In RME map editor, open map, click CTRL + T to view towns, their names and their IDs. + // townID => 'townName' ex: [1 => 'Rookgaard'] + $config['towns'] = array( + 1 => 'Rookgaard', + 2 => 'Rookgaard Tutorial Island', + 3 => 'Island Of Destiny', + 4 => 'Dawnport', + 5 => "Ab'Dendriel", + 6 => 'Carlin', + 7 => 'Kazordoon', + 8 => 'Thais', + 9 => 'Venore', + 10 => 'Ankrahmun', + 11 => 'Edron', + 12 => 'Farmine', + 13 => 'Darashia', + 14 => 'Liberty Bay', + 15 => 'Port Hope', + 16 => 'Svargrond', + 17 => 'Yalahar', + 18 => 'Gray Beach', + 19 => 'Krailos', + 20 => 'Rathleton', + 21 => 'Roshamuul', + 22 => 'Issavi' + ); + + // -- HOUSE AUCTION SYSTEM! (TFS 1.x ONLY) + $config['houseConfig'] = array( + 'HouseListDefaultTown' => 8, // Default town id to display when visting house list page page. + 'minimumBidSQM' => 200, // Minimum bid cost on auction (per SQM) + 'auctionPeriod' => 24 * 60 * 60, // 24 hours auction time. + 'housesPerPlayer' => 1, + 'requirePremium' => false, + 'levelToBuyHouse' => 8, + // Instant buy with shop points + 'shopPoints' => array( + 'enabled' => true, + // SQM count => points cost + 'cost' => array( + 1 => 10, + 25 => 15, + 60 => 25, + 100 => 30, + 200 => 40, + 300 => 50, + ), + ), + ); + + // Leave on black square in map and player should get teleported to their selected town. + // If chars get buggy set this position to a beginner location to force players there. + $config['default_pos'] = array( + 'x' => 5, + 'y' => 5, + 'z' => 2, + ); + + $config['war_status'] = array( + 0 => 'Pending', + 1 => 'Accepted', + 2 => 'Rejected', + 3 => 'Canceled', + 4 => 'Ended by kill limit', + 5 => 'Ended', + ); + + /* -- SUB PAGES -- + Some custom layouts/templates have custom pages, they can use + this sub page functionality for that. + */ + $config['allowSubPages'] = true; + + // ---------------- \\ + // Create Character \\ + // ---------------- \\ + + // Max characters on each account: + $config['max_characters'] = 7; + + // Available character vocation users can choose (specify vocation ID). + $config['available_vocations'] = array(1, 2, 3, 4); + + // Available towns (specify town ids, etc: (1, 2, 3); to display 3 town options (town id 1, 2 and 3). + // Town IDs are the ones from $config['towns'] array + $config['available_towns'] = array(6, 7, 8, 9); + + $config['player'] = array( + 'base' => array( + 'level' => 8, + 'health' => 185, + 'mana' => 90, + 'cap' => 470, + 'soul' => 100 + ), + // Health, mana cap etc are calculated with $config['vocations_gain'] and 'base' values of $config['player'] + 'create' => array( + 'level' => 8, + 'novocation' => array( // Vocation id 0 (No vocation) special settings + 'level' => 1, + 'forceTown' => true, + 'townId' => 1 + ), + 'skills' => array( // See $config['vocations'] for proper vocation names of these IDs + // No vocation + 0 => array( + 'magic' => 0, + 'fist' => 10, + 'club' => 10, + 'axe' => 10, + 'sword' => 10, + 'dist' => 10, + 'shield' => 10, + 'fishing' => 10, + ), + // Sorcerer + 1 => array( + 'magic' => 0, + 'fist' => 10, + 'club' => 10, + 'axe' => 10, + 'sword' => 10, + 'dist' => 10, + 'shield' => 10, + 'fishing' => 10, + ), + // Druid + 2 => array( + 'magic' => 0, + 'fist' => 10, + 'club' => 10, + 'axe' => 10, + 'sword' => 10, + 'dist' => 10, + 'shield' => 10, + 'fishing' => 10, + ), + // Paladin + 3 => array( + 'magic' => 0, + 'fist' => 10, + 'club' => 10, + 'axe' => 10, + 'sword' => 10, + 'dist' => 10, + 'shield' => 10, + 'fishing' => 10, + ), + // Knight + 4 => array( + 'magic' => 0, + 'fist' => 10, + 'club' => 10, + 'axe' => 10, + 'sword' => 10, + 'dist' => 10, + 'shield' => 10, + 'fishing' => 10, + ), + ), + 'male_outfit' => array( + 'id' => 128, + 'head' => 78, + 'body' => 68, + 'legs' => 58, + 'feet' => 76 + ), + 'female_outfit' => array( + 'id' => 136, + 'head' => 78, + 'body' => 68, + 'legs' => 58, + 'feet' => 76 + ) + ) + ); + + // Minimum allowed letters in character name. Ex: 4 letters: "Kare". + $config['minL'] = 3; + // Maximum allowed letters in character name. Ex: 20 letters: "Bobkareolesofiesberg" + // Pre QT clients (lower than version 11) support only 20 letters max, while newer clients support up to 25 + $config['maxL'] = 20; + // Maximum allowed words in character name. Ex: 2 words = "Bob Kare", 3 words: "Bob Arne Kare" as maximum char name words. + $config['maxW'] = 3; + + // -------------- \\ + // WEBSITE STUFF \\ + // -------------- \\ + + // News to be displayed per page + $config['news_per_page'] = 5; + + // Enable or disable changelog ticker in news page. + $config['UseChangelogTicker'] = true; + + // Highscore configuration + $config['highscore'] = array( + 'rows' => 100, + 'rowsPerPage' => 20, + 'ignoreGroupId' => 2, // Ignore this and higher group ids (staff) + ); + + // ONLY FOR TFS 0.2 (TFS 0.3/4 users don't need to care about this, as its fully loaded from db) + $config['house'] = array( + 'house_file' => 'C:\test\Mystic Spirit_0.2.5\data\world\forgotten-house.xml', + 'price_sqm' => '50', // price per house sqm + ); + + $config['delete_character_interval'] = '3 DAY'; // Delay after user character delete request is executed, ex: 1 DAY, 2 HOUR, 3 MONTH etc. + + $config['validate_IP'] = false; + $config['salt'] = false; + + // Restricted names + $config['invalidNameTags'] = array( + "owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "hitler", "cm", "gm", "game master", "anal", "anus", "arse", "ass", "asses", "assfucker", "assfukka", "asshole", "arsehole", "asswhole", "assmunch", "ballsack", "wanky", "whore", "whoar", "xxx", "xx", "yaoi", "yury", "bastard", "beastial", "bestial", "bellend", "bdsm", "beastiality", "bestiality", "bitch", "bitches", "bitchin", "bitching", "bimbo", "bimbos", "blow job", "blowjob", "blowjobs", "blue waffle", "boob", "boobs", "booobs", "boooobs", "booooobs", "booooooobs", "breasts", "booty call", "brown shower", "brown showers", "boner", "bondage", "buceta", "bukake", "bukkake", "bullshit", "bull shit", "busty", "butthole", "carpet muncher", "cawk", "chink", "cipa", "clit", "clits", "clitoris", "cnut", "cock", "cocks", "cockface", "cockhead", "cockmunch", "cockmuncher", "cocksuck", "cocksucked", "cocksucking", "cocksucks", "cocksucker", "cokmuncher", "coon", "cow girl", "cow girls", "cowgirl", "cowgirls", "crap", "crotch", "cum", "cummer", "cumming", "cuming", "cums", "cumshot", "cunilingus", "cunillingus", "cunnilingus", "cunt", "cuntlicker", "cuntlicking", "cunts", "damn", "dick", "dickhead", "dildo", "dildos", "dink", "dinks", "deepthroat", "deep throat", "dog style", "doggie style", "doggiestyle", "doggy style", "doggystyle", "donkeyribber", "doosh", "douche", "duche", "dyke", "ejaculate", "ejaculated", "ejaculates", "ejaculating", "ejaculatings", "ejaculation", "ejakulate", "erotic", "erotism", "fag", "faggot", "fagging", "faggit", "faggitt", "faggs", "fagot", "fagots", "fags", "fatass", "femdom", "fingering", "footjob", "foot job", "fuck", "fucks", "fucker", "fuckers", "fucked", "fuckhead", "fuckheads", "fuckin", "fucking", "fcuk", "fcuker", "fcuking", "felching", "fellate", "fellatio", "fingerfuck", "fingerfucked", "fingerfucker", "fingerfuckers", "fingerfucking", "fingerfucks", "fistfuck", "fistfucked", "fistfucker", "fistfuckers", "fistfucking", "fistfuckings", "fistfucks", "flange", "fook", "fooker", "fucka", "fuk", "fuks", "fuker", "fukker", "fukkin", "fukking", "futanari", "futanary", "gangbang", "gangbanged", "gang bang", "gokkun", "golden shower", "goldenshower", "gaysex", "goatse", "handjob", "hand job", "hentai", "hooker", "hoer", "homo", "horny", "incest", "jackoff", "jack off", "jerkoff", "jerk off", "jizz", "knob", "kinbaku", "labia", "masturbate", "masochist", "mofo", "mothafuck", "motherfuck", "motherfucker", "mothafucka", "mothafuckas", "mothafuckaz", "mothafucked", "mothafucker", "mothafuckers", "mothafuckin", "mothafucking", "mothafuckings", "mothafucks", "mother fucker", "motherfucked", "motherfucker", "motherfuckers", "motherfuckin", "motherfucking", "motherfuckings", "motherfuckka", "motherfucks", "milf", "muff", "negro", "nigga", "nigger", "nigg", "nipple", "nipples", "nob", "nob jokey", "nobhead", "nobjocky", "nobjokey", "numbnuts", "nutsack", "nude", "nudes", "orgy", "orgasm", "orgasms", "panty", "panties", "penis", "playboy", "pinto", "porn", "porno", "pornography", "pron", "punheta", "pussy", "pussies", "puta", "rape", "raping", "rapist", "rectum", "retard", "rimming", "sadist", "sadism", "schlong", "scrotum", "sex", "semen", "shemale", "she male", "shibari", "shibary", "shit", "shitdick", "shitfuck", "shitfull", "shithead", "shiting", "shitings", "shits", "shitted", "shitters", "shitting", "shittings", "shitty", "shota", "skank", "slut", "sluts", "smut", "smegma", "spunk", "strip club", "stripclub", "tit", "tits", "titties", "titty", "titfuck", "tittiefucker", "titties", "tittyfuck", "tittywank", "titwank", "threesome", "three some", "throating", "twat", "twathead", "twatty", "twunt", "viagra", "vagina", "vulva", "viado", "wank", "wanker", + ); + + // Comment out the below array if you want to allow players to use creature names: + $config['creatureNameTags'] = array( + "acolyte of the cult", "adept of the cult", "amazon", "ancient scarab", "arachnophobica", "assassin", "azure frog", "badger", "bandit", "banshee", "barbarian bloodwalker", "barbarian brutetamer", "barbarian headsplitter", "barbarian skullhunter", "bat", "bear", "behemoth", "betrayed wraith", "biting book", "black knight", "black sphinx acolyte", "blightwalker", "blood beast", "blood crab", "blood hand", "blood priest", "blue djinn", "boar", "bog frog", "bog raider", "bonebeast", "bonelord", "boogy", "brain squid", "braindeath", "breach brood", "brimstone bug", "burning book", "burning gladiator", "burster spectre", "carniphila", "carrion worm", "cave devourer", "centipede", "chakoya toolshaper", "chakoya tribewarden", "chakoya windcaller", "choking fear", "clay guardian", "clomp", "cobra", "coral frog", "corym charlatan", "corym skirmisher", "corym vanguard", "crab", "crazed beggar", "crazed summer rearguard", "crazed summer vanguard", "crazed winter rearguard", "crazed winter vanguard", "crimson frog", "crocodile", "crypt defiler", "crypt shambler", "crypt warden", "crystal spider", "crystalcrusher", "cult believer", "cult enforcer", "cult scholar", "cyclops", "cyclops drone", "cyclops smith", "dark apprentice", "dark faun", "dark magician", "dark monk", "dark torturer", "dawnfire asura", "death blob", "deathling scout", "deathling spellsinger", "deepling guard", "deepling scout", "deepling spellsinger", "deepling warrior", "deepling worker", "deepworm", "defiler", "demon outcast", "demon skeleton", "demon", "destroyer", "devourer", "diabolic imp", "diamond servant", "diremaw", "dragon hatchling", "dragon lord hatchling", "dragon lord", "dragon", "draken abomination", "draken elite", "draken spellweaver", "draken warmaster", "dread intruder", "drillworm", "dwarf geomancer", "dwarf guard", "dwarf henchman", "dwarf soldier", "dwarf", "dworc fleshhunter", "dworc venomsniper", "dworc voodoomaster", "earth elemental", "efreet", "elder bonelord", "elder wyrm", "elephant", "elf arcanist", "elf scout", "elf", "emerald damselfly", "energetic book", "energy elemental", "enfeebled silencer", "enlightened of the cult", "enraged crystal golem", "eternal guardian", "falcon knight", "falcon paladin", "faun", "fire devil", "fire elemental", "firestarter", "forest fury", "fox", "frazzlemaw", "frost dragon hatchling", "frost dragon", "frost flower asura", "fury", "gargoyle", "gazer spectre", "ghastly dragon", "ghost", "ghoul", "giant spider", "gladiator", "gloom wolf", "glooth bandit", "glooth blob", "glooth brigand", "glooth golem", "gnarlhound", "guzzlemaw", "hand of cursed fate", "haunted treeling", "hellhound", "hellflayer", "hellfire fighter", "hellspawn", "hero", "honour guard", "hunter", "hydra", "ice golem", "ice witch", "infernalist", "juggernaut", "killer caiman", "kongra", "lancer beetle", "lamassu", "lich", "lizard chosen", "lizard dragon priest", "lizard high guard", "lizard legionnaire", "lizard sentinel", "lizard snakecharmer", "lizard templar", "lizard zaogun", "lost soul", "lumbering carnivor", "mad scientist", "mammoth", "marid", "marsh stalker", "medusa", "menacing carnivor", "mercury blob", "merlkin", "metal gargoyle", "midnight asura", "minotaur amazon", "minotaur archer", "minotaur cult follower", "minotaur cult prophet", "minotaur cult zealot", "minotaur guard", "minotaur hunter", "minotaur mage", "minotaur", "monk", "mooh'tah warrior", "moohtant", "mummy", "mutated bat", "mutated human", "mutated rat", "mutated tiger", "necromancer", "nightmare scion", "nightmare", "nightstalker", "nomad", "novice of the cult ", "nymph", "omnivora", "orc berserker", "orc leader", "orc rider", "orc shaman", "orc warlord", "orc warrior", "orc", "pirate buccaneer", "pirate corsair", "pirate cutthroat", "pirate ghost", "pirate marauder", "pirate skeleton", "pixie", "plaguesmith", "priestess", "pooka", "ravenous lava lurker", "renegade knight", "retching horror", "ripper spectre", "roaring lion", "rot elemental", "rotworm", "rustheap golem", "scarab", "scorpion", "sea serpent", "serpent spawn", "sibang", "silencer", "skeleton elite warrior", "souleater", "spectre", "spiky carnivor", "stone golem", "stonerefiner", "swamp troll", "tarantula", "terramite", "thornback tortoise", "toad", "tortoise", "twisted pooka", "undead elite gladiator", "undead gladiator", "valkyrie", "vampire bride", "vampire viscount", "vampire", "vexclaw", "vicious squire", "vile grandmaster", "vulcongra", "wailing widow", "war golem", "war wolf", "warlock", "wasp", "water elemental", "weakened frazzlemaw", "werebadger", "werebear", "wereboar", "werefox", "werewolf", "worm priestess", "wolf", "wyrm", "wyvern", "yielothax", "young sea serpent", "zombie", "adult goanna", "black sphinx acolyte", "burning gladiator", "cobra assassin", "cobra scout", "cobra vizier", "crypt warden", "feral sphinx", "lamassu", "manticore", "ogre rowdy", "ogre ruffian", "ogre sage", "priestess of the wild sun", "sphinx", "sun-marked goanna", "young goanna", "cursed prospector", "evil prospector", "flimsy lost soul", "freakish lost soul", "mean lost soul", "a shielded astral glyph", "abyssador", "an astral glyph", "ascending ferumbras", "annihilon", "apocalypse", "apprentice sheng", "arachir the ancient one", "armenius", "azerus", "barbaria", "baron brute", "battlemaster zunzu", "bazir", "big boss trolliver", "bones", "boogey", "bretzecutioner", "brokul", "bruise payne", "brutus bloodbeard", "bullwark", "chizzoron the distorter", "coldheart", "countess sorrow", "deadeye devious", "deathbine", "deathstrike", "demodras", "dharalion", "diblis the fair", "dirtbeard", "diseased bill", "diseased dan", "diseased fred", "doomhowl", "dracola", "dreadwing", "ekatrix", "energized raging mage", "esmeralda", "ethershreck", "evil mastermind", "fatality", "fazzrah", "fernfang", "feroxa", "ferumbras", "flameborn", "fleshcrawler", "fleshslicer", "fluffy", "foreman kneebiter", "freegoiz", "fury of the emperor", "furyosa", "gaz'haragoth", "general murius", "ghazbaran", "glitterscale", "gnomevil", "golgordan", "grand mother foulscale", "groam", "grorlam", "gorgo", "hairman the huge", "haunter", "hellgorak", "hemming", "heoni", "hide", "hirintror", "horadron", "horestis", "incineron", "infernatil", "inky", "jaul", "kerberos", "koshei the deathless", "kraknaknork's demon", "kraknaknork", "kroazur", "latrivan", "lethal lissy", "leviathan", "lisa", "lizard abomination", "lord of the elements", "mad mage", "mad technomancer", "madareth", "man in the cave", "massacre", "mawhawk", "menace", "mephiles", "minishabaal", "monstor", "morgaroth", "morik the gladiator", "mr. punish", "munster", "mutated zalamon", "necropharus", "obujos", "orshabaal", "paiz the pauperizer", "raging mage", "ribstride", "rocko", "ron the ripper", "rottie the rotworm", "rotworm queen", "scarlett etzel", "scorn of the emperor", "shardhead", "sharptooth", "sir valorcrest", "snake god essence", "snake thing", "spider queen", "spite of the emperor", "splasher", "stonecracker", "sulphur scuttler", "tanjis", "terofar", "teleskor", "the abomination", "the axeorcist", "the blightfather", "the bloodtusk", "the bloodweb", "the book of death", "the collector", "the count", "the weakened count", "the dreadorian", "the evil eye", "the frog prince", "the handmaiden", "the horned fox", "the keeper", "the imperor", "the many", "the noxious spawn", "the old widow", "the pale count", "the plasmother", "the snapper", "the distorted astral source", "the astral source", "thul", "tiquandas revenge", "tirecz", "tyrn", "tormentor", "tremorak", "tromphonyte", "ungreez", "ushuriel", "verminor", "versperoth", "warlord ruzad", "white pale", "wrath of the emperor", "xenia", "yaga the crone", "yakchal", "zanakeph", "zavarash", "zevelon duskbringer", "zomba", "zoralurk", "zugurosh", "zushuka", "zulazza the corruptor", "glooth bomb", "bibby bloodbath", "doctor perhaps", "mooh'tah master", "the welter" + ); + + // Use guild logo system + $config['use_guild_logos'] = true; + + // Use country flags + $config['country_flags'] = array( + 'enabled' => true, + 'highscores' => true, + 'onlinelist' => true, + 'characterprofile' => true, + 'server' => 'http://flag.znote.eu' + ); + + // Show outfits + $config['show_outfits'] = array( + 'shop' => true, + 'highscores' => true, + 'characterprofile' => true, + 'onlinelist' => true, + // Image server may be unreliable and only for test, + // host yourself: https://otland.net/threads/item-images-10-92.242492/ + 'imageServer' => 'https://outfit-images.ots.me/animatedOutfits1099/animoutfit.php' + ); + + // Show advanced inventory data in character profile + $config['EQ_shower'] = array( + 'enabled' => true, + 'equipment' => true, + 'skills' => true, + 'outfits' => true, + // Player storage (storage_value + outfitId) + // used to see if player has outfit. + // see Lua scripts folder for otserv code + 'storage_value' => 10000 + ); + + // Level requirement to create guild? (Just set it to 1 to allow all levels). + $config['create_guild_level'] = 8; + + // Change Gender can be purchased in shop, or perhaps you want to allow everyone to change gender for free? + $config['free_sex_change'] = false; + + // Do you need to have premium account to create a guild? + $config['guild_require_premium'] = true; + + // There is a TFS 1.3 bug related to guild nicks + // https://github.com/otland/forgottenserver/issues/2561 + // So if your using TFS 1.x, you might need to disable guild nicks until the crash has been fixed. + $config['guild_allow_nicknames'] = true; + + $config['guildwar_enabled'] = false; + + // Use htaccess rewrite? (basically this makes website.com/username work instead of website.com/characterprofile.php?name=username + // Linux users needs to enable mod_rewrite php extention to make it work properly, so set it to false if your lost and using Linux. + $config['htwrite'] = true; + + // What client version and server port are you using on this OT? + // Used for the Downloads page. + $config['client'] = 1098; // 954 = client 9.54 + + // Download link to client. + $config['client_download'] = 'http://tibiaclient.otslist.eu/download/tibia'. $config['client'] .'.exe'; + $config['client_download_linux'] = 'http://tibiaclient.otslist.eu/download/tibia'. $config['client'] .'.tgz'; + + $config['port'] = 7171; // Port number to connect to your OT. + + $config['status'] = array( + 'status_check' => false, // Enable or disable status checker + 'status_ip' => '127.0.0.1', + 'status_port' => "7171", + ); + + // Gameserver info is used for client 11+ loginWebService + $config['login_web_service'] = true; // loginWebService for client 11+ enabled? + $config['gameserver'] = array( + 'ip' => '127.0.0.1', + 'port' => 7172, + 'name' => 'Forgotten' // Must be identical to config.lua (OT config file) server name. + ); + // Unlock all protocol 12 client features? Free premium in config.lua? Then set this to true. + $config['freePremium'] = true; + + // How often do you want highscores (cache) to update? + $config['cache'] = array( + // If you have two instances installed on same server, make each instance prefix unique + 'prefix' => 'znote_', + // 60 * 15; // 15 minutes. + 'lifespan' => 5, + // Store cache in memory/RAM? Requires PHP extension APCu + 'memory' => true + ); + + // WARNING! Account names written here will have admin access to web page! + $config['page_admin_access'] = array( + 'firstaccountName', + 'secondaccountName', + ); + + // Built-in FORUM + // Enable forum, enable guildboards, level to create threads/post in them + // How long do they have to wait to create thread or post? + // How to design/display hidden/closed/sticky threads. + $config['forum'] = array( + 'enabled' => true, + 'outfit_avatars' => true, // Show character outfit as forum avatar? + 'player_position' => true, // Show character position? ex: Tutor, Community Manager, God + 'guildboard' => true, + 'level' => 5, + 'cooldownPost' => 1, // 60, + 'cooldownCreate' => 1, // 180, + 'newPostsBumpThreads' => true, + 'hidden' => '[H]', + 'closed' => '[C]', + 'sticky' => '[S]', + ); + + // Guilds and guild war pages will do lots of queries on bigger databases. + // So its recommended to require login to view them, but you can disable this + // If you don't have any problems with load. + $config['require_login'] = array( + 'guilds' => false, + 'guildwars' => false, + ); + + // IMPORTANT! Write a character name(that exist) that will represent website bans! + // Or remember to create character named "God Website". + // If you don't do this, ban from admin panel won't work properly. + $config['website_char'] = 'God Website'; + + // ---------------- \\ + // ADVANCED STUFF \\ + // ---------------- \\ + // API config + $config['api'] = array( + 'debug' => false, + ); + + // website.com/gallery.php + // website.com/admin_gallery.php + // we use imgur as image host, and need to register app with them and add client/secret id. + // https://github.com/Znote/ZnoteAAC/wiki/IMGUR-powered-Gallery-page + $config['gallery'] = array( + 'Client Name' => 'ZnoteAAC-Gallery', + 'Client ID' => '4dfcdc4f2cabca6', + 'Client Secret' => '697af737777c99a8c0be07c2f4419aebb2c48ac5' + ); + + // Email Server configurations (SMTP) + /* Please consider using a released stable version of PHPMailer or you may run into issues. + Download PHPMailer: https://github.com/PHPMailer/PHPMailer/releases + Extract to Znote AAC directory (where this config.php file is located) + Rename the folder to "PHPMailer". Then configure this with your SMTP mail settings from your email provider. + */ + $config['mailserver'] = array( + 'register' => false, // Send activation mail + 'accountRecovery' => false, // Recover username or password through mail + 'myaccount_verify_email' => false, // Allow user to verify their email in myaccount page + 'verify_email_points' => 0, // 0 = disabled. Give users points reward for verifying their email + 'host' => "mailserver.znote.eu", // Outgoing mail server host. + 'securityType' => 'ssl', // ssl or tls + 'port' => 465, // SMTP port number - likely to be 465(ssl) or 587(tls) + 'email' => 'noreply@znote.eu', + 'username' => 'noreply@znote.eu', // Likely the same as email + 'password' => 'emailpassword', // The password. + 'debug' => false, // Enable debugging if you have problems and are looking for errors. + 'fromName' => $config['site_title'], + ); + + // Don't touch this unless you know what you are doing. (modifying these (key value) also requires modifications in OT files data/XML/groups.xml). + $config['ingame_positions'] = array( + 1 => 'Player', + 2 => 'Tutor', + 3 => 'Senior Tutor', + 4 => 'Gamemaster', + 5 => 'Community Manager', + 6 => 'God', + ); + + // Enable OS advanced features? false = no, true = yes + $config['os_enabled'] = false; + + // What kind of computer are you hosting this website on? + // Available options: LINUX or WINDOWS + $config['os'] = ZNOTE_OS; // Use 'ZNOTE_OS' to auto-detect + + // Measure how much players are lagging in-game. (Not completed). + $config['ping'] = false; + + // BAN STUFF - Don't touch this unless you know what you are doing. + // You can order the lines the way you want, from top to bottom, in which order you + // wish for them to be displayed in admin panel. Just make sure key[#] represent your description. + $config['ban_type'] = array( + 4 => 'NOTATION_ACCOUNT', + 2 => 'NAMELOCK_PLAYER', + 3 => 'BAN_ACCOUNT', + 5 => 'DELETE_ACCOUNT', + 1 => 'BAN_IPADDRESS', + ); + + // BAN STUFF - Don't touch this unless you know what you are doing. + // You can order the lines the way you want, from top to bot, in which order you + // wish for them to be displayed in admin panel. Just make sure key[#] represent your description. + $config['ban_action'] = array( + 0 => 'Notation', + 1 => 'Name Report', + 2 => 'Banishment', + 3 => 'Name Report + Banishment', + 4 => 'Banishment + Final Warning', + 5 => 'NR + Ban + FW', + 6 => 'Statement Report', + ); + + // Ban reasons, for changes beside default values to work with client, + // you also need to edit sources (https://github.com/otland/forgottenserver/blob/master/src/enums.h#L29) + $config['ban_reason'] = array( + 0 => 'Offensive Name', + 1 => 'Invalid Name Format', + 2 => 'Unsuitable Name', + 3 => 'Name Inciting Rule Violation', + 4 => 'Offensive Statement', + 5 => 'Spamming', + 6 => 'Illegal Advertising', + 7 => 'Off-Topic Public Statement', + 8 => 'Non-English Public Statement', + 9 => 'Inciting Rule Violation', + 10 => 'Bug Abuse', + 11 => 'Game Weakness Abuse', + 12 => 'Using Unofficial Software to Play', + 13 => 'Hacking', + 14 => 'Multi-Clienting', + 15 => 'Account Trading or Sharing', + 16 => 'Threatening Gamemaster', + 17 => 'Pretending to Have Influence on Rule Enforcement', + 18 => 'False Report to Gamemaster', + 19 => 'Destructive Behaviour', + 20 => 'Excessive Unjustified Player Killing', + 21 => 'Spoiling Auction', + ); + + // BAN STUFF + // Ban time duration selection in admin panel + // seconds => description + $config['ban_time'] = array( + 3600 => '1 hour', + 21600 => '6 hours', + 43200 => '12 hours', + 86400 => '1 day', + 259200 => '3 days', + 604800 => '1 week', + 1209600 => '2 weeks', + 2592000 => '1 month', + ); + + // --------------- \\ + // SECURITY STUFF \\ + // --------------- \\ + $config['use_token'] = false; + // Set up captcha keys on https://www.google.com/recaptcha/ + $config['use_captcha'] = false; + $config['captcha_site_key'] = "Site key"; + $config['captcha_secret_key'] = "Secret key"; + $config['captcha_use_curl'] = false; // Set to false if you don't have cURL installed, otherwise set it to true + + // Session prefix, if you are hosting multiple sites, make the session name different to avoid conflict. + $config['session_prefix'] = 'znote_'; + + /* Store visitor data + Store visitor data in the database, logging every IP visiting site, + and how many times they have visited the site. And sometimes what + they do on the site. + + This helps to prevent POST SPAM (like register 1000 accounts in a few seconds) + and other things which can stress and slow down the server. + + The only downside is that database can get pretty fed up with much IP data + if table never gets flushed once in a while. So I highly recommend you + to configure flush_ip_logs if IPs are logged. + */ + $config['log_ip'] = false; + + // Flush IP logs each configured seconds, 60 * 15 = 15 minutes. + // Set to false to entirely disable ip log flush. + // It is important to flush for optimal performance. + $config['flush_ip_logs'] = 59 * 27; + + /* IP SECURTY REQUIRE: $config['log_ip'] = true; + Configure how tight this security shall be. + Etc: You can max click on anything/refresh page + [max activity] 15 times, within time period 10 + seconds. During time_period, you can also only + register 1 account and 1 character. + */ + $config['ip_security'] = array( + 'time_period' => 10, // In seconds + 'max_activity' => 10, // page clicks/visits + 'max_post' => 6, // register, create, highscore, character search and such actions + 'max_account' => 1, // register + 'max_character' => 1, // create char + 'max_forum_post' => 1, // create threads and post in forum + ); + + ////////////// + /// PAYPAL /// + ////////////// + // https://www.paypal.com/ + + // Write your paypal address here, and what currency you want to receive money in. + $config['paypal'] = array( + 'enabled' => false, + 'email' => 'edit@me.com', // Example: paypal@mail.com + 'currency' => 'EUR', + 'points_per_currency' => 10, // 1 currency = ? points? [ONLY used to calculate bonuses] + 'success' => "http://".$_SERVER['HTTP_HOST']."/success.php", + 'failed' => "http://".$_SERVER['HTTP_HOST']."/failed.php", + 'ipn' => "http://".$_SERVER['HTTP_HOST']."/ipn.php", + 'showBonus' => true, + ); + + // Configure the "buy now" buttons prices, first write price, then how many points you get. + // Giving some bonus points for higher donations will tempt users to donate more. + $config['paypal_prices'] = array( + // price => points, + 1 => 45, // -10% bonus + 10 => 100, // 0% bonus + 15 => 165, // +10% bonus + 20 => 240, // +20% bonus + 25 => 325, // +30% bonus + 30 => 420, // +40% bonus + ); + + ///////////////// + /// PAGSEGURO /// + ///////////////// + // https://pagseguro.uol.com.br/ + + // Write your pagseguro address here, and what currency you want to receive money in. + $config['pagseguro'] = array( + 'enabled' => false, + 'sandbox' => false, + 'email' => 'edit@me.com', // Example: pagseguro@mail.com + 'token' => '', + 'currency' => 'BRL', + 'product_name' => '', + 'price' => 100, // 1 real + 'ipn' => "http://".$_SERVER['HTTP_HOST']."/pagseguro_ipn.php", + 'urls' => array( + 'www' => 'pagseguro.uol.com.br', + 'ws' => 'ws.pagseguro.uol.com.br', + 'stc' => 'stc.pagseguro.uol.com.br' + ) + ); + + if ($config['pagseguro']['sandbox']) { + $config['pagseguro']['urls'] = array_map(function ($item) { + return str_replace('pagseguro', 'sandbox.pagseguro', $item); + }, $config['pagseguro']['urls']); + } + + ////////////////// + /// PAYGOL SMS /// + ////////////////// + // https://www.paygol.com/ + // !!! Paygol takes 60%~ of the money, and send aprox 40% to your paypal. + // You can configure paygol to send each month, then they will send money + // to you 1 month after receiving 50+ eur. + $config['paygol'] = array( + 'enabled' => false, + 'serviceID' => 86648, // Service ID from paygol.com + 'secretKey' => 'xxxx-xxxx-xxxx-xxxx', // Secret key from paygol.com. Never share your secret key + 'currency' => 'SEK', + 'price' => 20, + 'points' => 20, + 'name' => '20 points', + 'returnURL' => "http://".$_SERVER['HTTP_HOST']."/success.php", + 'cancelURL' => "http://".$_SERVER['HTTP_HOST']."/failed.php" + ); + + //////////// + /// SHOP /// + //////////// + // If useDB is set to true, player can shop in-game as well using Znote Lua shop system plugin. + $config['shop'] = array( + 'enabled' => false, + 'loginToView' => false, // Do user need to login to see the shop offers? + 'enableShopConfirmation' => true, // Verify that user wants to buy with popup + 'useDB' => false, // Fetch offers from database, or the below config array + 'showImage' => true, + 'imageServer' => 'items.znote.eu', + 'imageType' => 'gif', + ); + + ////////// + /// Let players sell, buy and bid on characters. + /// Creates a deeper shop economy, encourages players to spend more money in shop for points. + /// Pay to win/progress mechanic, but also lets people who can barely afford points to gain it + /// by leveling characters to sell. It can also discourages illegal/risky third-party account + /// services. Since players can buy officially & support the server, dodgy competitors have to sell for cheaper. + /// Without admin interference this is organic to each individual community economy inflation. + ////////// + $config['shop_auction'] = array( + 'characterAuction' => false, // Enable/disable this system + // Account ID of the account that stores players in the auction. + // Make sure storage account has a very secure password! + 'storage_account_id' => 500000, // Separate secure account ID, not your GM. + 'step' => 5, // Minimum amount someone can raise a bid by + 'step_duration' => 1 * 60 * 60, // When bidding over someone else, extend bid period by 1 hour. + 'lowestLevel' => 20, // Minimum level of sold character + 'lowestPrice' => 10, // Lowest donation points a char can be sold for. + 'biddingDuration' => 1 * 24 * 60 * 60, // = 1 day, 0 to disable bidding + 'deposit' => 10 // Seller has to add 10=10% deposit to auction which he gets back later. + ); + + /* + type 1 = Items + type 2 = Premium days + type 3 = Change character gender + type 4 = Change character name + type 5 = Buy outfit (put outfit id as itemid), + (put addon id as count [0 = nothing, 1 = first addon, 2 = second addon, 3 = both addons]) + type 6 = Buy mount (put mount id as itemid) + type 7 = Buy house (hardcoded in the house system, type used for data log) + type 8+ = custom coded stuff + */ + $config['shop_offers'] = array( + 1 => array( + 'type' => 1, + 'itemid' => 2160, // item to get in-game + 'count' => 5, // Stack number (5x itemid) + 'description' => "5 x Crystal coin", // Description shown on website + 'points' => 100, // How many points this offer costs + ), + 2 => array( + 'type' => 1, + 'itemid' => 2392, + 'count' => 1, + 'description' => "Fire sword", + 'points' => 10, + ), + 3 => array( + 'type' => 2, + 'itemid' => 12466, // Item to display on page + 'count' => 7, // Days of premium account + 'description' => "Premium membership", + 'points' => 25, + ), + 4 => array( + 'type' => 3, + 'itemid' => 12666, // Item to display on page + 'count' => 3, + 'description' => "Change character gender", + 'points' => 10, + ), + 5 => array( + 'type' => 3, + 'itemid' => 12666, // Item to display on page + 'count' => 0, // 0 = unlimited + 'description' => "Change character gender", + 'points' => 20, + ), + 6 => array( + 'type' => 4, + 'itemid' => 12666, // Item to display on page + 'count' => 1, + 'description' => "Change character name", + 'points' => 20, + ), + 7 => array( + 'type' => 5, + 'itemid' => [132, 140], // Outfit ID + 'count' => 3, // Addon 0 = none, 1 = first, 2 = second, 3 = both + 'description' => "Noble outfit with both addons", + 'points' => 20, + ), + 8 => array( + 'type' => 6, + 'itemid' => 32, // Mount ID + 'count' => 1, + 'description' => "Gnarlhound mount", + 'points' => 20, + ), + 9 => array( + 'type' => 6, + 'itemid' => 17, + 'count' => 1, + 'description' => "War horse", + 'points' => 20, + ), + ); + + ////////////////////////// + /// OTServers.eu voting + // + // Start by creating an account at OTServers.eu and add your server. + // You can find your secret token by logging in on OTServers.eu and go to 'MY SERVER' then 'Encourage players to vote'. + $config['otservers_eu_voting'] = [ + 'enabled' => false, + 'simpleVoteUrl' => '', // This url is used if the player isn't logged in. + 'voteUrl' => 'https://api.otservers.eu/vote_link.php', + 'voteCheckUrl' => 'https://api.otservers.eu/vote_check.php', + 'secretToken' => '', // Enter your secret token. Do not share with anyone! + 'landingPage' => '/voting.php?action=reward', // The user will be redirected to this page after voting + 'points' => '1' // Amount of points to give as reward + ]; diff --git a/app/ZnoteAAC/contact.php b/app/ZnoteAAC/contact.php new file mode 100644 index 0000000..b6a5bb8 --- /dev/null +++ b/app/ZnoteAAC/contact.php @@ -0,0 +1,6 @@ + + +

    Contact

    +

    TODO: Edit the contact details here.

    + + \ No newline at end of file diff --git a/app/ZnoteAAC/createcharacter.php b/app/ZnoteAAC/createcharacter.php new file mode 100644 index 0000000..1a6dce6 --- /dev/null +++ b/app/ZnoteAAC/createcharacter.php @@ -0,0 +1,169 @@ +$value) { + if (empty($value) && in_array($key, $required_fields) === true) { + $errors[] = 'You need to fill in all fields.'; + break 1; + } + } + + // check errors (= user exist, pass long enough + if (empty($errors) === true) { + if (!Token::isValid($_POST['token'])) { + $errors[] = 'Token is invalid.'; + } + $_POST['name'] = validate_name($_POST['name']); + if ($_POST['name'] === false) { + $errors[] = 'Your name can not contain more than 2 words.'; + } else { + if (user_character_exist($_POST['name']) !== false) { + $errors[] = 'Sorry, that character name already exist.'; + } + if (!preg_match("/^[a-zA-Z ]+$/", $_POST['name'])) { + $errors[] = 'Your name may only contain a-z, A-Z and spaces.'; + } + if (strlen($_POST['name']) < $config['minL'] || strlen($_POST['name']) > $config['maxL']) { + $errors[] = 'Your character name must be between ' . $config['minL'] . ' - ' . $config['maxL'] . ' characters long.'; + } + // name restriction + $resname = explode(" ", $_POST['name']); + $username = $_POST['name']; + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } + if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + if(in_array(strtolower($username), $config['creatureNameTags'])) { + $errors[] = 'Your username contains a creature name.'; + } + // Validate vocation id + if (!in_array((int)$_POST['selected_vocation'], $config['available_vocations'])) { + $errors[] = 'Permission Denied. Wrong vocation.'; + } + // Validate town id + if (!in_array((int)$_POST['selected_town'], $config['available_towns'])) { + $errors[] = 'Permission Denied. Wrong town.'; + } + // Validate gender id + if (!in_array((int)$_POST['selected_gender'], array(0, 1))) { + $errors[] = 'Permission Denied. Wrong gender.'; + } + if (vocation_id_to_name($_POST['selected_vocation']) === false) { + $errors[] = 'Failed to recognize that vocation, does it exist?'; + } + if (town_id_to_name($_POST['selected_town']) === false) { + $errors[] = 'Failed to recognize that town, does it exist?'; + } + if (gender_exist($_POST['selected_gender']) === false) { + $errors[] = 'Failed to recognize that gender, does it exist?'; + } + // Char count + $char_count = user_character_list_count($session_user_id); + if ($char_count >= $config['max_characters'] && !is_admin($user_data)) { + $errors[] = 'Your account is not allowed to have more than '. $config['max_characters'] .' characters.'; + } + if (validate_ip(getIP()) === false && $config['validate_IP'] === true) { + $errors[] = 'Failed to recognize your IP address. (Not a valid IPv4 address).'; + } + } + } +} +?> + +

    Create Character

    + format_character_name($_POST['name']), + 'account_id'=> $session_user_id, + 'vocation' => $_POST['selected_vocation'], + 'town_id' => $_POST['selected_town'], + 'sex' => $_POST['selected_gender'], + 'lastip' => getIPLong(), + 'created' => time() + ); + + user_create_character($character_data); + header('Location: createcharacter.php?success'); + exit(); + //End register + + } else if (empty($errors) === false){ + echo ''; + echo output_errors($errors); + echo ''; + } + ?> +
    +
      +
    • + Name:
      + +
    • +
    • + + Vocation:
      + +
    • +
    • + + Gender:
      + +
    • + 1): + ?> +
    • + + Town:
      + +
    • + + + +
    • + +
    • +
    +
    + diff --git a/app/ZnoteAAC/credits.php b/app/ZnoteAAC/credits.php new file mode 100644 index 0000000..1ccd223 --- /dev/null +++ b/app/ZnoteAAC/credits.php @@ -0,0 +1,86 @@ + + +

    Znote AAC

    +

    This website is powered by the Znote AAC engine.

    +

    An OT website (Automatic Account Creator) created by Znote from the OT forum community otland.net.

    +

    Znote AAC is an open source project where everyone can help with development.

    + +

    Developers:

    + +

    See the full list of developers HERE.

    + +
    + +
    +
    Avatar of: <?php echo $developer['login']; ?>
    +

    +
    Updates:

    +
    + +
    + + + +

    Thanks to: (in no particular order)

    +

    + Chris - PHP OOP file samples, testing, bugfixing. +
    Kiwi Dan - Researching TFS 0.2 for me, participation in developement. +
    Amoaz - Pentesting and security tips. +
    Evan, Gremlee - Researching TFS 0.3, constructive feedback, suggestion and participation. +
    ATT3 - Reporting and fixing bugs, TFS 1.0 research. +
    Mark - Old repository, TFS distributions which this AAC was primarily built for. +
    Tedbro, Exura, PrinterLUA - Reporting bugs. +
    Nottinghster - OTHIRE distribution compatibility. +

    + + diff --git a/app/ZnoteAAC/deaths.php b/app/ZnoteAAC/deaths.php new file mode 100644 index 0000000..fe56a3f --- /dev/null +++ b/app/ZnoteAAC/deaths.php @@ -0,0 +1,39 @@ +hasExpired()) { + + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'TFS_10') { + $deaths = fetchLatestDeaths(); + } else if ($config['ServerEngine'] == 'TFS_03' || $config['ServerEngine'] == 'OTHIRE') { + $deaths = fetchLatestDeaths_03(30); + } + $cache->setContent($deaths); + $cache->save(); +} else { + $deaths = $cache->load(); +} +if ($deaths) { +?> +

    Latest Deaths

    + + + + + + + '; + echo ""; + echo ""; + if ($death['is_player'] == 1) echo ""; + else if ($death['is_player'] == 0) { + if ($config['ServerEngine'] == 'TFS_03') echo ""; + else echo ""; + } + else echo ""; + echo ''; + } ?> +
    VictimTimeKiller
    At level ". $death['level'] .": ". $death['victim'] ."". getClock($death['time'], true) ."Player: ". $death['killed_by'] ."Monster: ". ucfirst(str_replace("a ", "", $death['killed_by'])) ."Monster: ". ucfirst($death['killed_by']) ."". $death['killed_by'] ."
    + diff --git a/app/ZnoteAAC/downloads.php b/app/ZnoteAAC/downloads.php new file mode 100644 index 0000000..451b9bb --- /dev/null +++ b/app/ZnoteAAC/downloads.php @@ -0,0 +1,31 @@ + + +

    Downloads

    +

    In order to play, you need an compatible IP changer and a Tibia client.

    + +

    Download IP changer HERE.

    +

    Download Tibia client for windows HERE.

    +

    Download Tibia client for linux HERE.

    + +

    How to connect and play:

    +
      +
    1. + Download and install the tibia client if you havent already. +
    2. +
    3. + Download and run the IP changer. +
    4. +
    5. + In the IP changer, change Client Path to the tibia.exe file where you installed the client. +
    6. +
    7. + In the IP changer, write this in the IP field: +
    8. +
    9. + Now you can successfully login on the tibia client and play clicking on Apply.
      + If you do not have an account to login with, you need to register an account HERE. +
    10. +
    + + diff --git a/app/ZnoteAAC/engine/XML/items.xml b/app/ZnoteAAC/engine/XML/items.xml new file mode 100644 index 0000000..b9d240c --- /dev/null +++ b/app/ZnoteAAC/engine/XML/items.xml @@ -0,0 +1,36177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ZnoteAAC/engine/XML/spells.xml b/app/ZnoteAAC/engine/XML/spells.xml new file mode 100644 index 0000000..57d1fad --- /dev/null +++ b/app/ZnoteAAC/engine/XML/spells.xml @@ -0,0 +1,745 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ZnoteAAC/engine/XML/stages.xml b/app/ZnoteAAC/engine/XML/stages.xml new file mode 100644 index 0000000..beb1e51 --- /dev/null +++ b/app/ZnoteAAC/engine/XML/stages.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/ZnoteAAC/engine/cert/cacert.pem b/app/ZnoteAAC/engine/cert/cacert.pem new file mode 100644 index 0000000..d8fda7d --- /dev/null +++ b/app/ZnoteAAC/engine/cert/cacert.pem @@ -0,0 +1,3534 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Dec 12 04:12:04 2023 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 1970dd65858925d68498d2356aea6d03f764422523c5887deca8ce3ba9e1f845 +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +Security Communication RootCA3 +============================== +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw +IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD +b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw +CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE +AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r +hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE +NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2 +/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm +npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY +XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK +p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC +3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf +GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw +CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu +Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O +H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx +YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ +XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml ++LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn +KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9 +dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm +6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg== +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- + +BJCA Global Root CA1 +==================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG +EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK +Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG +A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD +DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm +CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS +sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn +P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW +yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj +eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn +MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b +OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh +GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK +H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB +AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ +dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 +60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh +TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW +4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp +GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx +4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps +3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S +SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= +-----END CERTIFICATE----- + +BJCA Global Root CA2 +==================== +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD +TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg +R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE +BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC +SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl +SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK +/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI +1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 +W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g +UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx +eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot +6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 +Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW +pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M +MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE +SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 +Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 +3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft +nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 +uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq +ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs +vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c +Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif +BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 +lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo +KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH ++VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 +5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM +3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck +jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf +Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W +NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ +o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ +oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc +1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM +6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V +rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx +7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC +e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W +Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp +M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf +hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr +eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE +VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t +Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx +cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF +1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa +MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd +gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O +HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm +YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr +dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ +iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN +lM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- diff --git a/app/ZnoteAAC/engine/database/connect.php b/app/ZnoteAAC/engine/database/connect.php new file mode 100644 index 0000000..dc23896 --- /dev/null +++ b/app/ZnoteAAC/engine/database/connect.php @@ -0,0 +1,91 @@ +Install: +
      +
    1. +

      + Make sure you have imported TFS database. (OTdir/schema.sql OR OTdir/schemas/mysql.sql OR OTdir/forgottenserver.sql) +

      +
    2. +
    3. Import the Znote AAC schema to a TFS database in phpmyadmin.
    4. +
    5. +

      + Edit config.php with correct mysql connection details. +

      +
    6. +
    +"; + +$connect = new mysqli($config['sqlHost'], $config['sqlUser'], $config['sqlPassword'], $config['sqlDatabase']); + +if ($connect->connect_errno) { + die("Failed to connect to MySQL: (" . $connect->connect_errno . ") " . $connect->connect_error . $install); +} + +function mysql_znote_escape_string($escapestr) { + global $connect; + return mysqli_real_escape_string($connect, $escapestr); +} + +// Select single row from database +function mysql_select_single($query) { + global $connect; + global $aacQueries; + $aacQueries++; + + global $accQueriesData; + $accQueriesData[] = "[" . elapsedTime() . "] " . $query; + $result = mysqli_query($connect,$query) or die(var_dump($query)."
    (query - SQL error)
    Type: select_single (select single row from database)

    ".mysqli_error($connect)); + $row = mysqli_fetch_assoc($result); + return !empty($row) ? $row : false; +} + +// Selecting multiple rows from database. +function mysql_select_multi($query){ + global $connect; + global $aacQueries; + $aacQueries++; + global $accQueriesData; + $accQueriesData[] = "[" . elapsedTime() . "] " . $query; + $array = array(); + $results = mysqli_query($connect,$query) or die(var_dump($query)."
    (query - SQL error)
    Type: select_multi (select multiple rows from database)

    ".mysqli_error($connect)); + while($row = mysqli_fetch_assoc($results)) { + $array[] = $row; + } + return !empty($array) ? $array : false; +} + +////// +// Query database without expecting returned results + +// - mysql update +function mysql_update($query){ voidQuery($query); } +// mysql insert +function mysql_insert($query){ voidQuery($query); } +// mysql delete +function mysql_delete($query){ voidQuery($query); } +// Send a void query +function voidQuery($query) { + global $connect; + global $aacQueries; + $aacQueries++; + global $accQueriesData; + $accQueriesData[] = "[" . elapsedTime() . "] " . $query; + mysqli_query($connect,$query) or die(var_dump($query)."
    (query - SQL error)
    Type: voidQuery (voidQuery is used for update, insert or delete from database)

    ".mysqli_error($connect)); +} +?> diff --git a/app/ZnoteAAC/engine/database/znote_schema.sql b/app/ZnoteAAC/engine/database/znote_schema.sql new file mode 100644 index 0000000..288f3a6 --- /dev/null +++ b/app/ZnoteAAC/engine/database/znote_schema.sql @@ -0,0 +1,312 @@ +-- Start of Znote AAC database schema + +SET @znote_version = '1.6'; + +CREATE TABLE IF NOT EXISTS `znote` ( + `id` int NOT NULL AUTO_INCREMENT, + `version` varchar(30) NOT NULL COMMENT 'Znote AAC version', + `installed` int NOT NULL, + `cached` int DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_accounts` ( + `id` int NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `ip` bigint UNSIGNED NOT NULL, + `created` int NOT NULL, + `points` int DEFAULT 0, + `cooldown` int DEFAULT 0, + `active` tinyint NOT NULL DEFAULT '0', + `active_email` tinyint NOT NULL DEFAULT '0', + `activekey` int NOT NULL DEFAULT '0', + `flag` varchar(20) NOT NULL, + `secret` char(16) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_news` ( + `id` int NOT NULL AUTO_INCREMENT, + `title` varchar(30) NOT NULL, + `text` text NOT NULL, + `date` int NOT NULL, + `pid` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_images` ( + `id` int NOT NULL AUTO_INCREMENT, + `title` varchar(30) NOT NULL, + `desc` text NOT NULL, + `date` int NOT NULL, + `status` int NOT NULL, + `image` varchar(50) NOT NULL, + `delhash` varchar(30) NOT NULL, + `account_id` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_paypal` ( + `id` int NOT NULL AUTO_INCREMENT, + `txn_id` varchar(30) NOT NULL, + `email` varchar(255) NOT NULL, + `accid` int NOT NULL, + `price` int NOT NULL, + `points` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_paygol` ( + `id` int NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `price` int NOT NULL, + `points` int NOT NULL, + `message_id` varchar(255) NOT NULL, + `service_id` varchar(255) NOT NULL, + `shortcode` varchar(255) NOT NULL, + `keyword` varchar(255) NOT NULL, + `message` varchar(255) NOT NULL, + `sender` varchar(255) NOT NULL, + `operator` varchar(255) NOT NULL, + `country` varchar(255) NOT NULL, + `currency` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_players` ( + `id` int NOT NULL AUTO_INCREMENT, + `player_id` int NOT NULL, + `created` int NOT NULL, + `hide_char` tinyint NOT NULL, + `comment` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_player_reports` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + `posx` int NOT NULL, + `posy` int NOT NULL, + `posz` int NOT NULL, + `report_description` varchar(255) NOT NULL, + `date` int NOT NULL, + `status` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_changelog` ( + `id` int NOT NULL AUTO_INCREMENT, + `text` varchar(255) NOT NULL, + `time` int NOT NULL, + `report_id` int NOT NULL, + `status` tinyint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_shop` ( + `id` int NOT NULL AUTO_INCREMENT, + `type` int NOT NULL, + `itemid` int DEFAULT NULL, + `count` int NOT NULL DEFAULT '1', + `description` varchar(255) NOT NULL, + `points` int NOT NULL DEFAULT '10', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_shop_logs` ( + `id` int NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `player_id` int NOT NULL, + `type` int NOT NULL, + `itemid` int NOT NULL, + `count` int NOT NULL, + `points` int NOT NULL, + `time` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_shop_orders` ( + `id` int NOT NULL AUTO_INCREMENT, + `account_id` int NOT NULL, + `type` int NOT NULL, + `itemid` int NOT NULL, + `count` int NOT NULL, + `time` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_visitors` ( + `id` int NOT NULL AUTO_INCREMENT, + `ip` bigint NOT NULL, + `value` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_visitors_details` ( + `id` int NOT NULL AUTO_INCREMENT, + `ip` bigint NOT NULL, + `time` int NOT NULL, + `type` tinyint NOT NULL, + `account_id` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Forum 1/3 (boards) +CREATE TABLE IF NOT EXISTS `znote_forum` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + `access` tinyint NOT NULL, + `closed` tinyint NOT NULL, + `hidden` tinyint NOT NULL, + `guild_id` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Forum 2/3 (threads) +CREATE TABLE IF NOT EXISTS `znote_forum_threads` ( + `id` int NOT NULL AUTO_INCREMENT, + `forum_id` int NOT NULL, + `player_id` int NOT NULL, + `player_name` varchar(50) NOT NULL, + `title` varchar(50) NOT NULL, + `text` text NOT NULL, + `created` int NOT NULL, + `updated` int NOT NULL, + `sticky` tinyint NOT NULL, + `hidden` tinyint NOT NULL, + `closed` tinyint NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Forum 3/3 (posts) +CREATE TABLE IF NOT EXISTS `znote_forum_posts` ( + `id` int NOT NULL AUTO_INCREMENT, + `thread_id` int NOT NULL, + `player_id` int NOT NULL, + `player_name` varchar(50) NOT NULL, + `text` text NOT NULL, + `created` int NOT NULL, + `updated` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Pending characters for deletion +CREATE TABLE IF NOT EXISTS `znote_deleted_characters` ( + `id` int NOT NULL AUTO_INCREMENT, + `original_account_id` int NOT NULL, + `character_name` varchar(255) NOT NULL, + `time` datetime NOT NULL, + `done` tinyint NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_guild_wars` ( + `id` int NOT NULL AUTO_INCREMENT, + `limit` int NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Helpdesk system +CREATE TABLE IF NOT EXISTS `znote_tickets` ( + `id` int NOT NULL AUTO_INCREMENT, + `owner` int NOT NULL, + `username` varchar(32) CHARACTER SET latin1 NOT NULL, + `subject` text CHARACTER SET latin1 NOT NULL, + `message` text CHARACTER SET latin1 NOT NULL, + `ip` bigint NOT NULL, + `creation` int NOT NULL, + `status` varchar(20) CHARACTER SET latin1 NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_tickets_replies` ( + `id` int NOT NULL AUTO_INCREMENT, + `tid` int NOT NULL, + `username` varchar(32) CHARACTER SET latin1 NOT NULL, + `message` text CHARACTER SET latin1 NOT NULL, + `created` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `znote_global_storage` ( + `key` varchar(32) NOT NULL, + `value` TEXT NOT NULL, + UNIQUE (`key`) +) ENGINE=InnoDB; + +-- Character auction system +CREATE TABLE IF NOT EXISTS `znote_auction_player` ( + `id` int NOT NULL AUTO_INCREMENT, + `player_id` int NOT NULL, + `original_account_id` int NOT NULL, + `bidder_account_id` int NOT NULL, + `time_begin` int NOT NULL, + `time_end` int NOT NULL, + `price` int NOT NULL, + `bid` int NOT NULL, + `deposit` int NOT NULL, + `sold` tinyint NOT NULL, + `claimed` tinyint NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- Populate basic info +INSERT INTO `znote` (`version`, `installed`) VALUES +(@znote_version, UNIX_TIMESTAMP(CURDATE())); + +-- Add default forum boards +INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`) VALUES +('Staff Board', '4', '0', '0', '0'), +('Tutors Board', '2', '0', '0', '0'), +('Discussion', '1', '0', '0', '0'), +('Feedback', '1', '0', '1', '0'); + +-- Convert existing accounts in database to be Znote AAC compatible +INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `flag`) +SELECT + `a`.`id` AS `account_id`, + 0 AS `ip`, + UNIX_TIMESTAMP(CURDATE()) AS `created`, + '' AS `flag` +FROM `accounts` AS `a` +LEFT JOIN `znote_accounts` AS `z` + ON `a`.`id` = `z`.`account_id` +WHERE `z`.`created` IS NULL; + +-- Convert existing players in database to be Znote AAC compatible +INSERT INTO `znote_players` (`player_id`, `created`, `hide_char`, `comment`) +SELECT + `p`.`id` AS `player_id`, + UNIX_TIMESTAMP(CURDATE()) AS `created`, + 0 AS `hide_char`, + '' AS `comment` +FROM `players` AS `p` +LEFT JOIN `znote_players` AS `z` + ON `p`.`id` = `z`.`player_id` +WHERE `z`.`created` IS NULL; + +-- Delete duplicate account records +DELETE `d` FROM `znote_accounts` AS `d` +INNER JOIN ( + SELECT `i`.`account_id`, + MAX(`i`.`id`) AS `retain` + FROM `znote_accounts` AS `i` + GROUP BY `i`.`account_id` + HAVING COUNT(`i`.`id`) > 1 +) AS `x` + ON `d`.`account_id` = `x`.`account_id` + AND `d`.`id` != `x`.`retain`; + +-- Delete duplicate player records +DELETE `d` FROM `znote_players` AS `d` +INNER JOIN ( + SELECT `i`.`player_id`, + MAX(`i`.`id`) AS `retain` + FROM `znote_players` AS `i` + GROUP BY `i`.`player_id` + HAVING COUNT(`i`.`id`) > 1 +) AS `x` + ON `d`.`player_id` = `x`.`player_id` + AND `d`.`id` != `x`.`retain`; + +-- End of Znote AAC database schema diff --git a/app/ZnoteAAC/engine/footer.php b/app/ZnoteAAC/engine/footer.php new file mode 100644 index 0000000..3398a0a --- /dev/null +++ b/app/ZnoteAAC/engine/footer.php @@ -0,0 +1,8 @@ +
    + © Znote AAC. + +
    diff --git a/app/ZnoteAAC/engine/function/cache.php b/app/ZnoteAAC/engine/function/cache.php new file mode 100644 index 0000000..80c82c9 --- /dev/null +++ b/app/ZnoteAAC/engine/function/cache.php @@ -0,0 +1,167 @@ +setExpiration($cfg['lifespan']); + if (function_exists('apcu_fetch')) { + $this->_canMemory = true; + $this->_memory = $cfg['memory']; + } + $this->_file = $file . self::EXT; + + if (!$this->_memory && $cfg['memory']) die(" +

    Configuration error! +
    Cannot save cache to memory, but it is configured to do so. +
    You need to enable PHP extension APCu to enable memory cache. +
    Install it or set \$config['cache']['memory'] to false! +
    Ubuntu install: sudo apt install php-apcu

    + "); + } + + + /** + * Sets the cache expiration limit (IMPORTANT NOTE: seconds, NOT ms!). + * + * @param integer $span + * @access public + * @return void + **/ + public function setExpiration($span) { + $this->_lifespan = $span; + } + + + /** + * Enable or disable memory RAM storage. + * + * @param bool $bool + * @access public + * @return bool $status + **/ + public function useMemory($bool) { + if ($bool and $this->_canMemory) { + $this->_memory = true; + return true; + } + $this->_memory = false; + return false; + } + + + /** + * Set the content you'd like to cache. + * + * @param mixed $content + * @access public + * @return void + **/ + public function setContent($content) { + $this->_content = (!$this->_memory && strtolower(gettype($content)) == 'array') ? json_encode($content) : $content; + } + + + /** + * Validates whether it is time to refresh the cache data or not. + * + * @access public + * @return boolean + **/ + public function hasExpired() { + if ($this->_memory) { + return !apcu_exists($this->_file); + } + if (is_file($this->_file) && time() < filemtime($this->_file) + $this->_lifespan) { + return false; + } + return true; + } + + /** + * Returns remaining time before scoreboard will update itself. + * + * @access public + * @return integer + **/ + public function remainingTime() { + $remaining = 0; + if ($this->_memory) { + if (apcu_exists($this->_file)) { + $meta = apcu_cache_info(); + foreach ($meta['cache_list'] AS $item) { + if ($item['info'] == $this->_file) { + $remaining = ($item['creation_time'] + $item['ttl']) - time(); + return ($remaining > 0) ? $remaining : 0; + } + } + } + return $remaining; + } + if (!$this->hasExpired()) { + $remaining = (filemtime($this->_file) + $this->_lifespan) - time(); + } + return $remaining; + } + + + /** + * Saves the content into its appropriate cache file. + * + * @access public + * @return void + **/ + public function save() { + if ($this->_memory) { + return apcu_store($this->_file, $this->_content, $this->_lifespan); + } + $handle = fopen($this->_file, 'w'); + fwrite($handle, $this->_content); + fclose($handle); + } + + + /** + * Loads the content from a specified cache file. + * + * @access public + * @return mixed + **/ + public function load() { + if ($this->_memory) { + return apcu_fetch($this->_file); + } + if (!is_file($this->_file)) { + return false; + } + + ob_start(); + include_once($this->_file); + $content = ob_get_clean(); + + if (!isset($content) && strlen($content) == 0) { + return false; + } + + if ($content = json_decode($content, true)) { + return (array) $content; + } else { + return $content; + } + } +} diff --git a/app/ZnoteAAC/engine/function/general.php b/app/ZnoteAAC/engine/function/general.php new file mode 100644 index 0000000..1f77f92 --- /dev/null +++ b/app/ZnoteAAC/engine/function/general.php @@ -0,0 +1,613 @@ + $getValue) { + if ($count > 0) $string .= '&'; + $string .= "{$getKey}={$getValue}"; + } + header("Location: {$location}?{$string}"); + exit(); +} + +// Sweet error reporting +function data_dump($print = false, $var = false, $title = false) { + if ($title !== false) echo "
    $title
    "; + else echo '
    ';
    +	if ($print !== false) {
    +		echo 'Print: - ';
    +		print_r($print);
    +		echo "
    "; + } + if ($var !== false) { + echo 'Var_dump: - '; + var_dump($var); + } + echo '

    '; +} + +function accountAccess($accountId, $TFS) { + $accountId = (int)$accountId; + $access = 0; + + // TFS 0.3/4 + $yourChars = mysql_select_multi("SELECT `name`, `group_id`, `account_id` FROM `players` WHERE `account_id`='$accountId';"); + if ($yourChars !== false) { + foreach ($yourChars as $char) { + if ($TFS === 'TFS_03' || $TFS === 'OTHIRE') { + if ($char['group_id'] > $access) $access = $char['group_id']; + } else { + if ($char['group_id'] > 1) { + if ($access == 0) { + $acc = mysql_select_single("SELECT `type` FROM `accounts` WHERE `id`='". $char['account_id'] ."' LIMIT 1;"); + $access = $acc['type']; + } + } + } + } + if ($access == 0) $access++; + return $access; + } else return false; + // +} +// Generate recovery key +function generate_recovery_key($lenght) { + $lenght = (int)$lenght; + $tmp = rand(1000, 9000); + $tmp += time(); + $tmp = sha1($tmp); + + $results = ''; + for ($i = 0; $i < $lenght; $i++) $results = $results.''.$tmp[$i]; + + return $results; +} + +// Calculate discount +function calculate_discount($orig, $new) { + $orig = (int)$orig; + $new = (int)$new; + + $tmp = ''; + if ($new >= $orig) { + if ($new != $orig) { + $calc = ($new/$orig) - 1; + $calc *= 100; + $tmp = '+'. floor($calc) .'%'; + } else $tmp = '0%'; + } else { + $calc = 1 - ($new/$orig); + $calc *= 100; + $tmp = '-'. floor($calc) .'%'; + } + return $tmp; +} + +// Proper URLs +function url($path = false) { + $folder = dirname($_SERVER['SCRIPT_NAME']); + return config('site_url') . '/' . $path; +} + +function getCache() { + $results = mysql_select_single("SELECT `cached` FROM `znote`;"); + return ($results !== false) ? $results['cached'] : false; +} + +function setCache($time) { + $time = (int)$time; + mysql_update("UPDATE `znote` set `cached`='$time'"); +} + +// Get visitor basic data +function znote_visitors_get_data() { + return mysql_select_multi("SELECT `ip`, `value` FROM `znote_visitors` ORDER BY `id` DESC LIMIT 1000;"); +} + +// Set visitor basic data +function znote_visitor_set_data($visitor_data) { + $exist = false; + $ip = getIPLong(); + + foreach ((array)$visitor_data as $row) { + if ($ip == $row['ip']) { + $exist = true; + $value = $row['value']; + } + } + + if ($exist && isset($value)) { + // Update the value + $value++; + mysql_update("UPDATE `znote_visitors` SET `value` = '$value' WHERE `ip` = '$ip'"); + } else { + // Insert new row + mysql_insert("INSERT INTO `znote_visitors` (`ip`, `value`) VALUES ('$ip', '1')"); + } +} + +// Get visitor basic data +function znote_visitors_get_detailed_data($cache_time) { + $period = (int)time() - (int)$cache_time; + return mysql_select_multi("SELECT `ip`, `time`, `type`, `account_id` FROM `znote_visitors_details` WHERE `time` >= '$period' LIMIT 0, 50"); +} + +function znote_visitor_insert_detailed_data($type) { + $type = (int)$type; + /* + type 0 = normal visits + type 1 = register form + type 2 = character creation + type 3 = fetch highscores + type 4 = search character + */ + $time = time(); + $ip = getIPLong(); + if (user_logged_in()) { + $acc = (int)getSession('user_id'); + mysql_insert("INSERT INTO `znote_visitors_details` (`ip`, `time`, `type`, `account_id`) VALUES ('$ip', '$time', '$type', '$acc')"); + } else mysql_insert("INSERT INTO `znote_visitors_details` (`ip`, `time`, `type`, `account_id`) VALUES ('$ip', '$time', '$type', '0')"); +} + +function something () { + // Make acc data compatible: + $ip = getIPLong(); +} + +// Secret token +function create_token() { + echo 'Checking whether to create token or not
    '; + #if (empty($_SESSION['token'])) { + echo 'Creating token
    '; + $token = sha1(uniqid(time(), true)); + $token2 = $token; + var_dump($token, $token2); + $_SESSION['token'] = $token2; + #} + + echo ""; +} +function reset_token() { + echo 'Reseting token
    '; + unset($_SESSION['token']); +} + +// Time based functions +// 60 seconds to 1 minute +function second_to_minute($seconds) { + return ($seconds / 60); +} + +// 1 minute to 60 seconds +function minute_to_seconds($minutes) { + return ($minutes * 60); +} + +// 60 minutes to 1 hour +function minute_to_hour($minutes) { + return ($minutes / 60); +} + +// 1 hour to 60 minutes +function hour_to_minute($hours) { + return ($hour * 60); +} + +// seconds / 60 / 60 = hours. +function seconds_to_hours($seconds) { + $minutes = second_to_minute($seconds); + $hours = minute_to_hour($minutes); + return $hours; +} + +function remaining_seconds_to_clock($seconds) { + return date("(H:i)",time() + $seconds); +} + +/** + * Check if name contains more than configured max words + * + * @param string $string + * @return string|boolean + */ +function validate_name($string) { + return (str_word_count(trim($string)) > config('maxW')) ? false : trim($string); +} + +// Checks if an IPv4(or localhost IPv6) address is valid +function validate_ip($ip) { + $ipL = safeIp2Long($ip); + $ipR = long2ip((int)$ipL); + + if ($ip === $ipR) { + return true; + } elseif ($ip=='::1') { + return true; + } else { + return false; + } +} + +// Fetch a config value. Etc config('vocations') will return vocation array from config.php. +function config($value) { + global $config; + return $config[$value]; +} + +// Some functions uses several configurations from config.php, so it sounds +// smarter to give them the whole array instead of calling the function all the time. +function fullConfig() { + global $config; + return $config; +} + +// Capitalize Every Word In String. +function format_character_name($name) { + return ucwords(strtolower($name)); +} + +// Gets you the actual IP address even from users behind ISP proxies and so on. +function getIP() { + /* + $IP = ''; + if (getenv('HTTP_CLIENT_IP')) { + $IP =getenv('HTTP_CLIENT_IP'); + } elseif (getenv('HTTP_X_FORWARDED_FOR')) { + $IP =getenv('HTTP_X_FORWARDED_FOR'); + } elseif (getenv('HTTP_X_FORWARDED')) { + $IP =getenv('HTTP_X_FORWARDED'); + } elseif (getenv('HTTP_FORWARDED_FOR')) { + $IP =getenv('HTTP_FORWARDED_FOR'); + } elseif (getenv('HTTP_FORWARDED')) { + $IP = getenv('HTTP_FORWARDED'); + } else { + $IP = $_SERVER['REMOTE_ADDR']; + } */ +return $_SERVER['REMOTE_ADDR']; +} + +function safeIp2Long($ip) { + return sprintf('%u', ip2long($ip)); +} + +// Gets you the actual IP address even from users in long type +function getIPLong() { + return safeIp2Long(getIP()); +} + +// Deprecated, just use count($array) instead. +function array_length($ar) { + $r = 1; + foreach($ar as $a) { + $r++; + } + return $r; +} +// Parameter: level, returns experience for that level from an experience table. +function level_to_experience($level) { + return 50/3*(pow($level, 3) - 6*pow($level, 2) + 17*$level - 12); +} + +// Parameter: players.hide_char returns: Status word inside a font with class identifier so it can be designed later on by CSS. +function hide_char_to_name($id) { + $id = (int)$id; + if ($id == 1) { + return 'hidden'; + } else { + return 'visible'; + } +} + +// Parameter: players.online returns: Status word inside a font with class identifier so it can be designed later on by CSS. +function online_id_to_name($id) { + $id = (int)$id; + if ($id == 1) { + return 'ONLINE'; + } else { + return 'offline'; + } +} + +// Parameter: players.vocation_id. Returns: Configured vocation name. +function vocation_id_to_name($id) { + $vocations = config('vocations'); + return (isset($vocations[$id]['name'])) ? $vocations[$id]['name'] : "{$id} - Unknown"; +} + +// Parameter: players.name. Returns: Configured vocation id. +function vocation_name_to_id($name) { + $vocations = config('vocations'); + foreach ($vocations as $id => $vocation) + if ($vocation['name'] == $name) + return $id; + return false; +} + +// Parameter: players.group_id. Returns: Configured group name. +function group_id_to_name($id) { + $positions = config('ingame_positions'); + return ($positions[$id] >= 0) ? $positions[$id] : false; +} + +function gender_exist($gender) { + // Range of allowed gender ids, fromid toid + if ($gender >= 0 && $gender <= 1) { + return true; + } else { + return false; + } +} + +function skillid_to_name($skillid) { + $skillname = array( + 0 => 'fist fighting', + 1 => 'club fighting', + 2 => 'sword fighting', + 3 => 'axe fighting', + 4 => 'distance fighting', + 5 => 'shielding', + 6 => 'fishing', + 7 => 'experience', // Hardcoded, does not actually exist in database as a skillid. + 8 => 'magic level' // Hardcoded, does not actually exist in database as a skillid. + ); + + return ($skillname[$skillid] >= 0) ? $skillname[$skillid] : false; +} + +// Parameter: players.town_id. Returns: Configured town name. +function town_id_to_name($id) { + $towns = config('towns'); + return (array_key_exists($id, $towns)) ? $towns[$id] : 'Missing Town'; +} + +// Unless you have an internal mail server then mail sending will not be supported in this version. +function email($to, $subject, $body) { + mail($to, $subject, $body, 'From: TEST'); +} + +function logged_in_redirect() { + if (user_logged_in() === true) { + header('Location: myaccount.php'); + } +} + +function protect_page() { + if (user_logged_in() === false) { + header('Location: protected.php'); + exit(); + } +} + +// When function is called, you will be redirected to protect_page and deny access to rest of page, as long as you are not admin. +function admin_only($user_data) { + // Chris way + $gotAccess = is_admin($user_data); + + if ($gotAccess == false) { + logged_in_redirect(); + exit(); + } +} + +function is_admin($user_data) { + if (config('ServerEngine') === 'OTHIRE') + return in_array($user_data['id'], config('page_admin_access')) ? true : false; + else + return in_array($user_data['name'], config('page_admin_access')) ? true : false; +} + +function array_sanitize(&$item) { + $item = htmlentities(strip_tags(mysql_znote_escape_string($item))); +} + +function sanitize($data) { + return htmlentities(strip_tags(mysql_znote_escape_string($data))); +} + +function output_errors($errors) { + return '
    • '. implode('
    • ', $errors) .'
    '; +} + +// Resize images + +function resize_imagex($file, $width, $height) { + + list($w, $h) = getimagesize($file['tmp']); + + $ratio = max($width/$w, $height/$h); + $h = ceil($height / $ratio); + $x = ($w - $width / $ratio) / 2; + $w = ceil($width / $ratio); + + $path = 'engine/guildimg/'.$file['new_name']; + + $imgString = file_get_contents($file['tmp']); + + $image = imagecreatefromstring($imgString); + $tmp = imagecreatetruecolor($width, $height); + imagecopyresampled($tmp, $image, + 0, 0, + $x, 0, + $width, $height, + $w, $h); + + imagegif($tmp, $path); + imagedestroy($image); + imagedestroy($tmp); + + return true; +} + +// Guild logo upload security +function check_image($image) { + + $image_data = array('new_name' => $_GET['name'].'.gif', 'name' => $image['name'], 'tmp' => $image['tmp_name'], 'error' => $image['error'], 'size' => $image['size'], 'type' => $image['type']); + + // First security check, quite useless but still do its job + if ($image_data['type'] === 'image/gif') { + + // Second security check, lets go + $check = getimagesize($image_data['tmp']); + + if ($check) { + + // Third + if ($check['mime'] === 'image/gif') { + + $path_info = pathinfo($image_data['name']); + + // Last one + if ($path_info['extension'] === 'gif') { + + // Resize image + $img = resize_imagex($image_data, 100, 100); + + if ($img) { + + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + + } else { + + header('Location: guilds.php?error=Only gif images accepted, you uploaded:['.$path_info['extension'].'].&name='. $_GET['name']); + exit(); + } + + } else { + + header('Location: guilds.php?error=Only gif images accepted, you uploaded:['.$check['mime'].'].&name='. $_GET['name']); + exit(); + } + + } else { + + header('Location: guilds.php?error=Uploaded image is invalid.&name='. $_GET['name']); + exit(); + } + + } else { + + header('Location: guilds.php?error=Only gif images are accepted, you uploaded:['.$image_data['type'].'].&name='. $_GET['name']); + exit(); + } +} + +// Check guild logo +function logo_exists($guild) { + $guild = sanitize($guild); + if (file_exists('engine/guildimg/'.$guild.'.gif')) { + + echo'engine/guildimg/'.$guild.'.gif'; + + } else { + + echo'engine/guildimg/default@logo.gif'; + } +} + +function generateRandomString($length = 16) { + $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; +} + +function verifyGoogleReCaptcha($postResponse = null) { + if(!isset($postResponse) || empty($postResponse)) { + return false; + } + + $recaptcha_api_url = 'https://www.google.com/recaptcha/api/siteverify'; + $secretKey = config('captcha_secret_key'); + $ip = $_SERVER['REMOTE_ADDR']; + $params = 'secret='.$secretKey.'&response='.$postResponse.'&remoteip='.$ip; + + $useCurl = config('captcha_use_curl'); + if($useCurl) { + $curl_connection = curl_init($recaptcha_api_url); + + curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $params); + + $response = curl_exec($curl_connection); + curl_close($curl_connection); + } else { + $response = file_get_contents($recaptcha_api_url . '?' . $params); + } + + $json = json_decode($response); + return isset($json->success) && $json->success; +} + +// html encoding function (encode any string to valid UTF-8 HTML) +function hhb_tohtml(/*string*/ $str)/*:string*/ { + return htmlentities($str, ENT_QUOTES | ENT_HTML401 | ENT_SUBSTITUTE | ENT_DISALLOWED, 'UTF-8', true); +} + +// php5-compatibile version of php7's random_bytes() +// $crypto_strong: a boolean value that determines if the algorithm used was "cryptographically strong" +function random_bytes_compat($length, &$crypto_strong = null) { + $crypto_strong = false; + if (!is_int($length)) { + throw new \InvalidArgumentException("argument 1 must be an int, is " . gettype($length)); + } + if ($length < 0) { + throw new \InvalidArgumentException("length must be >= 0"); + } + if (is_callable("random_bytes")) { + $crypto_strong = true; + return random_bytes($length); + } + if (is_callable("openssl_random_pseudo_bytes")) { + return openssl_random_pseudo_bytes($length, $crypto_strong); + } + $ret = @file_get_contents("/dev/urandom", false, null, 0, $length); + if (is_string($ret) && strlen($ret) === $length) { + $crypto_strong = true; + return $ret; + } + // fallback to non-cryptographically-secure mt_rand() implementation... + $crypto_strong = false; + $ret = ""; + for ($i = 0; $i < $length; ++$i) { + $ret .= chr(mt_rand(0, 255)); + } + return $ret; +} + +// hash_equals legacy support < 5.6 +if(!function_exists('hash_equals')) { + function hash_equals($str1, $str2) { + if(strlen($str1) != strlen($str2)) { + return false; + } + $res = $str1 ^ $str2; + $ret = 0; + for($i = strlen($res) - 1; $i >= 0; $i--) { + $ret |= ord($res[$i]); + } + return !$ret; + } +} +?> diff --git a/app/ZnoteAAC/engine/function/itemparser/itemlistparser.php b/app/ZnoteAAC/engine/function/itemparser/itemlistparser.php new file mode 100644 index 0000000..61b3080 --- /dev/null +++ b/app/ZnoteAAC/engine/function/itemparser/itemlistparser.php @@ -0,0 +1,34 @@ + 'name' + $items = getItemList(); + echo $items[2160]; // Returns 'Crystal Coin' +*/ + +function getItemList() { + return parseItems(); +} + +function getItemById($id) { + $items = parseItems(); + if(isset($items[$id])) { + return $items[$id]; + } + return false; +} + +function parseItems() { + $file = Config('server_path') . '/data/items/items.xml'; + if (file_exists($file)) { + $itemList = array(); + $items = simplexml_load_file($file); + // Create our parsed item list + foreach ($items->children() as $item) { + if ($item['id'] && $item['name'] != NULL) { + $itemList[(int)$item['id']] = (string)$item['name']; + } + } + return $itemList; + } + return $file; +} +?> diff --git a/app/ZnoteAAC/engine/function/mail.php b/app/ZnoteAAC/engine/function/mail.php new file mode 100644 index 0000000..d604261 --- /dev/null +++ b/app/ZnoteAAC/engine/function/mail.php @@ -0,0 +1,99 @@ +_config = $config; + } + + /** + * Sets the cache expiration limit (IMPORTANT NOTE: seconds, NOT ms!). + * + * @param string $to, string $title, string $text, string $accname + * @access public + * @return boolean + **/ + public function sendMail($to, $title, $text, $accname = '') { + //SMTP needs accurate times, and the PHP time zone MUST be set + //This should be done in your php.ini, but this is how to do it if you don't have access to that + //date_default_timezone_set('Etc/UTC'); + + require_once __DIR__.'/../../PHPMailer/src/Exception.php'; + require_once __DIR__.'/../../PHPMailer/src/PHPMailer.php'; + require_once __DIR__.'/../../PHPMailer/src/SMTP.php'; + + //Create a new PHPMailer instance + $mail = new PHPMailer(); + + //Tell PHPMailer to use SMTP + $mail->isSMTP(); + + //Enable SMTP debugging + // 0 = off (for production use) + // 1 = client messages + // 2 = client and server messages + $mail->SMTPDebug = ($this->_config['debug']) ? 2 : 0; + + //Ask for HTML-friendly debug output + $mail->Debugoutput = 'html'; + + //Set the hostname of the mail server + $mail->Host = $this->_config['host']; + + //Set the SMTP port number - likely to be 25, 465 or 587 + $mail->Port = $this->_config['port']; + + //Whether to use SMTP authentication + $mail->SMTPAuth = true; + $mail->SMTPSecure = $this->_config['securityType']; + + //Username to use for SMTP authentication + $mail->Username = $this->_config['username']; + + //Password to use for SMTP authentication + $mail->Password = $this->_config['password']; + + //Set who the message is to be sent from + $mail->setFrom($this->_config['email'], $this->_config['fromName']); + + //Set who the message is to be sent to + $mail->addAddress($to, $accname); + + //Set the subject line + $mail->Subject = $title; + + // Body + $mail->Body = $text; + + // Convert HTML -> plain for legacy mail recievers + // Create new lines instead of
    html tags. + $text = str_replace("
    ", "\n", $text); + $text = str_replace("", "\n", $text); + $text = str_replace("
    ", "\n", $text); + // Then get rid of the rest of the html tags. + $text = strip_tags($text); + + //Replace the plain text body with one created manually + $mail->AltBody = $text; + + + //send the message, check for errors + $status = false; + if (!$mail->send()) { + echo "Mailer Error: " . $mail->ErrorInfo; + exit(); + } else { + $status = true; + } + return $status; + } +} diff --git a/app/ZnoteAAC/engine/function/rfc6238.php b/app/ZnoteAAC/engine/function/rfc6238.php new file mode 100644 index 0000000..28dabdd --- /dev/null +++ b/app/ZnoteAAC/engine/function/rfc6238.php @@ -0,0 +1,285 @@ +'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7', + 'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15', + 'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23', + 'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31' + ); + + /** + * Use padding false when encoding for urls + * + * @return base32 encoded string + * @author Bryan Ruiz + **/ + public static function encode($input, $padding = true) { + if(empty($input)) return ""; + + $input = str_split($input); + $binaryString = ""; + + for($i = 0; $i < count($input); $i++) { + $binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT); + } + + $fiveBitBinaryArray = str_split($binaryString, 5); + $base32 = ""; + $i=0; + + while($i < count($fiveBitBinaryArray)) { + $base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)]; + $i++; + } + + if($padding && ($x = strlen($binaryString) % 40) != 0) { + if($x == 8) $base32 .= str_repeat(self::$map[32], 6); + else if($x == 16) $base32 .= str_repeat(self::$map[32], 4); + else if($x == 24) $base32 .= str_repeat(self::$map[32], 3); + else if($x == 32) $base32 .= self::$map[32]; + } + + return $base32; + } + + public static function decode($input) { + if(empty($input)) return; + + $paddingCharCount = substr_count($input, self::$map[32]); + $allowedValues = array(6,4,3,1,0); + + if(!in_array($paddingCharCount, $allowedValues)) return false; + + for($i=0; $i<4; $i++){ + if($paddingCharCount == $allowedValues[$i] && + substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false; + } + + $input = str_replace('=','', $input); + $input = str_split($input); + $binaryString = ""; + + for($i=0; $i < count($input); $i = $i+8) { + $x = ""; + + if(!in_array($input[$i], self::$map)) return false; + + for($j=0; $j < 8; $j++) { + $x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + + $eightBits = str_split($x, 8); + + for($z = 0; $z < count($eightBits); $z++) { + $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; + } + } + + return $binaryString; + } +} + +// http://www.faqs.org/rfcs/rfc6238.html +// https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/rfc6238.php +// Local changes: http -> https, consistent indentation, 200x200 -> 300x300 QR image size, PHP end tag +class TokenAuth6238 { + + /** + * verify + * + * @param string $secretkey Secret clue (base 32). + * @return bool True if success, false if failure + */ + public static function verify($secretkey, $code, $rangein30s = 3) { + $key = base32static::decode($secretkey); + $unixtimestamp = time()/30; + + for($i=-($rangein30s); $i<=$rangein30s; $i++) { + $checktime = (int)($unixtimestamp+$i); + $thiskey = self::oath_hotp($key, $checktime); + + if ((int)$code == self::oath_truncate($thiskey,6)) { + return true; + } + + } + return false; + } + + + public static function getTokenCode($secretkey,$rangein30s = 3) { + $result = ""; + $key = base32static::decode($secretkey); + $unixtimestamp = time()/30; + + for($i=-($rangein30s); $i<=$rangein30s; $i++) { + $checktime = (int)($unixtimestamp+$i); + $thiskey = self::oath_hotp($key, $checktime); + $result = $result." # ".self::oath_truncate($thiskey,6); + } + + return $result; + } + + public static function getTokenCodeDebug($secretkey,$rangein30s = 3) { + $result = ""; + print "
    SecretKey: $secretkey
    "; + + $key = base32static::decode($secretkey); + print "Key(base 32 decode): $key
    "; + + $unixtimestamp = time()/30; + print "UnixTimeStamp (time()/30): $unixtimestamp
    "; + + for($i=-($rangein30s); $i<=$rangein30s; $i++) { + $checktime = (int)($unixtimestamp+$i); + print "Calculating oath_hotp from (int)(unixtimestamp +- 30sec offset): $checktime basing on secret key
    "; + + $thiskey = self::oath_hotp($key, $checktime, true); + print "======================================================
    "; + print "CheckTime: $checktime oath_hotp:".$thiskey."
    "; + + $result = $result." # ".self::oath_truncate($thiskey,6,true); + } + + return $result; + } + + public static function getBarCodeUrl($username, $domain, $secretkey, $issuer) { + $url = "https://chart.apis.google.com/chart"; + $url = $url."?chs=300x300&chld=M|0&cht=qr&chl=otpauth://totp/"; + $url = $url.$username . "@" . $domain . "%3Fsecret%3D" . $secretkey . '%26issuer%3D' . rawurlencode($issuer); + return $url; + } + + public static function generateRandomClue($length = 16) { + $b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM"; + $s = ""; + + for ($i = 0; $i < $length; $i++) + $s .= $b32[rand(0,31)]; + + return $s; + } + + private static function hotp_tobytestream($key) { + $result = array(); + $last = strlen($key); + for ($i = 0; $i < $last; $i = $i + 2) { + $x = $key[$i] + $key[$i + 1]; + $x = strtoupper($x); + $x = hexdec($x); + $result = $result.chr($x); + } + + return $result; + } + + private static function oath_hotp ($key, $counter, $debug=false) { + $result = ""; + $orgcounter = $counter; + $cur_counter = array(0,0,0,0,0,0,0,0); + + if ($debug) { + print "Packing counter $counter (".dechex($counter).")into binary string - pay attention to hex representation of key and binary representation
    "; + } + + for($i=7;$i>=0;$i--) { // C for unsigned char, * for repeating to the end of the input data + $cur_counter[$i] = pack ('C*', $counter); + + if ($debug) { + print $cur_counter[$i]."(".dechex(ord($cur_counter[$i])).")"." from $counter
    "; + } + + $counter = $counter >> 8; + } + + if ($debug) { + foreach ($cur_counter as $char) { + print ord($char) . " "; + } + + print "
    "; + } + + $binary = implode($cur_counter); + + // Pad to 8 characters + str_pad($binary, 8, chr(0), STR_PAD_LEFT); + + if ($debug) { + print "Prior to HMAC calculation pad with zero on the left until 8 characters.
    "; + print "Calculate sha1 HMAC(Hash-based Message Authentication Code http://en.wikipedia.org/wiki/HMAC).
    "; + print "hash_hmac ('sha1', $binary, $key)
    "; + } + + $result = hash_hmac ('sha1', $binary, $key); + + if ($debug) { + print "Result: $result
    "; + } + + return $result; + } + + private static function oath_truncate($hash, $length = 6, $debug=false) { + $result=""; + + // Convert to dec + if($debug) { + print "converting hex hash into characters
    "; + } + + $hashcharacters = str_split($hash,2); + + if($debug) { + print_r($hashcharacters); + print "
    and convert to decimals:
    "; + } + + for ($j=0; $j"; + print "offset:".$offset; + } + + $result = ( + (($hmac_result[$offset+0] & 0x7f) << 24 ) | + (($hmac_result[$offset+1] & 0xff) << 16 ) | + (($hmac_result[$offset+2] & 0xff) << 8 ) | + ($hmac_result[$offset+3] & 0xff) + ) % pow(10,$length); + + return $result; + } +} +?> diff --git a/app/ZnoteAAC/engine/function/token.php b/app/ZnoteAAC/engine/function/token.php new file mode 100644 index 0000000..870114a --- /dev/null +++ b/app/ZnoteAAC/engine/function/token.php @@ -0,0 +1,89 @@ +'; + } + + + /** + * Returns the active token, if there is one. + * + * @access public + * @static true + * @return mixed + **/ + public static function get() { + return isset($_SESSION['token']) ? $_SESSION['token'] : false; + } + + + /** + * Validates whether the active token is valid or not. + * + * @param string $post + * @access public + * @static true + * @return boolean + **/ + public static function isValid($post) { + if (config('use_token')) { + // Token doesn't exist yet, return false. + if (!self::get()) { + return false; + } + + // Token was invalid, return false. + if ($post == $_SESSION['old_token'] || $post == $_SESSION['token']) { + //self::_reset(); + return true; + } else { + return false; + } + } else { + return true; + } + } + + + /** + * Destroys the active token. + * + * @access protected + * @static true + * @return void + **/ + protected static function _reset() { + unset($_SESSION['token']); + } + + + /** + * Displays information on both the post token and the session token. + * + * @param string $post + * @access public + * @static true + * @return void + **/ + public static function debug($post) { + echo '
    ', var_dump(array(
    +				'post' => $post,
    +				'old_token' => $_SESSION['old_token'],
    +				'token' => self::get()
    +			)), '
    '; + } + } +?> diff --git a/app/ZnoteAAC/engine/function/users.php b/app/ZnoteAAC/engine/function/users.php new file mode 100644 index 0000000..f922d6c --- /dev/null +++ b/app/ZnoteAAC/engine/function/users.php @@ -0,0 +1,1768 @@ + 1 ORDER BY `p`.`account_id` DESC, `p`.`group_id` ASC, `p`.`level` ASC;"); + else $staffs = mysql_select_multi("SELECT `a`.`type` as `group_id`, `p`.`name`, `p`.`online`, `p`.`account_id` FROM `players` AS `p` INNER JOIN `accounts` AS `a` ON `a`.`id` = `p`.`account_id` WHERE `a`.`type` > 1 ORDER BY `p`.`account_id` DESC, `p`.`group_id` ASC, `p`.`level` ASC;"); + if ($staffs !== false) { + foreach($staffs as $k => $v) { + foreach($staffs as $key => $value) { + if($k != $key && $v['account_id'] == $value['account_id']) { + unset($staffs[$k]); + } + } + } + $staffs = array_values($staffs); + if ($TFS == 'TFS_10') { + for ($i = 0; $i < count($staffs); $i++) { + // Fix online status on TFS 1.0 + $staffs[$i]['online'] = (isset($staffs[$i]['id']) && user_is_online_10($staffs[$i]['id'])) ? 1 : 0; + unset($staffs[$i]['id']); + } + } + } + return $staffs; +} + +function support_list03() { + $staffs = mysql_select_multi("SELECT `group_id`, `name`, `online`, `account_id` FROM `players` WHERE `group_id` > 1 ORDER BY `group_id` ASC;"); + + if ($staffs !== false) { + for ($i = 0; $i < count($staffs); $i++) { + // $staffs[$i][''] + unset($staffs[$i]['account_id']); + } + } + return $staffs; +} + +// NEWS +function fetchAllNews() { + return mysql_select_multi("SELECT `n`.`id`, `n`.`title`, `n`.`text`, `n`.`date`, `p`.`name` FROM `znote_news` AS `n` INNER JOIN `players` AS `p` ON `n`.`pid` = `p`.`id` ORDER BY `n`.`id` DESC;"); +} + +// HOUSES +function fetchAllHouses_03() { + return mysql_select_multi("SELECT * FROM `houses`;"); +} + +// TFS Storage value functions (Warning, I think these things are saved in cache, +// and thus require server to be offline, or affected players to be offline while using) + +// Get player storage list +function getPlayerStorageList($storage, $minValue) { + $minValue = (int)$minValue; + $storage = (int)$storage; + return mysql_select_multi("SELECT `player_id`, `value` FROM `player_storage` WHERE `key`='$storage' AND `value`>='$minValue' ORDER BY `value` DESC;"); +} + +// Get global storage value +function getGlobalStorage($storage) { + $storage = (int)$storage; + return mysql_select_single("SELECT `value` FROM `global_storage` WHERE `key`='$storage';"); +} + +// Set global storage value +function setGlobalStorage($storage, $value) { + $storage = (int)$storage; + $value = (int)$value; + + // If the storage does not exist yet + if (getGlobalStorage($storage) === false) { + mysql_insert("INSERT INTO `global_storage` (`key`, `world_id`, `value`) VALUES ('$storage', 0, '$value')"); + } else {// If the storage exist + mysql_update("UPDATE `global_storage` SET `value`='$value' WHERE `key`='$storage'"); + } +} + +// Get player storage value. +function getPlayerStorage($player_id, $storage, $online = false) { + if ($online) $online = user_is_online($player_id); + if (!$online) { + // user is offline (false), we may safely proceed: + $player_id = (int)$player_id; + $storage = (int)$storage; + return mysql_select_single("SELECT `value` FROM `player_storage` WHERE `key`='$storage' AND `player_id`='$player_id';"); + } else return false; +} + +// Set player storage value +function setPlayerStorage($player_id, $storage, $value) { + $storage = (int)$storage; + $value = (int)$value; + $player_id = (int)$player_id; + + // If the storage does not exist yet + if (getPlayerStorage($storage) === false) { + mysql_insert("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES ('$player_id', '$storage', '$value')"); + } else {// If the storage exist + mysql_update("UPDATE `player_storage` SET `value`='$value' WHERE `key`='$storage' AND `player_id`='$player_id'"); + } +} + +// Is player online +function user_is_online($player_id) { + $status = user_character_data($player_id, 'online'); + if ($status !== false) { + if ($status['online'] == 1) $status = true; + else $status = false; + } + return $status; +} +// For TFS 1.0 +function user_is_online_10($player_id) { + $player_id = (int)$player_id; + $status = mysql_select_single("SELECT `player_id` FROM `players_online` WHERE `player_id`='$player_id' LIMIT 1;"); + return !$status ? $status : true; +} + +// Shop +// Gets a list of tickets and ticket ids +function shop_delete_row_order($rowid) { + $rowid = (int)$rowid; + mysql_delete("DELETE FROM `znote_shop_orders` WHERE `id`='$rowid';"); +} + +function shop_update_row_count($rowid, $count) { + $rowid = (int)$rowid; + $count = (int)$count; + mysql_update("UPDATE `znote_shop_orders` SET `count`='$count' WHERE `id`='$rowid'"); +} + +function shop_account_gender_tickets($accid) { + $accid = (int)$accid; + return mysql_select_multi("SELECT `id`, `count` FROM `znote_shop_orders` WHERE `account_id`='$accid' AND `type`='3';"); +} + +// GUILDS +// +function guild_remove_member($cid) { + $cid = (int)$cid; + mysql_update("UPDATE `players` SET `rank_id`='0', `guildnick`= NULL WHERE `id`=$cid"); +} +function guild_remove_member_10($cid) { + $cid = (int)$cid; + mysql_update("DELETE FROM `guild_membership` WHERE `player_id`='$cid' LIMIT 1;"); +} + +// Change guild rank name. +function guild_change_rank($rid, $name) { + $rid = (int)$rid; + $name = sanitize($name); + + mysql_update("UPDATE `guild_ranks` SET `name`='$name' WHERE `id`=$rid"); +} + +// Change guild leader (parameters: cid, new and old leader). +function guild_change_leader($nCid, $oCid) { + $nCid = (int)$nCid; + $oCid = (int)$oCid; + $gid = guild_leader_gid($oCid); + $ranks = get_guild_rank_data($gid); + $leader_rid = 0; + $vice_rid = 0; + + + // Get rank id for leader and vice leader. + foreach ($ranks as $rank) { + if ($rank['level'] == 3) $leader_rid = $rank['id']; + if ($rank['level'] == 2) $vice_rid = $rank['id']; + } + + $status = false; + if ($leader_rid > 0 && $vice_rid > 0) $status = true; + + // Verify that we found the rank ids for vice leader and leader. + if ($status) { + + // Update players and set their new rank id + if (config('ServerEngine') !== 'TFS_10') { + mysql_update("UPDATE `players` SET `rank_id`='$leader_rid' WHERE `id`=$nCid LIMIT 1;"); + mysql_update("UPDATE `players` SET `rank_id`='$vice_rid' WHERE `id`=$oCid LIMIT 1;"); + } else { + mysql_update("UPDATE `guild_membership` SET `rank_id`='$leader_rid' WHERE `player_id`=$nCid LIMIT 1;"); + mysql_update("UPDATE `guild_membership` SET `rank_id`='$vice_rid' WHERE `player_id`=$oCid LIMIT 1;"); + } + + // Update guilds set new ownerid + guild_new_leader($nCid, $gid); + } + + return $status; +} + +// Changes leadership of aguild to player_id +function guild_new_leader($new_leader, $gid) { + $new_leader = (int)$new_leader; + $gid = (int)$gid; + if (config('ServerEngine') !== 'OTHIRE') + mysql_update("UPDATE `guilds` SET `ownerid`='$new_leader' WHERE `id`=$gid"); + else + mysql_update("UPDATE `guilds` SET `owner_id`='$new_leader' WHERE `id`=$gid"); +} + +// Returns $gid of a guild leader($cid). +function guild_leader_gid($leader) { + $leader = (int)$leader; + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `id` FROM `guilds` WHERE `ownerid`='$leader';"); + else + $data = mysql_select_single("SELECT `id` FROM `guilds` WHERE `owner_id`='$leader';"); + return ($data === false) ? false : $data['id']; +} + +// Returns guild leader(charID) of a guild. (parameter: guild_ID) +function guild_leader($gid) { + $gid = (int)$gid; + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `ownerid` FROM `guilds` WHERE `id`='$gid';"); + else + $data = mysql_select_single("SELECT `owner_id` FROM `guilds` WHERE `id`='$gid';"); + return ($data !== false) ? $data['ownerid'] : false; +} + +// Disband guild +function guild_remove_invites($gid) { + $gid = (int)$gid; + mysql_delete("DELETE FROM `guild_invites` WHERE `guild_id`='$gid';"); +} + +// Remove guild invites +function guild_delete($gid) { + $gid = (int)$gid; + mysql_delete("DELETE FROM `guilds` WHERE `id`='$gid';"); +} + +// Player leave guild +function guild_player_leave($cid) { + $cid = (int)$cid; + mysql_update("UPDATE `players` SET `rank_id`='0', `guildnick`= NULL WHERE `id`=$cid LIMIT 1;"); +} +function guild_player_leave_10($cid) { + $cid = (int)$cid; + mysql_delete("DELETE FROM `guild_membership` WHERE `player_id`='$cid' LIMIT 1;"); +} + +// Player join guild +function guild_player_join($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + // Create a status we can return depending on results. + $status = false; + + if (config('ServerEngine') !== 'TFS_10') { + // Get rank data + $ranks = get_guild_rank_data($gid); + // Locate rank id for regular member position in this guild + $rid = false; + foreach ($ranks as $rank) { + if ($rank['level'] == 1) $rid = $rank['id']; + } + // Add to guild if rank id was found: + if ($rid != false) { + // Remove the invite: + //guild_remove_invitation($cid, $gid); + guild_remove_all_invitations($cid); + // Add to guild: + mysql_update("UPDATE `players` SET `rank_id`='$rid' WHERE `id`=$cid"); + $status = true; + } + + } else { + // Find rank id for regular member in this guild + $guildrank = mysql_select_single("SELECT `id` FROM `guild_ranks` WHERE `guild_id`='$gid' AND `level`='1' LIMIT 1;"); + if ($guildrank !== false) { + $rid = $guildrank['id']; + // Remove invite + //guild_remove_invitation($cid, $gid); + guild_remove_all_invitations($cid); + // Add to guild + mysql_insert("INSERT INTO `guild_membership` (`player_id`, `guild_id`, `rank_id`, `nick`) VALUES ('$cid', '$gid', '$rid', '');"); + // Return success + return true; + } return false; + } + return $status; +} + +// Remove cid invitation from guild (gid) +function guild_remove_invitation($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + mysql_delete("DELETE FROM `guild_invites` WHERE `player_id`='$cid' AND `guild_id`='$gid';"); +} + +// Remove ALL invitations +function guild_remove_all_invitations($cid) { + $cid = (int)$cid; + mysql_delete("DELETE FROM `guild_invites` WHERE `player_id`='$cid';"); +} + +// Invite character to guild +function guild_invite_player($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + mysql_insert("INSERT INTO `guild_invites` (`player_id`, `guild_id`) VALUES ('$cid', '$gid')"); +} + +// Gets a list of invited players to a particular guild. +function guild_invite_list($gid) { + $gid = (int)$gid; + return mysql_select_multi("SELECT `gi`.`player_id`, `gi`.`guild_id`, `p`.`name` FROM `guild_invites` AS `gi` INNER JOIN `players` AS `p` ON `gi`.`player_id`=`p`.`id` WHERE `gi`.`guild_id`='$gid';"); +} + +// Update player's guild position +function update_player_guild_position($cid, $rid) { + $cid = (int)$cid; + $rid = (int)$rid; + mysql_update("UPDATE `players` SET `rank_id`='$rid' WHERE `id`=$cid"); +} +function update_player_guild_position_10($cid, $rid) { + $cid = (int)$cid; + $rid = (int)$rid; + mysql_update("UPDATE `guild_membership` SET `rank_id`='$rid' WHERE `player_id`=$cid"); +} + +// Update player's guild nick +function update_player_guildnick($cid, $nick) { + $cid = (int)$cid; + $nick = sanitize($nick); + if (!empty($nick)) { + mysql_update("UPDATE `players` SET `guildnick`='$nick' WHERE `id`=$cid"); + } else { + mysql_update("UPDATE `players` SET `guildnick`='' WHERE `id`=$cid"); + } +} +function update_player_guildnick_10($cid, $nick) { + $cid = (int)$cid; + $nick = sanitize($nick); + if (!empty($nick)) { + mysql_update("UPDATE `guild_membership` SET `nick`='$nick' WHERE `player_id`=$cid"); + } else { + mysql_update("UPDATE `guild_membership` SET `nick`='' WHERE `player_id`=$cid"); + } +} + +// Get guild data, using guild id. +function get_guild_rank_data($gid) { + $gid = (int)$gid; + return mysql_select_multi("SELECT `id`, `guild_id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id`='$gid' ORDER BY `id` DESC LIMIT 0, 30"); +} + +// Creates a guild, where cid is the owner of the guild, and name is the name of guild. +function create_guild($cid, $name) { + $cid = (int)$cid; + $name = trim(preg_replace('/\s\s+/', ' ', str_replace("\n", " ", sanitize($name)))); + $time = time(); + + // Create the guild + if (config('ServerEngine') !== 'OTHIRE') + mysql_insert("INSERT INTO `guilds` (`name`, `ownerid`, `creationdata`, `motd`) VALUES ('$name', '$cid', '$time', '');"); + else + mysql_insert("INSERT INTO `guilds` (`name`, `owner_id`, `creationdate`) VALUES ('$name', '$cid', '$time');"); + + // Get guild id + $gid = get_guild_id($name); + + // Get rank id for guild leader + $data = mysql_select_single("SELECT `id` FROM `guild_ranks` WHERE `guild_id`='$gid' AND `level`='3' LIMIT 1;"); + $rid = ($data !== false) ? $data['id'] : false; + + // Give player rank id for leader of his guild + if (config('ServerEngine') !== 'TFS_10') mysql_update("UPDATE `players` SET `rank_id`='$rid' WHERE `id`='$cid' LIMIT 1;"); + else mysql_insert("INSERT INTO `guild_membership` (`player_id`, `guild_id`, `rank_id`, `nick`) VALUES ('$cid', '$gid', '$rid', '');"); +} + +// Search player table on cid for his rank_id, returns rank_id +function get_character_guild_rank($cid) { + $cid = (int)$cid; + if (config('ServerEngine') !== 'TFS_10') { + $data = mysql_select_single("SELECT `rank_id` FROM `players` WHERE `id`='$cid';"); + return ($data !== false && $data['rank_id'] > 0) ? $data['rank_id'] : false; + } else { + $data = mysql_select_single("SELECT `rank_id` FROM `guild_membership` WHERE `player_id`='$cid' LIMIT 1;"); + return ($data !== false) ? $data['rank_id'] : false; + } +} + +// Get a player guild rank, using his rank_id +function get_player_guild_rank($rank_id) { + $rank_id = (int)$rank_id; + $data = mysql_select_single("SELECT `name` FROM `guild_ranks` WHERE `id`=$rank_id LIMIT 1;"); + return ($data !== false) ? $data['name'] : false; +} + +// Get a player guild position ID, using his rank_id +function get_guild_position($rid) { + $rid = (int)$rid; + $data = mysql_select_single("SELECT `level` FROM `guild_ranks` WHERE `id`=$rid;"); + return ($data !== false) ? $data['level'] : false; +} + +// Get a players rank_id, guild_id, rank_level(ID), rank_name(string), using cid(player id) +function get_player_guild_data($cid) { + $cid = (int)$cid; + if (config('ServerEngine') !== 'TFS_10') $playerdata = mysql_select_single("SELECT `rank_id` FROM `players` WHERE `id`='$cid' LIMIT 1;"); + else $playerdata = mysql_select_single("SELECT `rank_id` FROM `guild_membership` WHERE `player_id`='$cid' LIMIT 1;"); + + if ($playerdata !== false) { + $rankdata = mysql_select_single("SELECT `guild_id`, `level` AS `rank_level`, `name` AS `rank_name` FROM `guild_ranks` WHERE `id`='". $playerdata['rank_id'] ."' LIMIT 1;"); + if ($rankdata !== false) { + $rankdata['rank_id'] = $playerdata['rank_id']; + return $rankdata; + } else return false; + } else return false; +} + +// Returns guild name of guild id +function get_guild_name($gid) { + $gid = (int)$gid; + $guild = mysql_select_single("SELECT `name` FROM `guilds` WHERE `id`=$gid LIMIT 1;"); + if ($guild !== false) return $guild['name']; + else return false; +} + +// Returns guild id from name +function get_guild_id($name) { + $name = sanitize($name); + $data = mysql_select_single("SELECT `id` FROM `guilds` WHERE `name`='$name';"); + return ($data !== false) ? $data['id'] : false; +} + +// Returns guild data from name +function get_guild_data($name) { + $name = sanitize($name); + if (config('ServerEngine') !== 'OTHIRE') + return mysql_select_single("SELECT `id`, `name`, `ownerid`, `creationdata`, `motd` FROM `guilds` WHERE `name`='$name' LIMIT 1;"); + else + return mysql_select_single("SELECT `id`, `name`, `owner_id`, `creationdate` FROM `guilds` WHERE `name`='$name' LIMIT 1;"); +} + +// Get complete list of guilds +function get_guilds_list() { + if (config('ServerEngine') !== 'OTHIRE') + return mysql_select_multi("SELECT `id`, `name`, `creationdata` FROM `guilds` ORDER BY `name`;"); + else + return mysql_select_multi("SELECT `id`, `name`, `creationdate` FROM `guilds` ORDER BY `name`;"); +} + +// Get array of player data related to a guild. +function get_guild_players($gid) { + $gid = (int)$gid; // Sanitizing the parameter id + if (config('ServerEngine') !== 'TFS_10') return mysql_select_multi("SELECT `p`.`id`, `p`.`rank_id`, `p`.`name`, `p`.`level`, `p`.`guildnick`, `p`.`vocation`, `p`.`online`, `gr`.`name` AS `rank_name`, `gr`.`level` AS `rank_level` FROM `players` AS `p` LEFT JOIN `guild_ranks` AS `gr` ON `gr`.`id` = `p`.`rank_id` WHERE `gr`.`guild_id` ='$gid' ORDER BY `gr`.`id`, `p`.`name`;"); + else return mysql_select_multi("SELECT `p`.`id`, `p`.`name`, `p`.`level`, `p`.`vocation`, `gm`.`rank_id`, `gm`.`nick` AS `guildnick`, `gr`.`name` AS `rank_name`, `gr`.`level` AS `rank_level` FROM `players` AS `p` LEFT JOIN `guild_membership` AS `gm` ON `gm`.`player_id` = `p`.`id` LEFT JOIN `guild_ranks` AS `gr` ON `gr`.`id` = `gm`.`rank_id` WHERE `gm`.`guild_id` = '$gid' ORDER BY `gm`.`rank_id`, `p`.`name`"); +} + +// Get guild level data (avg level, total level, count of players) +function get_guild_level_data($gid) { + $gid = (int)$gid; + $data = (config('ServerEngine') !== 'TFS_10') ? mysql_select_multi("SELECT p.level FROM players AS p LEFT JOIN guild_ranks AS gr ON gr.id = p.rank_id WHERE gr.guild_id ='$gid';") : mysql_select_multi("SELECT p.level FROM players AS p LEFT JOIN guild_membership AS gm ON gm.player_id = p.id WHERE gm.guild_id = '$gid' ORDER BY gm.rank_id, p.name;"); + $members = 0; + $totallevels = 0; + if ($data !== false) { + foreach ($data as $player) { + $members++; + $totallevels += $player['level']; + } + return array('avg' => (int)($totallevels / $members), 'total' => $totallevels, 'players' => $members); + } else return false; +} + +// Returns total members in a guild (integer) +function count_guild_members($gid) { + $gid = (int)$gid; + if (config('ServerEngine') !== 'TFS_10') { + $data = mysql_select_single("SELECT COUNT(p.id) AS total FROM players AS p LEFT JOIN guild_ranks AS gr ON gr.id = p.rank_id WHERE gr.guild_id =$gid"); + return ($data !== false) ? $data['total'] : false; + } else { + $data = mysql_select_single("SELECT COUNT('guild_id') AS `total` FROM `guild_membership` WHERE `guild_id`='$gid';"); + return ($data !== false) ? $data['total'] : false; + } +} + +// +// GUILD WAR +// +// Returns guild war entry for id +function get_guild_war($warid) { + $warid = (int)$warid; // Sanitizing the parameter id + return mysql_select_single("SELECT `id`, `guild1`, `guild2`, `name1`, `name2`, `status`, `started`, `ended` FROM `guild_wars` WHERE `id`=$warid ORDER BY `started`;"); +} + +// TFS 0.3 compatibility +function get_guild_war03($warid) { + $warid = (int)$warid; // Sanitizing the parameter id + + $war = mysql_select_single("SELECT `id`, `guild_id`, `enemy_id`, `status`, `begin`, `end` + FROM `guild_wars` WHERE `id`=$warid ORDER BY `begin` DESC LIMIT 0, 30"); + if ($war !== false) { + $war['guild1'] = $war['guild_id']; + $war['guild2'] = $war['enemy_id']; + $war['name1'] = get_guild_name($war['guild_id']); + $war['name2'] = get_guild_name($war['enemy_id']); + $war['started'] = $war['begin']; + $war['ended'] = $war['end']; + } + return $war; +} + +// List all war entries +function get_guild_wars() { + return mysql_select_multi("SELECT `id`, `guild1`, `guild2`, `name1`, `name2`, `status`, `started`, `ended` FROM `guild_wars` ORDER BY `started` DESC LIMIT 0, 30"); +} + +// Untested. (TFS 0.3 compatibility) +function get_guild_wars03() { + $array = mysql_select_multi("SELECT `id`, `guild_id`, `enemy_id`, `status`, `begin`, `end` FROM `guild_wars` ORDER BY `begin` DESC LIMIT 0, 30"); + if ($array !== false) { + for ($i = 0; $i < count($array); $i++) { + // Generating TFS 0.2 key values for this 0.3 query for web cross compatibility + $array[$i]['guild1'] = $array[$i]['guild_id']; + $array[$i]['guild2'] = $array[$i]['enemy_id']; + $array[$i]['name1'] = get_guild_name($array[$i]['guild_id']); + $array[$i]['name2'] = get_guild_name($array[$i]['enemy_id']); + $array[$i]['started'] = $array[$i]['begin']; + $array[$i]['ended'] = $array[$i]['end']; + } + } + return $array; +} + +// List kill activity in wars. +function get_war_kills($war_id) { + $war_id = (int)$war_id;// Sanitize - verify its an integer. + return mysql_select_multi("SELECT `id`, `killer`, `target`, `killerguild`, `targetguild`, `warid`, `time` FROM `guildwar_kills` WHERE `warid`=$war_id ORDER BY `time` DESC"); +} + +// TFS 0.3 compatibility +function get_war_kills03($war_id) { + $war_id = (int)$war_id;// Sanitize - verify its an integer. + return mysql_select_multi("SELECT `id`, `guild_id`, `war_id`, `death_id` FROM `guild_kills` WHERE `war_id`=$war_id ORDER BY `id` DESC LIMIT 0, 30"); +} + +// Gesior compatibility port TFS .3 +function gesior_sql_death($warid) { + $warid = (int)$warid; // Sanitizing the parameter id + return mysql_select_multi('SELECT `pd`.`id`, `pd`.`date`, `gk`.`guild_id` AS `enemy`, `p`.`name`, `pd`.`level` FROM `guild_kills` gk LEFT JOIN `player_deaths` pd ON `gk`.`death_id` = `pd`.`id` LEFT JOIN `players` p ON `pd`.`player_id` = `p`.`id` WHERE `gk`.`war_id` = ' . $warid . ' AND `p`.`deleted` = 0 ORDER BY `pd`.`date` DESC'); +} +function gesior_sql_killer($did) { + $did = (int)$did; // Sanitizing the parameter id + return mysql_select_multi('SELECT `p`.`name` AS `player_name`, `p`.`deleted` AS `player_exists`, `k`.`war` AS `is_war` FROM `killers` k LEFT JOIN `player_killers` pk ON `k`.`id` = `pk`.`kill_id` LEFT JOIN `players` p ON `p`.`id` = `pk`.`player_id` WHERE `k`.`death_id` = ' . $did . ' ORDER BY `k`.`final_hit` DESC, `k`.`id` ASC'); +} +// end gesior +// END GUILD WAR + +// ADMIN FUNCTIONS +function set_ingame_position($name, $acctype) { + $acctype = (int)$acctype; + $name = sanitize($name); + + $acc_id = user_character_account_id($name); + $char_id = user_character_id($name); + + $group_id = 1; + if ($acctype == 4) { + $group_id = 2; + } elseif ($acctype >= 5) { + $group_id = 3; + } + mysql_update("UPDATE `accounts` SET `type` = '$acctype' WHERE `id` =$acc_id;"); + mysql_update("UPDATE `players` SET `group_id` = '$group_id' WHERE `id` =$char_id;"); +} + +// .3 +function set_ingame_position03($name, $acctype) { + $acctype = (int)$acctype; + $name = sanitize($name); + + $acc_id = user_character_account_id($name); + $char_id = user_character_id($name); + + $group_id = 2; + if ($acctype == 1) { + $group_id = 1; + } + mysql_update("UPDATE `players` SET `group_id` = '$acctype' WHERE `id` =$char_id;"); +} + +// Set rule violation. +// Return true if success, query error die if failed, and false if $config['website_char'] is not recognized. +function set_rule_violation($charname, $typeid, $actionid, $reasonid, $time, $comment) { + $charid = user_character_id($charname); + $typeid = (int)$typeid; + $actionid = (int)$actionid; + $reasonid = (int)$reasonid; + $time = (int)($time + time()); + + $data = user_character_data($charid, 'account_id', 'lastip'); + + $accountid = $data['account_id']; + $charip = $data['lastip']; + + $comment = sanitize($comment); + + // ... + $bannedby = config('website_char'); + if (user_character_exist($bannedby)) { + $bannedby = user_character_id($bannedby); + + if (Config('ServerEngine') === 'TFS_02') + mysql_insert("INSERT INTO `bans` (`type` ,`ip` ,`mask` ,`player` ,`account` ,`time` ,`reason_id` ,`action_id` ,`comment` ,`banned_by`) VALUES ('$typeid', '$charip', '4294967295', '$charid', '$accountid', '$time', '$reasonid', '$actionid', '$comment', '$bannedby');"); + elseif (Config('ServerEngine') === 'TFS_03') { + $now = time(); + switch ($typeid) { + case 1: // IP ban + mysql_insert("INSERT INTO `bans` (`type`, `value`, `param`, `active`, `expires`, `added`, `admin_id`, `comment`) VALUES ('$typeid', '$charip', '4294967295', '1', '$time', '$now', '$bannedby', '$comment');"); + break; + + case 2: // namelock + mysql_insert("INSERT INTO `bans` (`type`, `value`, `param`, `active`, `expires`, `added`, `admin_id`, `comment`) VALUES ('$typeid', '$charid', '4294967295', '1', '$time', '$now', '$bannedby', '$comment');"); + break; + + case 3: // acc ban + mysql_insert("INSERT INTO `bans` (`type`, `value`, `param`, `active`, `expires`, `added`, `admin_id`, `comment`) VALUES ('$typeid', '$accountid', '4294967295', '1', '$time', '$now', '$bannedby', '$comment');"); + break; + + case 4: // notation + mysql_insert("INSERT INTO `bans` (`type`, `value`, `param`, `active`, `expires`, `added`, `admin_id`, `comment`) VALUES ('$typeid', '$charid', '4294967295', '1', '$time', '$now', '$bannedby', '$comment');"); + break; + + case 5: // deletion + mysql_insert("INSERT INTO `bans` (`type`, `value`, `param`, `active`, `expires`, `added`, `admin_id`, `comment`) VALUES ('$typeid', '$charid', '4294967295', '1', '$time', '$now', '$bannedby', '$comment');"); + break; + } + } + elseif (Config('ServerEngine') === 'TFS_10') { + $now = time(); + + switch ($typeid) { + case 1: // IP ban + mysql_insert("INSERT INTO `ip_bans` (`ip`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ('$charip', '$comment', '$now', '$time', '$bannedby');"); + break; + + case 2: // namelock + mysql_insert("INSERT INTO `player_namelocks` (`player_id`, `reason`, `namelocked_at`, `namelocked_by`) VALUES ('$charid', 'comment', '$now', '$bannedby');"); + break; + + case 3: // acc ban + mysql_insert("INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ('$accountid', '$comment', '$now', '$time', '$bannedby');"); + break; + + case 4: // notation + data_dump(false, array('status' => false), "Function deprecated. Ban option does not exist in TFS 1.0."); + die(); + break; + + case 5: // deletion + data_dump(false, array('status' => false), "Function deprecated. Ban option does not exist in TFS 1.0."); + die(); + break; + } + } + + return true; + } else { + return false; + } +} + +// -- END admin + +// Fetch deathlist +function user_fetch_deathlist($char_id) { + $char_id = (int)$char_id; + return mysql_select_multi("SELECT * FROM `player_deaths` WHERE `player_id`='$char_id' order by `time` DESC LIMIT 0, 10"); +} + +// TFS .3 compatibility +function user_fetch_deathlist03($char_id) { + $char_id = (int)$char_id; + $data = mysql_select_multi("SELECT * FROM `player_deaths` WHERE `player_id`='$char_id' order by `date` DESC LIMIT 0, 10"); + if ($data !== false) { + for ($i = 0; $i < count($data); $i++) { + $data[$i]['time'] = $data[$i]['date']; + } + } + return $data; +} + +// same (death id ---> killer id) +function user_get_kid($did) { + $did = (int)$did; + $data = mysql_select_single("SELECT `id` FROM `killers` WHERE `death_id`='$did';"); + return ($data !== false) ? $data['id'] : false; +} +// same (killer id ---> player id) +function user_get_killer_id($kn) { + $kn = (int)$kn; + $data = mysql_select_single("SELECT `player_id` FROM `player_killers` WHERE `kill_id`='$kn';"); + return ($data !== false) ? $data['player_id'] : false; +} +// same (killer id ---> monster name) +function user_get_killer_m_name($mn) { + $mn = (int)$mn; + $data = mysql_select_single("SELECT `name` FROM `environment_killers` WHERE `kill_id`='$mn';"); + return ($data !== false) ? $data['name'] : false; +} + +// Count character deaths. Counts up 10. +function user_count_deathlist($char_id) { + $char_id = (int)$char_id; + $data = mysql_select_single("SELECT COUNT('id') AS `id` FROM `player_deaths` WHERE `player_id`='$char_id' order by `time` DESC LIMIT 0, 10"); + return ($data !== false) ? $data['id'] : false; +} + +// MY ACCOUNT RELATED \\ +function user_update_comment($char_id, $comment) { + $char_id = sanitize($char_id); + $comment = sanitize($comment); + mysql_update("UPDATE `znote_players` SET `comment`='$comment' WHERE `player_id`='$char_id'"); +} + +// Permamently delete character id. (parameter: character id) +function user_delete_character($char_id) { + $char_id = (int)$char_id; + mysql_delete("DELETE FROM `players` WHERE `id`='$char_id';"); + mysql_delete("DELETE FROM `znote_players` WHERE `player_id`='$char_id';"); +} + +// Delete character with supplied id with a delay. +function user_delete_character_soft($char_id) { + $char_id = (int)$char_id; + + $char_name = user_character_name($char_id); + $original_acc_id = user_character_account_id($char_name); + if(!user_character_pending_delete($char_name)) + mysql_insert('INSERT INTO `znote_deleted_characters`(`original_account_id`, `character_name`, `time`, `done`) VALUES(' . $original_acc_id . ', "' . $char_name . '", (NOW() + INTERVAL ' . config('delete_character_interval') . '), 0)'); + else + return false; +} + +// Check if character will be deleted soon. +function user_character_pending_delete($char_name) { + $char_name = sanitize($char_name); + $result = mysql_select_single('SELECT `done` FROM `znote_deleted_characters` WHERE `character_name` = "' . $char_name . '"'); + return ($result === false) ? false : !$result['done']; +} + +// Get pending character deletes for supplied account id. +function user_pending_deletes($acc_id) { + $acc_id = (int)$acc_id; + return mysql_select_multi('SELECT `id`, `character_name`, `time` FROM `znote_deleted_characters` WHERE `original_account_id` = ' . $acc_id . ' AND `done` = 0'); +} + +// Parameter: accounts.id returns: An array containing detailed information of every character on the account. +function user_character_list($account_id) { + //$count = user_character_list_count($account_id); + $account_id = (int)$account_id; + + if (config('ServerEngine') == 'TFS_10') { + $characters = mysql_select_multi("SELECT `p`.`id`, `p`.`name`, `p`.`level`, `p`.`vocation`, `p`.`town_id`, `p`.`lastlogin`, `gm`.`rank_id`, `po`.`player_id` AS `online` FROM `players` AS `p` LEFT JOIN `guild_membership` AS `gm` ON `p`.`id`=`gm`.`player_id` LEFT JOIN `players_online` AS `po` ON `p`.`id`=`po`.`player_id` WHERE `p`.`account_id`='$account_id' ORDER BY `p`.`level` DESC"); + if ($characters !== false) { + for ($i = 0; $i < count($characters); $i++) { + $characters[$i]['online'] = ($characters[$i]['online'] > 0) ? 1 : 0; + //unset($characters[$i]['id']); + } + } + + } else $characters = mysql_select_multi("SELECT `id`, `name`, `level`, `vocation`, `town_id`, `lastlogin`, `online`, `rank_id` FROM `players` WHERE `account_id`='$account_id' ORDER BY `level` DESC"); + + if ($characters !== false) { + $count = count($characters); + for ($i = 0; $i < $count; $i++) { + $characters[$i]['vocation'] = vocation_id_to_name($characters[$i]['vocation']); // Change vocation id to vocation name + $characters[$i]['town_id'] = town_id_to_name($characters[$i]['town_id']); // Change town id to town name + + // Make lastlogin human read-able. + if ($characters[$i]['lastlogin'] != 0) { + $characters[$i]['lastlogin'] = getClock($characters[$i]['lastlogin'], true, false); + } else { + $characters[$i]['lastlogin'] = 'Never.'; + } + + $characters[$i]['online'] = online_id_to_name($characters[$i]['online']); // 0 to "offline", 1 to "ONLINE". + } + } + + return $characters; +} + +// Returns an array containing all(up to 30) player_IDs an account have. (parameter: account_ID). +function user_character_list_player_id($account_id) { + //$count = user_character_list_count($account_id); + $account_id = sanitize($account_id); + return mysql_select_multi("SELECT `id` FROM `players` WHERE `account_id`='$account_id' ORDER BY `level` DESC LIMIT 0, 30"); +} + +// Parameter: accounts.id returns: number of characters on the account. +function user_character_list_count($account_id) { + $account_id = sanitize($account_id); + $data = mysql_select_single("SELECT COUNT('id') AS `id` FROM `players` WHERE `account_id`='$account_id'"); + return ($data !== false) ? $data['id'] : 0; +} + +// END MY ACCOUNT RELATED + +// HIGHSCORE FUNCTIONS \\ +function fetchAllScores($rows, $tfs, $g, $vlist, $v = -1, $flags = false, $outfits = false) { + if (config('ServerEngine') !== 'OTHIRE') { + if (config('client') < 780) { + $outfits = ($outfits) ? ", `p`.`lookbody` AS `body`, `p`.`lookfeet` AS `feet`, `p`.`lookhead` AS `head`, `p`.`looklegs` AS `legs`, `p`.`looktype` AS `type`" : ""; + } else { + $outfits = ($outfits) ? ", `p`.`lookbody` AS `body`, `p`.`lookfeet` AS `feet`, `p`.`lookhead` AS `head`, `p`.`looklegs` AS `legs`, `p`.`looktype` AS `type`, `p`.`lookaddons` AS `addons`" : ""; + } + } else { + $outfits = ($outfits) ? ", `p`.`lookbody` AS `body`, `p`.`lookfeet` AS `feet`, `p`.`lookhead` AS `head`, `p`.`looklegs` AS `legs`, `p`.`looktype` AS `type`" : ""; + } + // Return scores ordered by type and vocation (if set) + $data = array(); + + // Add "all" as a simulated vocation in vocation_list to represent all vocations and loop through them. + $vocGroups = array( + 'all' => array() + ); + foreach ($vlist AS $vid => $vdata) { + // If this vocation does not have a fromVoc attribute + if ($vdata['fromVoc'] === false) { + // Add it as a group + $vocGroups[(string)$vid] = array(); + } else { + // Add an extended group for both vocations + $sharedGroup = (string)$vdata['fromVoc'] . ", " . (string)$vid; + $vocGroups[$sharedGroup] = array(); + + // Make the fromVoc group a reference to the extended group for both vocations + $vocGroups[(string)$vdata['fromVoc']] = $sharedGroup; + $vocGroups[(string)$vid] = $sharedGroup; + } + } + + foreach ($vocGroups as $voc_id => $key_or_arr) { + + $vGrp = $voc_id; + // Change to correct vocation group if this vocation id reference a shared vocation group + if (!is_array($key_or_arr)) $vGrp = $key_or_arr; + + // If this vocation group is empty (then we need to fill it with highscore SQL Data) + if (empty($vocGroups[$vGrp])) { + + // Generate SQL WHERE-clause for vocation if $v is set + $v = ''; + if ($vGrp !== 'all') + $v = (strpos($vGrp, ',') !== false) ? 'AND `p`.`vocation` IN ('. $vGrp . ')' : 'AND `p`.`vocation` = \''.intval($vGrp).'\''; + + if ($tfs == 'TFS_10') { + + if ($flags === false) { // In this case we only need to query players table + $v = str_replace('`p`.', '', $v); + $outfits = str_replace('`p`.', '', $outfits); + + $vocGroups[$vGrp][1] = mysql_select_multi("SELECT `name`, `vocation`, `skill_club` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_club` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][2] = mysql_select_multi("SELECT `name`, `vocation`, `skill_sword` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_sword` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][3] = mysql_select_multi("SELECT `name`, `vocation`, `skill_axe` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_axe` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][4] = mysql_select_multi("SELECT `name`, `vocation`, `skill_dist` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_dist` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][5] = mysql_select_multi("SELECT `name`, `vocation`, `skill_shielding` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_shielding` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][6] = mysql_select_multi("SELECT `name`, `vocation`, `skill_fishing` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_fishing` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][7] = mysql_select_multi("SELECT `name`, `vocation`, `experience`, `level` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `experience` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][8] = mysql_select_multi("SELECT `name`, `vocation`, `maglevel` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `maglevel` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][9] = mysql_select_multi("SELECT `name`, `vocation`, `skill_fist` AS `value` $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `skill_fist` DESC LIMIT 0, $rows;"); + + } else { // Inner join znote_accounts table to retrieve the flag + $vocGroups[$vGrp][1] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_club` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_club` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][2] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_sword` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_sword` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][3] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_axe` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_axe` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][4] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_dist` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_dist` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][5] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_shielding` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_shielding` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][6] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_fishing` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_fishing` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][7] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`experience`, `level` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`experience` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][8] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`maglevel` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`maglevel` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][9] = mysql_select_multi("SELECT `p`.`name`, `p`.`vocation`, `p`.`skill_fist` AS `value`, `za`.`flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`skill_fist` DESC LIMIT 0, $rows;"); + } + } else { // TFS 0.2, 0.3, 0.4 + + if ($flags === false) { + $vocGroups[$vGrp][9] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 0 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][1] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 1 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][2] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 2 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][3] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 3 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][4] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 4 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][5] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 5 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][6] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation` $outfits FROM `player_skills` AS `s` LEFT JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` WHERE `s`.`skillid` = 6 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $v = str_replace('`p`.', '', $v); + $outfits = str_replace('`p`.', '', $outfits); + $vocGroups[$vGrp][7] = mysql_select_multi("SELECT `id`, `name`, `vocation`, `experience`, `level` AS `value` $outfits $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `experience` DESC limit 0, $rows;"); + $vocGroups[$vGrp][8] = mysql_select_multi("SELECT `id`, `name`, `vocation`, `maglevel` AS `value` $outfits $outfits FROM `players` WHERE `group_id` < $g $v ORDER BY `maglevel` DESC limit 0, $rows;"); + + } else { // Inner join znote_accounts table to retrieve the flag + $vocGroups[$vGrp][9] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 0 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][1] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 1 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][2] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 2 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][3] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 3 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][4] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 4 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][5] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 5 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][6] = mysql_select_multi("SELECT `s`.`player_id` AS `id`, `s`.`value` AS `value`, `p`.`name` AS `name`, `p`.`vocation` AS `vocation`, `za`.`flag` AS `flag` $outfits FROM `player_skills` AS `s` INNER JOIN `players` AS `p` ON `s`.`player_id`=`p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `s`.`skillid` = 6 AND `p`.`group_id` < $g $v ORDER BY `s`.`value` DESC LIMIT 0, $rows;"); + $vocGroups[$vGrp][7] = mysql_select_multi("SELECT `p`.`id`, `p`.`name`, `p`.`vocation`, `p`.`experience`, `p`.`level` AS `value`, `za`.`flag` AS `flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`experience` DESC limit 0, $rows;"); + $vocGroups[$vGrp][8] = mysql_select_multi("SELECT `p`.`id`, `p`.`name`, `p`.`vocation`, `p`.`maglevel` AS `value`, `za`.`flag` AS `flag` $outfits FROM `players` AS `p` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id`=`za`.`account_id` WHERE `p`.`group_id` < $g $v ORDER BY `p`.`maglevel` DESC limit 0, $rows;"); + } + } + } + } + return $vocGroups; +} +// END HIGHSCORE FUNCTIONS + +function user_recover($mode, $edom, $email, $character, $ip) { +/* -- Lost account function - user_recovery -- + + $mode = username/password recovery definition + $edom = The remembered value. (if mode is username, edom is players password and vice versa) + $email = email address + $character = character name + $ip = character IP +*/ + // Structure verify array data correctly + if (config('ServerEngine') !== 'OTHIRE') { + if ($mode === 'username') { + $verify_data = array( + 'password' => sha1($edom), + 'email' => $email + ); + } else { + $verify_data = array( + 'name' => $edom, + 'email' => $email + ); + } + } else { + if ($mode === 'username') { + $verify_data = array( + 'password' => sha1($edom), + 'email' => $email + ); + } else { + $verify_data = array( + 'id' => $edom, + 'email' => $email + ); + } + } + // Determine if the submitted information is correct and herit from same account + if (user_account_fields_verify_value($verify_data)) { + + // Structure account id fetch method correctly + if ($mode == 'username') { + $account_id = user_account_id_from_password($verify_data['password']); + } else { + if (config('ServerEngine') !== 'OTHIRE') + $account_id = user_id($verify_data['name']); + else + $account_id = user_id($verify_data['id']); + } + // get account id from character name + $player_account_id = user_character_account_id($character); + + //Verify that players.account_id matches account.id + if ($player_account_id == $account_id) { + // verify IP match (IP = accounts.email_new) \\ + // Fetch IP data + $ip_data = user_znote_account_data($account_id, 'ip'); + if ($ip == $ip_data['ip']) { + // IP Match, time to stop verifying SHIT and get on + // With giving the visitor his goddamn username/password! + if ($mode == 'username') { + if (config('ServerEngine') !== 'OTHIRE') { + $name_data = user_data($account_id, 'name'); + echo '

    Your username is:

    '. $name_data['name'] .'

    '; + } else { + $name_data = user_data($account_id, 'id'); + echo '

    Your account number is:

    '. $name_data['id'] .'

    '; + } + } else { + $newpass = substr(sha1(rand(1000000, 99999999)), 8); + echo '

    Your new password is:

    '. $newpass .'

    Remember to login and change it!

    '; + user_change_password($account_id, $newpass); + } + // END?! no, but almost. :) + } else { echo'IP does not match.'; } + } else { echo'Account data does not match.'; } + } else { echo'Account data does not match.'; } +} + +// Get account id from password. This can be inaccurate considering several people may have same password. +function user_account_id_from_password($password) { + $password = sanitize($password); + $tmp = mysql_select_single("SELECT `id` FROM `accounts` WHERE `password`='".$password."' LIMIT 1;"); + return $tmp['id']; +} + +// Get account name from id. +function user_account_id_from_name($id) { + $id = (int)$id;; + if (config('ServerEngine') !== 'OTHIRE') { + $result = mysql_select_single("SELECT `name` FROM `accounts` WHERE `id` = '" . $id . "' LIMIT 1;"); + return $result['name']; + } else { + $result = mysql_select_single("SELECT `id` FROM `accounts` WHERE `id` = '" . $id . "' LIMIT 1;"); + return $result['id']; + } +} + +// Add additional premium days to account id +function user_account_add_premdays($accid, $days) { + global $tfs_10_hasPremDays; // Initialized in engine/init.php + $accid = (int)$accid; + $days = (int)$days; + + if (config('ServerEngine') !== 'OTHIRE') { + if ($tfs_10_hasPremDays) { + if (mysql_select_single("SHOW COLUMNS from `accounts` WHERE `Field` = 'lastday'") === false) { + mysql_update("UPDATE `accounts` SET `premdays` = `premdays`+{$days} WHERE `id`='{$accid}'"); + } else { + mysql_update(" UPDATE `accounts` + SET `premdays` = `premdays`+{$days} + ,`lastday` = GREATEST(`lastday`,UNIX_TIMESTAMP()) + ({$days} * 86400) + WHERE `id`='{$accid}' + "); + } + } else { + mysql_update(" UPDATE `accounts` + SET `premium_ends_at` = GREATEST(`premium_ends_at`, UNIX_TIMESTAMP()) + ({$days} * 86400) + WHERE `id`='{$accid}'; + "); + } + } else { + $data = mysql_select_single("SELECT `premend` FROM `accounts` WHERE `id`='$accid';"); + $tmp = $data['premend']; + if($tmp == 0) + $tmp = time() + ($days * 24 * 60 * 60); + else + $tmp = $tmp + ($days * 24 * 60 * 60); + mysql_update("UPDATE `accounts` SET `premend`='$tmp' WHERE `id`='$accid'"); + } +} + +// Name = char name. Changes from male to female & vice versa. +function user_character_change_gender($name) { + $user_id = user_character_id($name); + $data = mysql_select_single("SELECT `sex` FROM `players` WHERE `id`='$user_id';"); + $gender = $data['sex']; + if ($gender == 1) mysql_update("UPDATE `players` SET `sex`='0' WHERE `id`='$user_id'"); + else mysql_update("UPDATE `players` SET `sex`='1' WHERE `id`='$user_id'"); +} + +// Fetch account ID from player NAME +function user_character_account_id($character) { + $character = sanitize($character); + $data = mysql_select_single("SELECT `account_id` FROM `players` WHERE `name`='$character';"); + return ($data !== false) ? $data['account_id'] : false; +} + +// Verify data from accounts table. Parameter is an array of - +// etc array('id' = 4, 'password' = 'test') will verify that logged in user have id 4 and password test. +function user_account_fields_verify_value($verify_data) { + $verify = array(); + array_walk($verify_data, 'array_sanitize'); + + foreach ($verify_data as $field=>$data) { + $verify[] = '`'. $field .'` = \''. $data .'\''; + } + $data = mysql_select_single("SELECT COUNT('id') AS `count` FROM `accounts` WHERE ". implode(' AND ', $verify) .";"); + return ($data !== false && $data['count'] == 1) ? true : false; +} + +// Update accounts, make sure user is logged in first. +function user_update_account($update_data) { + $update = array(); + array_walk($update_data, 'array_sanitize'); + + foreach ($update_data as $field=>$data) { + $update[] = '`'. $field .'` = \''. $data .'\''; + } + + $user_id = (int)getSession('user_id'); + + mysql_update("UPDATE `accounts` SET ". implode(', ', $update) ." WHERE `id`=". $user_id .";"); +} + +// Update znote_accounts table, make sure user is logged in for this. This is used to etc update lastIP +function user_update_znote_account($update_data) { + $update = array(); + array_walk($update_data, 'array_sanitize'); + + foreach ($update_data as $field=>$data) { + $update[] = '`'. $field .'` = \''. $data .'\''; + } + + $user_id = (int)getSession('user_id'); + + mysql_update("UPDATE `znote_accounts` SET ". implode(', ', $update) ." WHERE `account_id`=". $user_id .";"); +} + +// Change password on account_id (Note: You should verify that he knows the old password before doing this) +function user_change_password($user_id, $password) { + $user_id = sanitize($user_id); + $password = sha1($password); + + mysql_update("UPDATE `accounts` SET `password`='$password' WHERE `id`=$user_id"); +} +// .3 compatibility +function user_change_password03($user_id, $password) { + if (config('salt') === true) { + $user_id = sanitize($user_id); + $salt = user_data($user_id, 'salt'); + $password = sha1($salt['salt'].$password); + + mysql_update("UPDATE `accounts` SET `password`='$password' WHERE `id`=$user_id"); + } else { + user_change_password($user_id, $password); + } +} + +// Parameter: players.id, value[0 or 1]. Togge hide. +function user_character_set_hide($char_id, $value) { + $char_id = sanitize($char_id); + $value = sanitize($value); + + mysql_update("UPDATE `znote_players` SET `hide_char`='$value' WHERE `player_id`=$char_id"); +} + +// CREATE ACCOUNT +function user_create_account($register_data, $maildata) { + array_walk($register_data, 'array_sanitize'); + + if (config('ServerEngine') == 'TFS_03' && config('salt') === true) { + $register_data['salt'] = generate_recovery_key(18); + $register_data['password'] = sha1($register_data['salt'].$register_data['password']); + } else $register_data['password'] = sha1($register_data['password']); + + $ip = $register_data['ip']; + $created = $register_data['created']; + $flag = $register_data['flag']; + + unset($register_data['ip']); + unset($register_data['created']); + unset($register_data['flag']); + + if (config('ServerEngine') == 'TFS_10') $register_data['creation'] = $created; + + $fields = '`'. implode('`, `', array_keys($register_data)) .'`'; + $data = '\''. implode('\', \'', $register_data) .'\''; + + mysql_insert("INSERT INTO `accounts` ($fields) VALUES ($data)"); + + $account_id = (isset($register_data['name'])) ? user_id($register_data['name']) : user_id($register_data['id']); + $activeKey = rand(100000000,999999999); + $active = ($maildata['register']) ? 0 : 1; + mysql_insert("INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `active`, `active_email`, `activekey`, `flag`) VALUES ('$account_id', '$ip', '$created', '$active', '0', '$activeKey', '$flag')"); + + if ($maildata['register']) { + + $thisurl = config('site_url') . "$_SERVER[REQUEST_URI]"; + $thisurl .= "?authenticate&u=".$account_id."&k=".$activeKey; + + $mailer = new Mail($maildata); + + $title = "Please authenticate your account at $_SERVER[HTTP_HOST]."; + + $body = "

    Please click on the following link to authenticate your account:

    "; + $body .= "

    $thisurl

    "; + $body .= "

    Thank you for registering and enjoy your stay at $maildata[fromName].

    "; + $body .= "

    I am an automatic no-reply e-mail. Any emails sent back to me will be ignored.

    "; + + $mailer->sendMail($register_data['email'], $title, $body, $register_data['name']); + } +} + +// CREATE CHARACTER +function user_create_character($character_data) { + array_walk($character_data, 'array_sanitize'); + $cnf = fullConfig(); + + $vocation = (int)$character_data['vocation']; + $playercnf = $cnf['player']; + $base = $playercnf['base']; + $create = $playercnf['create']; + $skills = $create['skills'][$vocation]; + + $outfit = ($character_data['sex'] == 1) ? $create['male_outfit'] : $create['female_outfit']; + + $leveldiff = $create['level'] - $base['level']; + + $gains = $cnf['vocations_gain'][$vocation]; + + $health = $base['health'] + ( $gains['hp'] * $leveldiff ); + $mana = $base['mana'] + ( $gains['mp'] * $leveldiff ); + $cap = $base['cap'] + ( $gains['cap'] * $leveldiff ); + + // This is TFS 0.2 compatible import data with Znote AAC mysql schema + if (config('ServerEngine') !== 'OTHIRE') { + $import_data = array( + 'name' => $character_data['name'], + 'group_id' => 1, + 'account_id' => $character_data['account_id'], + 'level' => $create['level'], + 'vocation' => $vocation, + 'health' => $health, + 'healthmax' => $health, + 'experience' => level_to_experience($create['level']), + 'lookbody' => $outfit['body'], /* STARTER OUTFITS */ + 'lookfeet' => $outfit['feet'], + 'lookhead' => $outfit['head'], + 'looklegs' => $outfit['legs'], + 'looktype' => $outfit['id'], + 'lookaddons' => 0, + 'maglevel' => $skills['magic'], + 'mana' => $mana, + 'manamax' => $mana, + 'manaspent' => 0, + 'soul' => $base['soul'], + 'town_id' => $character_data['town_id'], + 'posx' => $cnf['default_pos']['x'], + 'posy' => $cnf['default_pos']['y'], + 'posz' => $cnf['default_pos']['z'], + 'conditions' => '', + 'cap' => $cap, + 'sex' => $character_data['sex'], + 'lastlogin' => 0, + 'lastip' => $character_data['lastip'], + 'save' => 1, + 'skull' => 0, + 'skulltime' => 0, + 'rank_id' => 0, + 'guildnick' => '', + 'lastlogout' => 0, + 'blessings' => 0, + 'direction' => 0, + 'loss_experience' => 10, + 'loss_mana' => 10, + 'loss_skills' => 10, + 'premend' => 0, + 'online' => 0, + 'balance' => 0 + ); + } else { + $import_data = array( + 'name' => $character_data['name'], + 'group_id' => 1, + 'account_id' => $character_data['account_id'], + 'level' => $create['level'], + 'vocation' => $vocation, + 'health' => $health, + 'healthmax' => $health, + 'experience' => level_to_experience($create['level']), + 'lookbody' => $outfit['body'], /* STARTER OUTFITS */ + 'lookfeet' => $outfit['feet'], + 'lookhead' => $outfit['head'], + 'looklegs' => $outfit['legs'], + 'looktype' => $outfit['id'], + 'maglevel' => $skills['magic'], + 'mana' => $mana, + 'manamax' => $mana, + 'manaspent' => 0, + 'soul' => $base['soul'], + 'town_id' => $character_data['town_id'], + 'posx' => $cnf['default_pos']['x'], + 'posy' => $cnf['default_pos']['y'], + 'posz' => $cnf['default_pos']['z'], + 'conditions' => '', + 'cap' => $cap, + 'sex' => $character_data['sex'], + 'lastlogin' => 0, + 'lastip' => $character_data['lastip'], + 'save' => 1, + 'skull_type' => 0, + 'skull_time' => 0, + 'rank_id' => 0, + 'guildnick' => '', + 'lastlogout' => 0, + 'direction' => 0, + 'loss_experience' => 100, + 'loss_mana' => 100, + 'loss_skills' => 100, + 'loss_items' => 10, + 'online' => 0, + 'balance' => 0 + ); + } + + // Clients below 7.8 don't have outfit addons + if (isset($import_data['lookaddons']) && config('client') < 780) { + unset($import_data['lookaddons']); + } + + // TFS 1.0 variations + if ($cnf['ServerEngine'] === 'TFS_10') { + unset($import_data['rank_id']); + unset($import_data['guildnick']); + unset($import_data['direction']); + unset($import_data['loss_experience']); + unset($import_data['loss_mana']); + unset($import_data['loss_skills']); + unset($import_data['premend']); + unset($import_data['online']); + + // Skills can be added into the same query on TFS 1.0+ + $import_data['skill_fist'] = $skills['fist']; + $import_data['skill_club'] = $skills['club']; + $import_data['skill_sword'] = $skills['sword']; + $import_data['skill_axe'] = $skills['axe']; + $import_data['skill_dist'] = $skills['dist']; + $import_data['skill_shielding'] = $skills['shield']; + $import_data['skill_fishing'] = $skills['fishing']; + } + + // If you are no vocation (id 0), use these details instead: + if ($vocation === 0) { + $import_data['level'] = $create['novocation']['level']; + $import_data['experience'] = level_to_experience($create['novocation']['level']); + + if ($create['novocation']['forceTown'] === true) { + $import_data['town_id'] = $create['novocation']['townId']; + } + } + + $fields = array_keys($import_data); // Fetch select fields + $data = array_values($import_data); // Fetch insert data + + $fields_sql = implode("`, `", $fields); // Convert array into SQL compatible string + $data_sql = implode("', '", $data); // Convert array into SQL compatible string + + mysql_insert("INSERT INTO `players`(`$fields_sql`) VALUES ('$data_sql');"); + + $created = time(); + $charid = user_character_id($import_data['name']); + mysql_insert("INSERT INTO `znote_players`(`player_id`, `created`, `hide_char`, `comment`) VALUES ('$charid', '$created', '0', '');"); + + // Player skills TFS 0.2, 0.3/4. (TFS 1.0 is done above character creation) + if ($cnf['ServerEngine'] != 'TFS_10') { + mysql_delete("DELETE FROM `player_skills` WHERE `player_id`='{$charid}';"); + mysql_insert("INSERT INTO `player_skills` (`player_id`, `skillid`, `value`) VALUES ('{$charid}', '0', '".$skills['fist']."'), ('{$charid}', '1', '".$skills['club']."'), ('{$charid}', '2', '".$skills['sword']."'), ('{$charid}', '3', '".$skills['axe']."'), ('{$charid}', '4', '".$skills['dist']."'), ('{$charid}', '5', '".$skills['shield']."'), ('{$charid}', '6', '".$skills['fishing']."');"); + } +} + +// Returns counted value of all players online +function user_count_online() { + if (config('ServerEngine') == 'TFS_10') { + $online = mysql_select_single("SELECT COUNT(`player_id`) AS `value` FROM `players_online`;"); + return ($online !== false) ? $online['value'] : 0; + } else { + $data = mysql_select_single("SELECT COUNT(`id`) AS `count` from `players` WHERE `online` = 1;"); + return ($data !== false) ? $data['count'] : 0; + } +} + +// Returns counted value of all accounts. +function user_count_accounts() { + $result = mysql_select_single("SELECT COUNT(`id`) AS `id` from `accounts`;"); + return ($result !== false) ? $result['id'] : 0; +} + +/* user_character_data (fetches whatever data you want from players table)! + Usage: + $player = user_data(player_ID, 'name', 'level'); + echo "Character name: ". $player['name'] .". Level: ". $player['level']; +*/ +function user_character_data($user_id) { + $data = array(); + $user_id = (int)$user_id; + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + if ($func_num_args > 1) { + unset($func_get_args[0]); + $fields = '`'. implode('`, `', $func_get_args) .'`'; + $data = mysql_select_single("SELECT $fields FROM `players` WHERE `id` = $user_id;"); + return $data; + } +} + +// return query data from znote_players table +function user_znote_character_data($character_id) { + $data = array(); + $charid = (int)$character_id; + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 1) { + unset($func_get_args[0]); + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + $data = mysql_select_single("SELECT $fields FROM `znote_players` WHERE `player_id` = $charid;"); + return $data; + } +} + +// return query data from znote table +// usage: $znoteAAC = user_znote_data('version'); +// echo $znoteAAC['version']; +function user_znote_data() { + $data = array(); + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 0) { + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + return mysql_select_single("SELECT $fields FROM `znote`;"); + } else return false; +} + +// return query data from znote_accounts table +// See documentation on user_data. This fetches information from znote_accounts table. +function user_znote_account_data($account_id) { + $data = array(); + $accid = (int)$account_id; + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 1) { + unset($func_get_args[0]); + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + return mysql_select_single("SELECT $fields FROM `znote_accounts` WHERE `account_id` = $accid LIMIT 1;"); + } else return false; +} + +// return query data from znote_visitors table +// See documentation on user_data, but this uses $longip instead. +function user_znote_visitor_data($longip) { + $data = array(); + $longip = (int)$longip; + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 1) { + unset($func_get_args[0]); + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + return mysql_select_single("SELECT $fields FROM `znote_visitors` WHERE `ip` = $longip;"); + } else return false; +} + +// return query data from znote_visitors_details table +// See documentation on user_data, but this uses $longip instead. +function user_znote_visitor_details_data($longip) { + $data = array(); + $longip = (int)$longip; + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 1) { + unset($func_get_args[0]); + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + return mysql_select_single("SELECT $fields FROM `znote_visitors_details` WHERE `ip` = $longip;"); + } else return false; +} + +/* user_data (fetches whatever data you want from accounts table)! + Usage: + $account = user_data(account_ID, 'password', 'email'); + echo $account['email']; //Will then echo out that accounts mail address. +*/ +function user_data($user_id) { + $data = array(); + $user_id = sanitize($user_id); + + $func_num_args = func_num_args(); + $func_get_args = func_get_args(); + + if ($func_num_args > 1) { + unset($func_get_args[0]); + + $fields = '`'. implode('`, `', $func_get_args) .'`'; + return mysql_select_single("SELECT $fields FROM `accounts` WHERE `id` = $user_id LIMIT 1;"); + } else return false; +} + +// Checks if user is activated (Not in use atm) +function user_activated($username) { + $username = sanitize($username); + // Deprecated, removed from DB. + return false; +} + +// Checks that username exist in database +function user_exist($username) { + $username = sanitize($username); + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `name`='$username';"); + else + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `id`='$username';"); + return ($data !== false) ? true : false; +} + +function user_name($id) { //USERNAME FROM PLAYER ID + $id = (int)$id; + $name = mysql_select_single("SELECT `name` FROM `players` WHERE `id`='$id';"); + if ($name !== false) return $name['name']; + else return false; +} + +// Checks that character name exist +function user_character_exist($username) { + $username = sanitize($username); + $player = mysql_select_single("SELECT `id` FROM `players` WHERE `name`='$username';"); + return ($player !== false) ? $player['id'] : false; +} + +// Checks that this email exist. +function user_email_exist($email) { + $email = sanitize($email); + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `email`='$email';"); + return ($data !== false) ? true : false; +} + +// Fetch user account ID from registered email. (this is used by etc lost account) +function user_id_from_email($email) { + $email = sanitize($email); + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `email`='$email';"); + return ($data !== false) ? $data['id'] : false; +} + +// Checks that a password exist in the database. +function user_password_exist($password) { + $password = sha1($password); // No need to sanitize passwords since we encrypt them. + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `password`='$password';"); + return ($data !== false) ? true : false; +} + +// Verify that submitted password match stored password in account id +function user_password_match($password, $account_id) { + $password = sha1($password); // No need to sanitize passwords since we encrypt them. + $account_id = (int)$account_id; + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `password`='$password' AND `id`='$account_id';"); + return ($data !== false) ? true : false; +} + +// Get user ID from name +function user_id($username) { + $username = sanitize($username); + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `name`='$username' LIMIT 1;"); + else + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `id`='$username' LIMIT 1;"); + if ($data !== false) return $data['id']; + else return false; +} + +// Get user login ID from username and password +function user_login_id($username, $password) { + $username = sanitize($username); + $password = sha1($password); + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `name`='$username' AND `password`='$password' LIMIT 1;"); + else + $data = mysql_select_single("SELECT `id` FROM `accounts` WHERE `id`='$username' AND `password`='$password' LIMIT 1;"); + if ($data !== false) return $data['id']; + else return false; +} + +// TFS 0.3+ compatibility. +function user_login_id_03($username, $password) { + if (config('salt') === true) { + if (user_exist($username)) { + $user_id = user_id($username); + $username = sanitize($username); + + $data = mysql_select_single("SELECT `salt`, `id`, `name`, `password` FROM `accounts` WHERE `id`='$user_id';"); + $salt = $data['salt']; + if (!empty($salt)) $password = sha1($salt.$password); + else $password = sha1($password); + return ($data !== false && $data['name'] == $username && $data['password'] == $password) ? $data['id'] : false; + } else return false; + } else return user_login_id($username, $password); +} + +// Get character ID from character name +function user_character_id($charname) { + $charname = sanitize($charname); + $char = mysql_select_single("SELECT `id` FROM `players` WHERE `name`='$charname';"); + if ($char !== false) return $char['id']; + else return false; +} + +// Get character name from character ID +function user_character_name($charID) { + $charID = (int)$charID; + $char = mysql_select_single('SELECT `name` FROM `players` WHERE `id` = ' . $charID); + if ($char !== false) return $char['name']; + else return false; +} + +// Hide user character. +function user_character_hide($username) { + $username = sanitize($username); + $username = user_character_id($username); + $char = mysql_select_single("SELECT `hide_char` FROM `znote_players` WHERE `player_id`='$username';"); + if ($char !== false) return $char['hide_char']; + else return false; +} + +// Login with a user. (TFS 0.2) +function user_login($username, $password) { + $username = sanitize($username); + $password = sha1($password); + if (config('ServerEngine') !== 'OTHIRE') + $data = mysql_select_single("SELECT `id` FROM accounts WHERE name='$username' AND password='$password';"); + else + $data = mysql_select_single("SELECT `id` FROM accounts WHERE id='$username' AND password='$password';"); + return ($data !== false) ? $data['id'] : false; +} + +// Login a user with TFS 0.3 compatibility +function user_login_03($username, $password) { + if (config('salt') === true) { + $username = sanitize($username); + $data = mysql_select_single("SELECT `salt`, `id`, `password`, `name` FROM `accounts` WHERE `name`='$username';"); + $salt = $data['salt']; + if (!empty($salt)) $password = sha1($salt.$password); + else $password = sha1($password); + return ($data !== false && $data['name'] == $username && $data['password'] == $password) ? $data['id'] : false; + } else return user_login($username, $password); +} + +// Verify that user is logged in +function user_logged_in() { + return (getSession('user_id') !== false) ? true : false; +} + +function guild_war_invitation($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + $gname = get_guild_name($cid); + $ename = get_guild_name($gid); + $time = time(); + mysql_insert("INSERT INTO `guild_wars` (`guild1`, `guild2`, `name1`, `name2`, `status`, `started`, `ended`) VALUES ('$cid', '$gid', '$gname', '$ename', '0', '$time', '0');"); +} + +function accept_war_invitation($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + mysql_update("UPDATE `guild_wars` SET `status` = 1 WHERE `guild1` = '$cid' AND `guild2` = '$gid' AND `status` = 0;"); +} + +function reject_war_invitation($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + $time = time(); + mysql_update("UPDATE `guild_wars` SET `status` = 2, `ended` = '$time' WHERE `guild1` = '$cid' AND `guild2` = '$gid';"); +} + +function cancel_war_invitation($cid, $gid) { + $cid = (int)$cid; + $gid = (int)$gid; + $time = time(); + mysql_update("UPDATE `guild_wars` SET `status` = 3, `ended` = '$time' WHERE `guild2` = '$cid' AND `guild1` = '$gid';"); +} + +?> diff --git a/app/ZnoteAAC/engine/guildimg/default@logo.gif b/app/ZnoteAAC/engine/guildimg/default@logo.gif new file mode 100644 index 0000000..404a0df Binary files /dev/null and b/app/ZnoteAAC/engine/guildimg/default@logo.gif differ diff --git a/app/ZnoteAAC/engine/img/bg.png b/app/ZnoteAAC/engine/img/bg.png new file mode 100644 index 0000000..96fe689 Binary files /dev/null and b/app/ZnoteAAC/engine/img/bg.png differ diff --git a/app/ZnoteAAC/engine/img/lifebarra.png b/app/ZnoteAAC/engine/img/lifebarra.png new file mode 100644 index 0000000..3f81199 Binary files /dev/null and b/app/ZnoteAAC/engine/img/lifebarra.png differ diff --git a/app/ZnoteAAC/engine/img/manabar.png b/app/ZnoteAAC/engine/img/manabar.png new file mode 100644 index 0000000..38dbdd8 Binary files /dev/null and b/app/ZnoteAAC/engine/img/manabar.png differ diff --git a/app/ZnoteAAC/engine/img/o/b_l.png b/app/ZnoteAAC/engine/img/o/b_l.png new file mode 100644 index 0000000..5c4e286 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/b_l.png differ diff --git a/app/ZnoteAAC/engine/img/o/b_m.png b/app/ZnoteAAC/engine/img/o/b_m.png new file mode 100644 index 0000000..e73cf7c Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/b_m.png differ diff --git a/app/ZnoteAAC/engine/img/o/b_r.png b/app/ZnoteAAC/engine/img/o/b_r.png new file mode 100644 index 0000000..2835fdb Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/b_r.png differ diff --git a/app/ZnoteAAC/engine/img/o/m_l.png b/app/ZnoteAAC/engine/img/o/m_l.png new file mode 100644 index 0000000..3973145 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/m_l.png differ diff --git a/app/ZnoteAAC/engine/img/o/m_m.png b/app/ZnoteAAC/engine/img/o/m_m.png new file mode 100644 index 0000000..5874199 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/m_m.png differ diff --git a/app/ZnoteAAC/engine/img/o/m_r.png b/app/ZnoteAAC/engine/img/o/m_r.png new file mode 100644 index 0000000..60d98e8 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/m_r.png differ diff --git a/app/ZnoteAAC/engine/img/o/t_l.png b/app/ZnoteAAC/engine/img/o/t_l.png new file mode 100644 index 0000000..3bcec98 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/t_l.png differ diff --git a/app/ZnoteAAC/engine/img/o/t_m.png b/app/ZnoteAAC/engine/img/o/t_m.png new file mode 100644 index 0000000..677a1c1 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/t_m.png differ diff --git a/app/ZnoteAAC/engine/img/o/t_r.png b/app/ZnoteAAC/engine/img/o/t_r.png new file mode 100644 index 0000000..1451458 Binary files /dev/null and b/app/ZnoteAAC/engine/img/o/t_r.png differ diff --git a/app/ZnoteAAC/engine/img/outfit.png b/app/ZnoteAAC/engine/img/outfit.png new file mode 100644 index 0000000..7ca99c3 Binary files /dev/null and b/app/ZnoteAAC/engine/img/outfit.png differ diff --git a/app/ZnoteAAC/engine/img/outfitbackgrounds.png b/app/ZnoteAAC/engine/img/outfitbackgrounds.png new file mode 100644 index 0000000..f8536f6 Binary files /dev/null and b/app/ZnoteAAC/engine/img/outfitbackgrounds.png differ diff --git a/app/ZnoteAAC/engine/img/skillsbackground.png b/app/ZnoteAAC/engine/img/skillsbackground.png new file mode 100644 index 0000000..e43f1e5 Binary files /dev/null and b/app/ZnoteAAC/engine/img/skillsbackground.png differ diff --git a/app/ZnoteAAC/engine/init.php b/app/ZnoteAAC/engine/init.php new file mode 100644 index 0000000..6fb7485 --- /dev/null +++ b/app/ZnoteAAC/engine/init.php @@ -0,0 +1,175 @@ +1. Find your php.ini file.
    2. Uncomment extension=php_curl
    Restart web server.

    If you don't want this then disable paypal & use_captcha in config.php."); +} +if ($config['use_captcha'] && !extension_loaded('openssl')) { + die("php openSSL is not enabled. It is required to for captcha services.
    1. Find your php.ini file.
    2. Uncomment extension=php_openssl
    Restart web server.

    If you don't want this then disable use_captcha in config.php."); +} + +// References ( & ) works as an alias for a variable, +// they point to the same memmory, instead of duplicating it. +if (!isset($config['TFSVersion'])) $config['TFSVersion'] = &$config['ServerEngine']; +if (!isset($config['ServerEngine'])) $config['ServerEngine'] = &$config['TFSVersion']; + +require_once 'database/connect.php'; +require_once 'function/general.php'; +require_once 'function/users.php'; +require_once 'function/cache.php'; +require_once 'function/mail.php'; +require_once 'function/token.php'; +require_once 'function/itemparser/itemlistparser.php'; + +if (isset($_SESSION['token'])) { + $_SESSION['old_token'] = $_SESSION['token']; +} +Token::generate(); + +$tfs_10_hasPremDays = true; // https://github.com/otland/forgottenserver/pull/2813 + +if (user_logged_in() === true) { + $session_user_id = getSession('user_id'); + if ($config['ServerEngine'] !== 'OTHIRE') { + if ($config['ServerEngine'] == 'TFS_10') { + $hasPremDays = mysql_select_single("SHOW COLUMNS from `accounts` WHERE `Field` = 'premdays'"); + if ($hasPremDays === false) { + $tfs_10_hasPremDays = false; + $user_data = user_data($session_user_id, 'id', 'name', 'password', 'email', 'premium_ends_at'); + $user_data['premdays'] = ($user_data['premium_ends_at'] - time() > 0) ? floor(($user_data['premium_ends_at'] - time()) / 86400) : 0; + } else { + $user_data = user_data($session_user_id, 'id', 'name', 'password', 'email', 'premdays'); + } + } else { + $user_data = user_data($session_user_id, 'id', 'name', 'password', 'email', 'premdays'); + } + } else + $user_data = user_data($session_user_id, 'id', 'password', 'email', 'premend'); + $user_znote_data = user_znote_account_data($session_user_id, 'ip', 'created', 'points', 'cooldown', 'flag' ,'active_email'); +} +$errors = array(); +// Log IP +if ($config['log_ip']) { + $visitor_config = $config['ip_security']; + + $flush = $config['flush_ip_logs']; + if ($flush != false) { + $timef = $time - $flush; + if (getCache() < $timef) { + $timef = $time - $visitor_config['time_period']; + mysql_delete("DELETE FROM znote_visitors_details WHERE time <= '$timef'"); + setCache($time); + } + } + + $visitor_data = znote_visitors_get_data(); + + znote_visitor_set_data($visitor_data); // update or insert data + znote_visitor_insert_detailed_data(0); // detailed data + + $visitor_detailed = znote_visitors_get_detailed_data($visitor_config['time_period']); + + // max activity + $v_activity = 0; + $v_register = 0; + $v_highscore = 0; + $v_c_char = 0; + $v_s_char = 0; + $v_form = 0; + foreach ((array)$visitor_detailed as $v_d) { + // Activity + if ($v_d['ip'] == getIPLong()) { + // count each type of visit + switch ($v_d['type']) { + case 0: // max activity + $v_activity++; + break; + + case 1: // account registered + $v_register++; + $v_form++; + break; + + case 2: // character creations + $v_c_char++; + $v_form++; + break; + + case 3: // Highscore fetched + $v_highscore++; + $v_form++; + break; + + case 4: // character searched + $v_s_char++; + $v_form++; + break; + + case 5: // Other forms (login.?) + $v_form++; + break; + } + + } + } + + // Deny access if activity is too high + if ($v_activity > $visitor_config['max_activity']) die("Chill down. Your web activity is too big. max_activity"); + if ($v_register > $visitor_config['max_account']) die("Chill down. You can't create multiple accounts that fast. max_account"); + if ($v_c_char > $visitor_config['max_character']) die("Chill down. Your web activity is too big. max_character"); + if ($v_form > $visitor_config['max_post']) die("Chill down. Your web activity is too big. max_post"); + + //var_dump($v_activity, $v_register, $v_highscore, $v_c_char, $v_s_char, $v_form); + //echo ' <--- IP logging activity past 10 seconds.'; +} + +// Sub page override system +$filename = explode('/', $_SERVER['SCRIPT_NAME']); +$filename = $filename[count($filename) - 1]; +$page_filename = str_replace('.php', '', $filename); +if ($config['allowSubPages']) { + require_once 'layout/sub.php'; + if (isset($subpages) && !empty($subpages)) { + foreach ($subpages as $page) { + if ($page['override'] && $page['file'] === $filename) { + require_once 'layout/overall/header.php'; + require_once 'layout/sub/'.$page['file']; + require_once 'layout/overall/footer.php'; + exit; + } + } + } else { + ?> +
    +

    Old layout!

    +

    The layout is running an outdated sub system which is not compatible with this version of Znote AAC.

    +

    The file /layout/sub.php is outdated. +
    Please update it to look like THIS. +

    +
    + diff --git a/app/ZnoteAAC/engine/js/jquery-1.10.2.min.js b/app/ZnoteAAC/engine/js/jquery-1.10.2.min.js new file mode 100644 index 0000000..f30ebe0 --- /dev/null +++ b/app/ZnoteAAC/engine/js/jquery-1.10.2.min.js @@ -0,0 +1,6 @@ +/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-latest.min.map +*/ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
    a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
    t
    ",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
    ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("';this.ne.selectedInstance.setContent(this.ne.selectedInstance.getContent()+u),this.removePane()}});nicEditors.registerPlugin(nicPlugin, nicYouTubeOptions); diff --git a/app/ZnoteAAC/failed.php b/app/ZnoteAAC/failed.php new file mode 100644 index 0000000..f09ae0e --- /dev/null +++ b/app/ZnoteAAC/failed.php @@ -0,0 +1,4 @@ + +

    Failed!

    +

    Something went wrong. :(

    + \ No newline at end of file diff --git a/app/ZnoteAAC/forum.php b/app/ZnoteAAC/forum.php new file mode 100644 index 0000000..4522e93 --- /dev/null +++ b/app/ZnoteAAC/forum.php @@ -0,0 +1,1111 @@ + + 1.2): + - Updated to the new date/clock time system + - Bootstrap design support. + + Changelog (1.2 --> 1.3): + - Show character outfit as avatar + - Show in-game position + + Changelog (1.3 -> 1.4): + - Fix SQL query error when editing Board name. +*/ +// BBCODE support: +function TransformToBBCode($string) { + $tags = array( + '[center]{$1}[/center]' => '
    $1
    ', + '[b]{$1}[/b]' => '$1', + '[img]{$1}[/img]' => 'image', + '[link]{$1}[/link]' => '$1', + '[link={$1}]{$2}[/link]' => '$2', + '[url={$1}]{$2}[/url]' => '$2', + '[color={$1}]{$2}[/color]' => '$2', + '[*]{$1}[/*]' => '
  • $1
  • ', + '[youtube]{$1}[/youtube]' => '
    ', + ); + + foreach ($tags as $tag => $value) { + $code = preg_replace('/placeholder([0-9]+)/', '(.*?)', preg_quote(preg_replace('/\{\$([0-9]+)\}/', 'placeholder$1', $tag), '/')); + $string = preg_replace('/'.$code.'/i', $value, $string); + if (strpos($string, "='1' AND `account_id`='". $user_data['id'] ."';"); +else $yourChars = mysql_select_multi("SELECT `id`, `name`, `group_id` FROM `players` WHERE `level`>='". $config['forum']['level'] ."' AND `account_id`='". $user_data['id'] ."';"); +if (!$yourChars) $yourChars = array(); +$charCount = count($yourChars); +$yourAccess = accountAccess($user_data['id'], $config['ServerEngine']); +if ($admin) { + if (!empty($_POST)) { + $guilds = mysql_select_multi("SELECT `id`, `name` FROM `guilds` ORDER BY `name`;"); + $guilds[] = array('id' => '0', 'name' => 'No guild'); + } + $yourAccess = 100; +} + +// Your characters, indexed by char_id +$charData = array(); +foreach ($yourChars as $char) { + $charData[$char['id']] = $char; + if (get_character_guild_rank($char['id']) > 0) { + $guild = get_player_guild_data($char['id']); + $charData[$char['id']]['guild'] = $guild['guild_id']; + $charData[$char['id']]['guild_rank'] = $guild['rank_level']; + } else $charData[$char['id']]['guild'] = '0'; +} +$cooldownw = array( + $user_znote_data['cooldown'], + time(), + $user_znote_data['cooldown'] - time() + ); + +///////////////// +// Guild Leader & admin +$leader = false; +foreach($charData as $char) { + if ($char['guild'] > 0 && $char['guild_rank'] == 3) $leader = true; +} +if ($admin && !empty($_POST) || $leader && !empty($_POST)) { + $admin_thread_delete = getValue($_POST['admin_thread_delete']); + $admin_thread_close = getValue($_POST['admin_thread_close']); + $admin_thread_open = getValue($_POST['admin_thread_open']); + $admin_thread_sticky = getValue($_POST['admin_thread_sticky']); + $admin_thread_unstick = getValue($_POST['admin_thread_unstick']); + $admin_thread_id = getValue($_POST['admin_thread_id']); + + // delete thread + if ($admin_thread_delete !== false) { + $admin_thread_id = (int)$admin_thread_id; + $access = false; + if (!$admin) { + $thread = mysql_select_single("SELECT `forum_id` FROM `znote_forum_threads` WHERE `id`='$admin_thread_id';"); + $forum = mysql_select_single("SELECT `guild_id` FROM `znote_forum` WHERE `id`='". $thread['forum_id'] ."';"); + foreach($charData as $char) if ($char['guild'] == $forum['guild_id'] && $char['guild_rank'] == 3) $access = true; + } else $access = true; + + if ($access) { + // Delete all associated posts + mysql_delete("DELETE FROM `znote_forum_posts` WHERE `thread_id`='$admin_thread_id';"); + // Delete thread itself + mysql_delete("DELETE FROM `znote_forum_threads` WHERE `id`='$admin_thread_id' LIMIT 1;"); + echo '

    Thread and all associated posts deleted.

    '; + } else echo '

    Permission denied.

    '; + } + + // Close thread + if ($admin_thread_close !== false) { + $admin_thread_id = (int)$admin_thread_id; + $access = false; + if (!$admin) { + $thread = mysql_select_single("SELECT `forum_id` FROM `znote_forum_threads` WHERE `id`='$admin_thread_id';"); + $forum = mysql_select_single("SELECT `guild_id` FROM `znote_forum` WHERE `id`='". $thread['forum_id'] ."';"); + foreach($charData as $char) if ($char['guild'] == $forum['guild_id'] && $char['guild_rank'] == 3) $access = true; + } else $access = true; + if ($access) { + mysql_update("UPDATE `znote_forum_threads` SET `closed`='1' WHERE `id`='$admin_thread_id' LIMIT 1;"); + //die("UPDATE `znote_forum_threads` SET `closed`='1' WHERE `id`='$admin_thread_id' LIMIT 1;"); + echo '

    Thread has been closed.

    '; + } else echo '

    Permission denied.

    '; + } + + // open thread + if ($admin_thread_open !== false) { + $admin_thread_id = (int)$admin_thread_id; + $access = false; + if (!$admin) { + $thread = mysql_select_single("SELECT `forum_id` FROM `znote_forum_threads` WHERE `id`='$admin_thread_id';"); + $forum = mysql_select_single("SELECT `guild_id` FROM `znote_forum` WHERE `id`='". $thread['forum_id'] ."';"); + foreach($charData as $char) if ($char['guild'] == $forum['guild_id'] && $char['guild_rank'] == 3) $access = true; + } else $access = true; + if ($access) { + mysql_update("UPDATE `znote_forum_threads` SET `closed`='0' WHERE `id`='$admin_thread_id' LIMIT 1;"); + echo '

    Thread has been opened.

    '; + } else echo '

    Permission denied.

    '; + } + + // stick thread + if ($admin_thread_sticky !== false) { + $admin_thread_id = (int)$admin_thread_id; + $access = false; + if (!$admin) { + $thread = mysql_select_single("SELECT `forum_id` FROM `znote_forum_threads` WHERE `id`='$admin_thread_id';"); + $forum = mysql_select_single("SELECT `guild_id` FROM `znote_forum` WHERE `id`='". $thread['forum_id'] ."';"); + foreach($charData as $char) if ($char['guild'] == $forum['guild_id'] && $char['guild_rank'] == 3) $access = true; + } else $access = true; + if ($access) { + mysql_update("UPDATE `znote_forum_threads` SET `sticky`='1' WHERE `id`='$admin_thread_id' LIMIT 1;"); + echo '

    Thread has been sticked.

    '; + } else echo '

    Permission denied.

    '; + } + + // unstick thread + if ($admin_thread_unstick !== false) { + $admin_thread_id = (int)$admin_thread_id; + $access = false; + if (!$admin) { + $thread = mysql_select_single("SELECT `forum_id` FROM `znote_forum_threads` WHERE `id`='$admin_thread_id';"); + $forum = mysql_select_single("SELECT `guild_id` FROM `znote_forum` WHERE `id`='". $thread['forum_id'] ."';"); + foreach($charData as $char) if ($char['guild'] == $forum['guild_id'] && $char['guild_rank'] == 3) $access = true; + } else $access = true; + if ($access) { + mysql_update("UPDATE `znote_forum_threads` SET `sticky`='0' WHERE `id`='$admin_thread_id' LIMIT 1;"); + echo '

    Thread has been unsticked.

    '; + } else echo '

    Permission denied.

    '; + } +} + +///////////////// +// ADMIN FUNCT +if ($admin && !empty($_POST)) { + $admin_post_id = getValue($_POST['admin_post_id']); + $admin_post_delete = getValue($_POST['admin_post_delete']); + + $admin_category_delete = getValue($_POST['admin_category_delete']); + $admin_category_edit = getValue($_POST['admin_category_edit']); + $admin_category_id = getValue($_POST['admin_category_id']); + + $admin_update_category = getValue($_POST['admin_update_category']); + $admin_category_name = getValue($_POST['admin_category_name']); + + $admin_category_access = getValue($_POST['admin_category_access']); + $admin_category_closed = getValue($_POST['admin_category_closed']); + $admin_category_hidden = getValue($_POST['admin_category_hidden']); + $admin_category_guild_id = getValue($_POST['admin_category_guild_id']); + + if ($admin_category_access === false) $admin_category_access = 0; + if ($admin_category_closed === false) $admin_category_closed = 0; + if ($admin_category_hidden === false) $admin_category_hidden = 0; + if ($admin_category_guild_id === false) $admin_category_guild_id = 0; + + $admin_board_create_name = getValue($_POST['admin_board_create_name']); + $admin_board_create_access = getValue($_POST['admin_board_create_access']); + $admin_board_create_closed = getValue($_POST['admin_board_create_closed']); + $admin_board_create_hidden = getValue($_POST['admin_board_create_hidden']); + $admin_board_create_guild_id = getValue($_POST['admin_board_create_guild_id']); + + if ($admin_board_create_access === false) $admin_board_create_access = 0; + if ($admin_board_create_closed === false) $admin_board_create_closed = 0; + if ($admin_board_create_hidden === false) $admin_board_create_hidden = 0; + if ($admin_board_create_guild_id === false) $admin_board_create_guild_id = 0; + + // Create board + if ($admin_board_create_name !== false) { + + // Insert data + mysql_insert("INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`) + VALUES ('$admin_board_create_name', + '$admin_board_create_access', + '$admin_board_create_closed', + '$admin_board_create_hidden', + '$admin_board_create_guild_id');"); + echo '

    Board has been created.

    '; + } + + ////////////////// + // update category + if ($admin_update_category !== false) { + $admin_category_id = (int)$admin_category_id; + + // Update the category + mysql_update("UPDATE `znote_forum` SET + `name`='$admin_category_name', + `access`='$admin_category_access', + `closed`='$admin_category_closed', + `hidden`='$admin_category_hidden', + `guild_id`='$admin_category_guild_id' + WHERE `id`='$admin_category_id' LIMIT 1;"); + echo '

    Board has been updated successfully.

    '; + } + + ////////////////// + // edit category + if ($admin_category_edit !== false) { + $admin_category_id = (int)$admin_category_id; + $category = mysql_select_single("SELECT `id`, `name`, `access`, `closed`, `hidden`, `guild_id` + FROM `znote_forum` WHERE `id`='$admin_category_id' LIMIT 1;"); + if ($category !== false) { + ?> +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + + Category not found.'; + + } + + // delete category + if ($admin_category_delete !== false) { + $admin_category_id = (int)$admin_category_id; + + // find all threads in category + $threads = mysql_select_multi("SELECT `id` FROM `znote_forum_threads` WHERE `forum_id`='$admin_category_id';"); + + // Then loop through all threads, and delete all associated posts: + foreach($threads as $thread) { + mysql_delete("DELETE FROM `znote_forum_posts` WHERE `thread_id`='". $thread['id'] ."';"); + } + // Then delete all threads + mysql_delete("DELETE FROM `znote_forum_threads` WHERE `forum_id`='$admin_category_id';"); + // Then delete the category + mysql_delete("DELETE FROM `znote_forum` WHERE `id`='$admin_category_id' LIMIT 1;"); + echo '

    Board, associated threads and all their associated posts deleted.

    '; + } + + // delete post + if ($admin_post_delete !== false) { + $admin_post_id = (int)$admin_post_id; + + // Delete the post + mysql_delete("DELETE FROM `znote_forum_posts` WHERE `id`='$admin_post_id' LIMIT 1;"); + echo '

    Post has been deleted.

    '; + } +} +// End admin function + +// Fetching get values +if (!empty($_GET)) { + $getCat = getValue($_GET['cat']); + $getForum = getValue($_GET['forum']); + $getThread = getValue($_GET['thread']); + + $new_thread_category = getValue($_POST['new_thread_category']); + $new_thread_cid = getValue($_POST['new_thread_cid']); + + $create_thread_cid = getValue($_POST['create_thread_cid']); + $create_thread_title = getValue($_POST['create_thread_title']); + $create_thread_text = getValue($_POST['create_thread_text']); + $create_thread_category = getValue($_POST['create_thread_category']); + + $update_thread_id = getValue($_POST['update_thread_id']); + $update_thread_title = getValue($_POST['update_thread_title']); + $update_thread_text = getValue($_POST['update_thread_text']); + + $edit_thread = getValue($_POST['edit_thread']); + $edit_thread_id = getValue($_POST['edit_thread_id']); + + $reply_thread = getValue($_POST['reply_thread']); + $reply_text = getValue($_POST['reply_text']); + $reply_cid = getValue($_POST['reply_cid']); + + $edit_post = getValue($_POST['edit_post']); + $edit_post_id = getValue($_POST['edit_post_id']); + + $update_post_id = getValue($_POST['update_post_id']); + $update_post_text = getValue($_POST['update_post_text']); + + ///////////////////// + // When you are POSTING in an existing thread + if ($reply_thread !== false && $reply_text !== false && $reply_cid !== false) { + $reply_cid = (int)$reply_cid; + + if ($user_znote_data['cooldown'] < time()) { + user_update_znote_account(array('cooldown'=>(time() + $config['forum']['cooldownPost']))); + + $thread = mysql_select_single("SELECT `closed` FROM `znote_forum_threads` WHERE `id`='$reply_thread' LIMIT 1;"); + + if ($thread['closed'] == 1 && $admin === false) $access = false; + else $access = true; + + if ($access) { + mysql_insert("INSERT INTO `znote_forum_posts` (`thread_id`, `player_id`, `player_name`, `text`, `created`, `updated`) VALUES ('$reply_thread', '$reply_cid', '". $charData[$reply_cid]['name'] ."', '$reply_text', '". time() ."', '". time() ."');"); + if ($config['forum']['newPostsBumpThreads']) mysql_update("UPDATE `znote_forum_threads` SET `updated`='". time() ."' WHERE `id`='$reply_thread';"); + } else echo '

    You don\'t have permission to post on this thread. [Thread: Closed]

    '; + } else { + ?> + Antispam: You need to wait seconds before you can create or post. + (time() + $config['forum']['cooldownCreate']))); + + $category = mysql_select_single("SELECT `access`, `closed`, `guild_id` FROM `znote_forum` WHERE `id`='$create_thread_category' LIMIT 1;"); + if ($category !== false) { + $access = true; + if (!$admin) { + if ($category['access'] > $yourAccess) $access = false; + if ($category['guild_id'] > 0) { + $status = false; + foreach($charData as $char) { + if ($char['guild'] == $category['guild_id']) $status = true; + } + if (!$status) $access = false; + } + if ($category['closed'] > 0) $access = false; + } + + if ($access) { + mysql_insert("INSERT INTO `znote_forum_threads` + (`forum_id`, `player_id`, `player_name`, `title`, `text`, `created`, `updated`, `sticky`, `hidden`, `closed`) + VALUES ( + '$create_thread_category', + '$create_thread_cid', + '". $charData[$create_thread_cid]['name'] ."', + '$create_thread_title', + '$create_thread_text', + '". time() ."', + '". time() ."', + '0', '0', '0');"); + SendGet(array('cat'=>$create_thread_category), 'forum.php'); + } else echo '

    Permission to create thread denied.

    '; + } else echo 'Category does not exist.'; + } else { + ?> + Antispam: You need to wait seconds before you can create or post. + post has been updated.'; + } else echo "

    Your permission to edit this post has been denied.

    "; + } + + ///////////////////// + // When you ARE updating thread + if ($update_thread_id !== false && $update_thread_title !== false && $update_thread_text !== false) { + // Fetch the thread data + $thread = mysql_select_single("SELECT `id`, `player_name`, `title`, `text`, `closed` FROM `znote_forum_threads` WHERE `id`='$update_thread_id' LIMIT 1;"); + + // Verify access + $access = PlayerHaveAccess($yourChars, $thread['player_name']); + if ($thread['closed'] == 1 && $admin === false) $access = false; + if ($admin) $access = true; + + if ($access) { + mysql_update("UPDATE `znote_forum_threads` SET `title`='$update_thread_title', `text`='$update_thread_text' WHERE `id`='$update_thread_id';"); + echo '

    Thread has been updated.

    '; + } else echo "

    Your permission to edit this thread has been denied.

    "; + } + + ///////////////////// + // When you want to edit a post + if ($edit_post_id !== false && $edit_post !== false) { + // Fetch the post data + $post = mysql_select_single("SELECT `id`, `thread_id`, `text`, `player_name` FROM `znote_forum_posts` WHERE `id`='$edit_post_id' LIMIT 1;"); + $thread = mysql_select_single("SELECT `closed` FROM `znote_forum_threads` WHERE `id`='". $post['thread_id'] ."' LIMIT 1;"); + // Verify access + $access = PlayerHaveAccess($yourChars, $post['player_name']); + if ($thread['closed'] == 1 && $admin === false) $access = false; + if ($admin) $access = true; + + if ($access) { + ?> +

    Edit Post

    +
    + +
    + +
    + You don\'t have permission to edit this post.

    '; + } else + + ///////////////////// + // When you want to edit a thread + if ($edit_thread_id !== false && $edit_thread !== false) { + // Fetch the thread data + $thread = mysql_select_single("SELECT `id`, `title`, `text`, `player_name`, `closed` FROM `znote_forum_threads` WHERE `id`='$edit_thread_id' LIMIT 1;"); + + $access = PlayerHaveAccess($yourChars, $thread['player_name']); + if ($thread['closed'] == 1) $access = false; + if ($admin) $access = true; + + if ($access) { + ?> +

    Edit Thread

    +
    + +

    +
    + +
    + Edit access denied.

    '; + } else + + ///////////////////// + // When you want to view a thread + if ($getThread !== false) { + $getThread = (int)$getThread; + $threadData = mysql_select_single("SELECT `id`, `forum_id`, `player_id`, `player_name`, `title`, `text`, `created`, `updated`, `sticky`, `hidden`, `closed` FROM `znote_forum_threads` WHERE `id`='$getThread' LIMIT 1;"); + + if ($threadData !== false) { + + $category = mysql_select_single("SELECT `hidden`, `access`, `guild_id` FROM `znote_forum` WHERE `id`='". $threadData['forum_id'] ."' LIMIT 1;"); + if ($category === false) die("Thread category does not exist."); + + $access = true; + $leader = false; + if ($category['hidden'] == 1 || $category['access'] > 1 || $category['guild_id'] > 0) { + $access = false; + if ($category['hidden'] == 1) $access = PlayerHaveAccess($yourChars, $threadData['player_name']); + if ($category['access'] > 1 && $yourAccess >= $category['access']) $access = true; + foreach($charData as $char) { + if ($category['guild_id'] == $char['guild']) $access = true; + if ($char['guild_rank'] == 3) $leader = true; + } + if ($admin) $access = true; + } + + + if ($access) { + $threadPlayer = ($config['forum']['outfit_avatars'] || $config['forum']['player_position']) ? mysql_select_single("SELECT `id`, `group_id`, `sex`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons` FROM `players` WHERE `id`='".$threadData['player_id']."';") : false; + ?> + LinkMap:
    Forum -
    + Viewing thread: ". $threadData['title'] .""; ?> + + + > + + - Created by: + ". $threadData['player_name'] .""; + endif; + ?> + + + + + + + + +
    + '> + +
    img + + +
    + +
    +

    +
    +
    + + + + + + + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    +
    + + + + + +
    +
    + + +
    +
    + + + + + > + + - Posted by: + ". $post['player_name'] .""; + endif; ?> + + + + + + + + +
    + '> + +
    img + + +
    + +
    +

    +
    +
    + +
    + + +
    + +
    + + +
    + 0) { + if ($threadData['closed'] == 0 || $yourAccess > 3) { + ?> +
    +
    + +

    [b]Bold Text[/b], [img]Direct Image Link[/img], [center]Centered Text[/center],
    [link]https://youtube.com/[/link], [color=GREEN]Green Text![/color], [*] - Dotted [/*]


    + +
    + + +
    + You don\'t have permission to post on this thread. [Thread: Closed]

    '; + } else { + ?>

    You must have a character on your account that is level + to reply to this thread.

    Your permission to access this thread has been denied.

    "; + } else { + ?> +

    Thread unavailable

    +

    Thread is unavailable for you, or do not exist any more. + 0 && !empty($_GET['forum'])) { + $tmpCat = getValue($_GET['cat']); + $tmpCatName = getValue($_GET['forum']); + ?> +
    Go back to:

    + +
    Go back to Forum

    + + $yourAccess) $access = false; + if ($category['guild_id'] > 0) { + $status = false; + foreach($charData as $char) { + if ($char['guild'] == $category['guild_id']) $status = true; + } + if (!$status) $access = false; + } + if ($category['closed'] > 0) $access = false; + } + + if ($access) { + ?> +

    Create new thread

    +
    + + + +

    +
    + +
    + Permission to create thread denied.

    '; + } + } else + + ///////////////////// + // When category is specified + if ($getCat !== false) { + $getCat = (int)$getCat; + + // Fetch category rules + $category = mysql_select_single("SELECT `name`, `access`, `closed`, `hidden`, `guild_id` FROM `znote_forum` WHERE `id`='$getCat' AND `access`<='$yourAccess' LIMIT 1;"); + + if ($category !== false && $category['guild_id'] > 0 && !$admin) { + $access = false; + foreach($charData as $char) if ($category['guild_id'] == $char['guild']) $access = true; + if ($access !== true) $category = false; + } + + if ($category !== false) { + // TODO : Verify guild access + //foreach($charData) + echo "

    Forum Board: ". $category['name'] ."

    "; + + // Threads + // - id - forum_id - player_id - player_name - title - text - created - updated - sticky - hidden - closed + $threads = mysql_select_multi("SELECT `id`, `player_name`, `title`, `sticky`, `closed` FROM `znote_forum_threads` WHERE `forum_id`='$getCat' ORDER BY `sticky` DESC, `updated` DESC;"); + + ///// HTML \\\\\ + if ($threads !== false) { + ?> + + + + + + 3) $access = true; + } + + if ($access) { + ?> + + '; + ?> + + + + '; + ?> + + + + + +
    TitleBy
    + 0) { + if ($category['closed'] == 0 || $admin) { + ?> +
    + + + +
    + This board is closed.

    '; + } else echo "

    You must have a character on your account that is level ". $config['forum']['level'] ."+ to create new threads.

    "; + } else echo "

    Your permission to access this board has been denied.
    If you are trying to access a Guild Board, you need level: ". $config['forum']['level'] ."+

    "; + + } +} else { + + ////////////////////// + // No category specified, show list of available categories + if (!$admin) $categories = mysql_select_multi( + "SELECT `id`, `name`, `access`, `closed`, `hidden`, `guild_id` FROM `znote_forum` WHERE `access`<='$yourAccess' ORDER BY `name`;"); + else $categories = mysql_select_multi("SELECT `id`, `name`, `access`, `closed`, `hidden`, `guild_id` FROM `znote_forum` ORDER BY `name`;"); + + $guildboard = false; + ?> + + + + 0) $guild = true; + } + + if ($admin || $guild) { + if (!isset($guilds)) { + $guilds = mysql_select_multi("SELECT `id`, `name` FROM `guilds` ORDER BY `name`;"); + $guilds[] = array('id' => '0', 'name' => 'No guild'); + } + $guildName = array(); + foreach($guilds as $guild) { + $guildName[$guild['id']] = $guild['name']; + } + if ($admin) { + ?> + + + + + 0) { + $guildboard[] = $category; + $access = false; + } + + /* + if ($guild) { + foreach($charData as $char) { + if ($category['guild_id'] == $char['guild']) $access = true; + } + } + */ + if ($access) { + $url = url("forum.php?cat=". $category['id']); + echo ''; + echo '"; + + // Admin columns + if ($admin) { + ?> + + + '; + } + } + } + ?> +
    Forum BoardsEditDelete
    '; + if ($category['closed'] == 1) echo $config['forum']['closed'],' '; + if ($category['hidden'] == 1) echo $config['forum']['hidden'],' '; + if ($category['guild_id'] > 0) { + echo "[". $guildName[$category['guild_id']] ."] "; + } + echo $category['name'] ." +
    + + +
    +
    +
    + + +
    +
    +
    + + + + + 0) $guild = true; + } + + if ($admin || $guild) { + if (!isset($guilds)) { + $guilds = mysql_select_multi("SELECT `id`, `name` FROM `guilds` ORDER BY `name`;"); + $guilds[] = array('id' => '0', 'name' => 'No guild'); + } + $guildName = array(); + foreach($guilds as $guild) { + $guildName[$guild['id']] = $guild['name']; + } + if ($admin) { + ?> + + + + + '; + echo '"; + + // Admin columns + if ($admin) { + ?> + + + '; + } + } + if ($count == 0 && !$admin) echo ''; + ?> +
    Guild BoardsEditDelete
    '; + if ($board['closed'] == 1) echo $config['forum']['closed'],' '; + if ($board['hidden'] == 1) echo $config['forum']['hidden'],' '; + if ($board['guild_id'] > 0) { + echo "[". $guildName[$board['guild_id']] ."] "; + } + echo $board['name'] ." +
    + + +
    +
    +
    + + +
    +
    You don\'t have access to any guildboards.
    + +

    Create board:

    +
    +

    + + Required access:

    + + Board closed:
    + + Board hidden:

    + + Guild board:

    + + +
    + diff --git a/app/ZnoteAAC/forum_search.php b/app/ZnoteAAC/forum_search.php new file mode 100644 index 0000000..ea01567 --- /dev/null +++ b/app/ZnoteAAC/forum_search.php @@ -0,0 +1,224 @@ +Search forum +
    + + + +
    +*/ +function stripBBCode($text_to_search) { + $pattern = '|[[\/\!]*?[^\[\]]*?]|si'; + $replace = ''; + return preg_replace($pattern, $replace, $text_to_search); +} + +//data_dump($_GET, false, "Post data:"); + +// Fetch and sanitize values: +$type = getValue($_GET['type']); +if ($type !== false) $type = (int)$type; +$text = getvalue($_GET['text']); + +$textTitleSql = ""; +$textPostSql = ""; +$textAuthorSql = ""; +if ($text !== false) { + $text = explode(' ', $text); + for ($i = 0; $i < count($text); $i++) { + if ($i != count($text) -1) { + $textTitleSql .= "`title` LIKE '%". $text[$i] ."%' AND "; + $textPostSql .= "`text` LIKE '%". $text[$i] ."%' AND "; + $textAuthorSql .= "`player_name` LIKE '%". $text[$i] ."%' AND "; + } else { + $textTitleSql .= "`title` LIKE '%". $text[$i] ."%'"; + $textPostSql .= "`text` LIKE '%". $text[$i] ."%'"; + $textAuthorSql .= "`player_name` LIKE '%". $text[$i] ."%'"; + } + } + //data_dump($text, array($textTitleSql, $textPostSql, $textAuthorSql), "search"); +} + +?> +

    Search forum

    +
    + + + +
    + 4 && $type <= 6) { + $forums = mysql_select_multi("SELECT `id` FROM `znote_forum` WHERE `access`='1' AND `guild_id`='0';"); + $allowedForums = array(); + foreach($forums as $forum) $allowedForums[] = $forum['id']; + + //data_dump($allowedForums, false, "Allowed forums to search"); + // in_array(6, $allowedForums) + + $results = false; + switch ($type) { + case 1: // Search titles + $results = mysql_select_multi("SELECT `id` AS `thread_id`, `forum_id`, `title`, `text`, `player_name` FROM `znote_forum_threads` WHERE $textTitleSql ORDER BY `id` DESC LIMIT $searchResults;"); + // Filter out search results in custom access boards. + for ($i = 0; $i < count($results); $i++) + if (!in_array($results[$i]['forum_id'], $allowedForums)) + $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + + //if ($results !== false) data_dump($results, false, "Search results"); + //else echo "
    No results."; + break; + + case 2: // Search posts + $results = mysql_select_multi("SELECT `thread_id`, `player_name`, `text` FROM `znote_forum_posts` WHERE $textPostSql ORDER BY `id` DESC LIMIT $searchResults;"); + // Missing ['forum_id'], ['title'], lets get them + for ($i = 0; $i < count($results); $i++) { + // $results[$i]['asd'] + $thread = mysql_select_single("SELECT `forum_id`, `title` FROM `znote_forum_threads` WHERE `id`='".$results[$i]['thread_id']."' LIMIT 1;"); + if ($thread !== false) { + $results[$i]['forum_id'] = $thread['forum_id']; + $results[$i]['title'] = $thread['title']; + if (!in_array($results[$i]['forum_id'], $allowedForums)) $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + } else $results[$i]['forum_id'] = false; + + } // DONE. :) + //data_dump(false, $results, "DATA"); + break; + + case 3: // Search authors last threads + $results = mysql_select_multi("SELECT `id` AS `thread_id`, `forum_id`, `title`, `text`, `player_name` FROM `znote_forum_threads` WHERE $textAuthorSql ORDER BY `id` DESC LIMIT $searchResults;"); + // Filter out search results in custom access boards. + for ($i = 0; $i < count($results); $i++) + if (!in_array($results[$i]['forum_id'], $allowedForums)) + $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + + //if ($results !== false) data_dump($results, false, "Search results"); + //else echo "
    No results."; + break; + + case 4: // Search authors last posts + $results = mysql_select_multi("SELECT `thread_id`, `player_name`, `text` FROM `znote_forum_posts` WHERE $textAuthorSql ORDER BY `id` DESC LIMIT $searchResults;"); + // Missing ['forum_id'], ['title'], lets get them + for ($i = 0; $i < count($results); $i++) { + // $results[$i]['asd'] + $thread = mysql_select_single("SELECT `forum_id`, `title` FROM `znote_forum_threads` WHERE `id`='".$results[$i]['thread_id']."' LIMIT 1;"); + if ($thread !== false) { + $results[$i]['forum_id'] = $thread['forum_id']; + $results[$i]['title'] = $thread['title']; + if (!in_array($results[$i]['forum_id'], $allowedForums)) $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + } else $results[$i]['forum_id'] = false; + + } // DONE. :) + break; + + case 5: // Search latest titles + $results = mysql_select_multi("SELECT `id` AS `thread_id`, `forum_id`, `title`, `text`, `player_name` FROM `znote_forum_threads` ORDER BY `id` DESC LIMIT $searchResults;"); + // Filter out search results in custom access boards. + for ($i = 0; $i < count($results); $i++) + if (!in_array($results[$i]['forum_id'], $allowedForums)) + $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + + //if ($results !== false) data_dump($results, false, "Search results"); + //else echo "
    No results."; + break; + + case 6: // Search posts + $results = mysql_select_multi("SELECT `thread_id`, `player_name`, `text` FROM `znote_forum_posts` ORDER BY `id` DESC LIMIT $searchResults;"); + // Missing ['forum_id'], ['title'], lets get them + for ($i = 0; $i < count($results); $i++) { + // $results[$i]['asd'] + $thread = mysql_select_single("SELECT `forum_id`, `title` FROM `znote_forum_threads` WHERE `id`='".$results[$i]['thread_id']."' LIMIT 1;"); + if ($thread !== false) { + $results[$i]['forum_id'] = $thread['forum_id']; + $results[$i]['title'] = $thread['title']; + if (!in_array($results[$i]['forum_id'], $allowedForums)) $results[$i]['forum_id'] = false; + else { + $results[$i]['title'] = stripBBCode($results[$i]['title']); + $results[$i]['text'] = stripBBCode($results[$i]['text']); + } + } else $results[$i]['forum_id'] = false; + + } // DONE. :) + //data_dump(false, $results, "DATA"); + break; + default: + # code... + break; + } + + // Create table and show stuff! + if ($results !== false) { + $count = 0; + foreach ($results as $r) if ($r['forum_id'] !== false) $count++; + if ($count > 0) { + ?> + + + + + + + + + + + + + +
    CharThreadPost
    + + + + 140) ? substr($result['text'],0,137).'...' : $result['text']; ?>
    + You must fill in all fields!"; + +include 'layout/overall/footer.php'; +?> diff --git a/app/ZnoteAAC/gallery.php b/app/ZnoteAAC/gallery.php new file mode 100644 index 0000000..4bae2ff --- /dev/null +++ b/app/ZnoteAAC/gallery.php @@ -0,0 +1,131 @@ + +

    Create image article

    +

    This gallery is powered by IMGUR image host.

    +
    + Select image to upload:

    + Image Title:

    + Image Description:

    + +
    + "file", + "name" => $_FILES['imagefile']['name'], + "image" => $image + ]); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Authorization: Client-ID {$imgurClientID}" + )); + $response = json_decode(curl_exec($ch)); + $image_url = $response->data->link; + $image_delete = $response->data->deletehash; + $title = $_POST['title']; + $desc = $_POST['desc']; + + if ($image_url !== false) { + + // Insert to database + $inserted = insertImage((int)$session_user_id, $title, $desc, $image_url, $image_delete); + if ($inserted === true) { + ?> +

    Image Posted

    +

    However, your image will not be listed until a GM have verified it.
    + Feel free to remind the GM in-game to login on website and approve the image post.

    + +

    Preview:

    + + + + + + + + + + +

    + <?php echo $title; ?> +
    + ", $descr); + ?> +

    +
    + +

    Image already exist

    +

    The image has already been posted. However, images will not be listed until a GM have verified it.

    + +

    Failed to find the image

    +

    We failed to find the image, did you give us the Image code from www.freeimagehosting.net?

    + +

    Gallery

    + +
    + Got some cool images to show the community? +
    + load(); + if (is_array($images) && !empty($images)) { + foreach($images as $image) { + ?> + + + + + + + + + + +

    + <?php echo $image['title']; ?> +
    + ", $descr); + ?> +

    +
    + There are currently no public images.'; + + if ($logged_in === false) echo 'You need to be logged in to add images.'; +} +include 'layout/overall/footer.php'; +?> diff --git a/app/ZnoteAAC/guilds.php b/app/ZnoteAAC/guilds.php new file mode 100644 index 0000000..809a2b8 --- /dev/null +++ b/app/ZnoteAAC/guilds.php @@ -0,0 +1,982 @@ +hasExpired()) { + if ($TFSVersion != 'TFS_10') + if ($TFSVersion === 'OTHIRE') + $guilds = mysql_select_multi("SELECT `t`.`id`, `t`.`name`, `t`.`creationdate`, (SELECT count(p.rank_id) FROM players AS p LEFT JOIN guild_ranks AS gr ON gr.id = p.rank_id WHERE gr.guild_id =`t`.`id`) AS `total` FROM `guilds` as `t` ORDER BY `t`.`name`;"); + else + $guilds = mysql_select_multi("SELECT `t`.`id`, `t`.`name`, `t`.`creationdata`, `motd`, (SELECT count(p.rank_id) FROM players AS p LEFT JOIN guild_ranks AS gr ON gr.id = p.rank_id WHERE gr.guild_id =`t`.`id`) AS `total` FROM `guilds` as `t` ORDER BY `t`.`name`;"); + else + $guilds = mysql_select_multi("SELECT `id`, `name`, `creationdata`, `motd`, (SELECT COUNT('guild_id') FROM `guild_membership` WHERE `guild_id`=`id`) AS `total` FROM `guilds` ORDER BY `name`;"); + + // Add level data info to guilds + if ($guilds !== false) + for ($i = 0; $i < count($guilds); $i++) + $guilds[$i]['level'] = get_guild_level_data($guilds[$i]['id']); + + $cache->setContent($guilds); + $cache->save(); + } else { + $guilds = $cache->load(); + } + return $guilds; +} + +include 'layout/overall/header.php'; + +if (user_logged_in() === true) { + + // fetch data + $char_count = user_character_list_count($session_user_id); + $char_array = user_character_list($user_data['id']); + $characters = array(); + $charactersId = array(); + $charactersRank = array(); + if ($char_array !== false) { + foreach ($char_array as $value) { + $characters[] = $value['name']; + $charactersId[] = $value['id']; + $charactersRank[] = $value['rank_id']; + } + } +} else { + $char_count = 0; +} + +if (empty($_GET['name'])) { +// Display the guild list + +//data_dump($guild, false, "guild data"); + +$guilds = guild_list($config['ServerEngine']); + +if (isset($guilds) && !empty($guilds) && $guilds !== false) { + //data_dump($guilds, false, "Guilds"); +?> + + + + + + + + = 1) { + $url = url("guilds.php?name=". $guild['name']); + ?> + + + + + + '. getClock($guild['creationdata'], true) .''; + } + } + ?> +
    LogoDescriptionGuild data
    + + + + 0) echo '
    '.$guild['motd']; ?> +
    + +
    +
    +
    +Guild list is empty.

    ';?> + += $config['create_guild_level']) { + + // If character is offline + if ($char_data['online'] == 0) { + + // If character is premium + if ($config['guild_require_premium'] == false || $user_data['premdays'] > 0) { + + if (get_character_guild_rank($user_id) < 1) { + + if (preg_match("/^[a-zA-Z_ ]+$/", $_POST['guild_name'])) { + // Only allow normal symbols as guild name + if (strlen($_POST['guild_name']) < 31) { + + $guildname = sanitize($_POST['guild_name']); + + $gid = get_guild_id($guildname); + if ($gid === false) { + create_guild($user_id, $guildname); + // Re-cache the guild list + $guilds = guild_list($config['ServerEngine']); + header('Location: success.php'); + exit(); + } else echo 'A guild with that name already exist.'; + } else echo 'Guild name is to long. It can has to be 30 or less characters long.'; + } else echo 'Guild name may only contain a-z, A-Z and spaces.'; + } else echo 'You are already in a guild.'; + } else echo 'You need a premium account to create a guild.'; + } else echo 'Your character must be offline to create a guild.'; + } else echo $name .' is level '. $char_data['level'] .'. But you need level '. $config['create_guild_level'] .'+ to create your own guild!'; + } + } + // end + ?> + + + +
    +
      +
    • + Create Guild:
      + + + + +
    • +
    +
    + + + + + $highest_access) $highest_access = $access; + } + } + + } + } + } + // Display the specific guild page +?> + +
    + ".sanitize($_GET['error'])."

    " : ""; ?> + +
    + +
    + +
    +

    Guild:

    +

    +
    +
    + + + + + + + + + '; + echo ''; + $rankName = $player['rank_name']; + echo ''; + echo ''; + echo ''; + if ($chardata['online'] == 1) echo ''; + else echo ''; + echo ''; + } + ?> +
    Rank:Name:Level:Vocation:Status:
    ' . ($rankName !== $player['rank_name'] ? $player['rank_name'] : '') . ''. $player['name'] .''; + if (!empty($player['guildnick'])) { + echo ' ('. $player['guildnick'] .')'; + } + echo ''. $player['level'] .''. $config['vocations'][$player['vocation']]['name'] .' Online Offline
    + + 0) { ?> +

    Invited characters

    + + + + Remove:'; + } + // Shuffle through visitor characters + $exist = false; + for ($i = 0; $i < $char_count; $i++) { + // Shuffle through invited character, see if they match your character. + if ($inv_data !== false) foreach ($inv_data as $inv) { + if ($charactersId[$i] == $inv['player_id']) { + $exist = true; + break; + } + } + } + if ($exist) echo ''; + ?> + + '; + echo ''; + // Remove invitation + if ($highest_access == 2 || $highest_access == 3) { + ?> '; + echo ''; + echo ''; + echo ''; + ?> '; + echo ''; + echo ''; + echo ''; + $bool = true; + } + } + if (isset($bool, $exist) && !$bool && $exist) { + echo ''; + $bool = false; + } + ?> '; + echo ''; + echo ''; + echo ''; + $bool = true; + } + } + if (isset($bool, $exist) && !$bool && $exist) { + echo ''; + $bool = false; + } + ?> '; + } + ?> +
    Name:Join Guild:Reject Invitation:
    '. $inv['name'] .'
    + + +Join guild request sent from wrong account.'; + include 'layout/overall/footer.php'; + exit(); + } + // Join a guild + if ($inv_data !== false) foreach ($inv_data as $inv) { + if ((int)$inv['player_id'] == $joining_player_id) { + if ($config['ServerEngine'] !== 'TFS_10') $chardata = user_character_data($joining_player_id, 'online'); + else $chardata['online'] = (user_is_online_10($joining_player_id)) ? 1 : 0; + if ($chardata['online'] == 0) { + // Ensure player is not already a member of another guild + if (get_character_guild_rank($joining_player_id) === false) { + if (guild_player_join($joining_player_id, (int)$gid)) { + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'Failed to find guild position representing member.'; + } else { + $already_guild = get_player_guild_data($joining_player_id); + $already_guild_name = get_guild_name($already_guild['guild_id']); + echo "You are already {$already_guild['rank_name']} of another guild: {$already_guild_name}.
    You need to leave that guild first before you can join another one.
    "; + } + } else echo 'Character must be offline before joining guild.'; + } + } + } + + if (!empty($_POST['leave_guild'])) { + $name = sanitize($_POST['leave_guild']); + $cidd = user_character_id($name); + + $leave_account = (int)user_character_account_id($name); + if ($leave_account !== $session_user_id) { + echo 'Leave guild request sent from wrong account.'; + include 'layout/overall/footer.php'; + exit(); + } + + // If character is offline + if ($config['ServerEngine'] !== 'TFS_10') $chardata = user_character_data($cidd, 'online'); + else $chardata['online'] = (user_is_online_10($cidd)) ? 1 : 0; + if ($chardata['online'] == 0) { + if ($config['ServerEngine'] !== 'TFS_10') guild_player_leave($cidd); + else guild_player_leave_10($cidd); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'Character must be offline first!'; + } + +if ($highest_access >= 2) { + // Guild leader stuff + + // Change Guild Nick + if (!empty($_POST['player_guildnick'])) { + if ($config['guild_allow_nicknames']) { + $p_cid = user_character_id($_POST['player_guildnick']); + $p_guild = get_player_guild_data($p_cid); + if (preg_match("/^[a-zA-Z_ ]+$/", $_POST['guildnick']) || empty($_POST['guildnick'])) { + // Only allow normal symbols as guild nick + $p_nick = sanitize($_POST['guildnick']); + if ($p_guild['guild_id'] == $gid) { + if ($config['ServerEngine'] !== 'TFS_10') $chardata = user_character_data($p_cid, 'online'); + else $chardata['online'] = (user_is_online_10($p_cid)) ? 1 : 0; + if ($chardata['online'] == 0) { + if ($config['ServerEngine'] !== 'TFS_10') update_player_guildnick($p_cid, $p_nick); + else update_player_guildnick_10($p_cid, $p_nick); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'Character not offline.'; + } + } else echo 'Character guild nick may only contain a-z, A-Z and spaces.'; + } else echo 'Change guild nickname feature has been disabled.'; + } + + // Promote character to guild position + if (!empty($_POST['promote_character']) && !empty($_POST['promote_position'])) { + // Verify that promoted character is from this guild. + $p_rid = $_POST['promote_position']; + $p_cid = user_character_id($_POST['promote_character']); + $p_guild = get_player_guild_data($p_cid); + + if ($p_guild['guild_id'] == $gid) { + // Do the magic. + if ($config['ServerEngine'] !== 'TFS_10') $chardata = user_character_data($p_cid, 'online'); + else $chardata['online'] = (user_is_online_10($p_cid)) ? 1 : 0; + if ($chardata['online'] == 0) { + if ($config['ServerEngine'] !== 'TFS_10') update_player_guild_position($p_cid, $p_rid); + else update_player_guild_position_10($p_cid, $p_rid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'Character not offline.'; + + } + } + if (!empty($_POST['invite'])) { + if (user_character_exist($_POST['invite'])) { + // Make sure they are not in another guild + + if ($config['ServerEngine'] != 'TFS_10') { + $charname = sanitize($_POST['invite']); + $playerdata = mysql_select_single("SELECT `id`, `rank_id` FROM `players` WHERE `name`='$charname' LIMIT 1;"); + $charid = $playerdata['id']; + $membership = ($playerdata['rank_id'] > 0) ? true : false; + } else { + $charid = user_character_id($_POST['invite']); + $membership = mysql_select_single("SELECT `rank_id` FROM `guild_membership` WHERE `player_id`='$charid' LIMIT 1;"); + } + if (!$membership) { + // + $status = false; + if ($inv_data !== false) { + foreach ($inv_data as $inv) { + if ($inv['player_id'] == user_character_id($_POST['invite'])) $status = true; + } + } + foreach ($players as $player) { + if ($player['name'] == $_POST['invite']) $status = true; + } + + if ($status == false) { + guild_invite_player($charid, $gid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'That character is already invited(or a member) on this guild.'; + } else echo 'That character is already in a guild.'; + + } else echo 'That character name does not exist.'; + } + // Guild Message (motd) + if (!empty($_POST['motd'])) { + $motd = sanitize($_POST['motd']); + mysql_update("UPDATE `guilds` SET `motd`='$motd' WHERE `id`='$gid' LIMIT 1;"); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + + if (!empty($_POST['disband'])) { + // $gidd = (int)$_POST['disband']; + $members = get_guild_players($gid); + $online = false; + + // First figure out if anyone are online. + foreach ($members as $member) { + if ($config['ServerEngine'] !== 'TFS_10') $chardata = user_character_data(user_character_id($member['name']), 'online'); + else $chardata['online'] = (user_is_online_10(user_character_id($member['name']))) ? 1 : 0; + if ($chardata['online'] == 1) { + $online = true; + } + } + + if (!$online) { + // Then remove guild rank from every player. + if ($config['ServerEngine'] !== 'TFS_10') foreach ($members as $member) guild_player_leave(user_character_id($member['name'])); + else foreach ($members as $member) guild_player_leave_10(user_character_id($member['name'])); + + // Remove all guild invitations to this guild + if ($inv_count > 0) guild_remove_invites($gidd); + + // Then remove the guild itself. + guild_delete($gidd); + header('Location: success.php'); + exit(); + } else echo 'All members must be offline to disband the guild.'; + } + + if (!empty($_POST['new_leader'])) { + $new_leader = (int)$_POST['new_leader']; + $old_leader = guild_leader($gid); + + $online = false; + if ($config['ServerEngine'] !== 'TFS_10') { + $newData = user_character_data($new_leader, 'online'); + $oldData = user_character_data($old_leader, 'online'); + } else { + $newData['online'] = (user_is_online_10($new_leader)) ? 1 : 0; + $oldData['online'] = (user_is_online_10($old_leader)) ? 1 : 0; + } + if ($newData['online'] == 1 || $oldData['online'] == 1) $online = true; + + if ($online == false) { + if (guild_change_leader($new_leader, $old_leader)) { + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'Something went wrong when attempting to change leadership.'; + } else echo 'The new and old leader must be offline to change leadership.'; + } + + if (!empty($_POST['change_ranks'])) { + //$c_gid = (int)$_POST['change_ranks']; + $c_ranks = get_guild_rank_data($gid); + $rank_data = array(); + $rank_ids = array(); + + // Feed new rank data + foreach ($c_ranks as $rank) { + $tmp = 'rank_name!'. $rank['level']; + if (!empty($_POST[$tmp])) { + $rank_data[$rank['level']] = sanitize($_POST[$tmp]); + $rank_ids[$rank['level']] = $rank['id']; + } + } + + foreach ($rank_data as $level => $name) { + guild_change_rank($rank_ids[$level], $name); + } + + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + + if (!empty($_POST['remove_member'])) { + $name = sanitize($_POST['remove_member']); + $cid = user_character_id($name); + + $p_guild = get_player_guild_data($cid); + if ($p_guild['guild_id'] == $gid) { + if ($config['ServerEngine'] !== 'TFS_10') guild_remove_member($cid); + else guild_remove_member_10($cid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + } + + if (!empty($_POST['forumGuildId'])) { + if ($config['forum']['guildboard'] === true) { + $forumExist = mysql_select_single("SELECT `id` FROM `znote_forum` WHERE `guild_id`='$gid' LIMIT 1;"); + if ($forumExist === false) { + // Insert data + mysql_insert("INSERT INTO `znote_forum` (`name`, `access`, `closed`, `hidden`, `guild_id`) + VALUES ('Guild', + '1', + '0', + '0', + '$gid');"); + echo '

    Guild board has been created.

    '; + } else echo '

    Guild board already exist.

    '; + + } else { + echo '

    Error: Guild board system is disabled.

    '; + } + } + + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'OTHIRE' || $config['ServerEngine'] == 'TFS_10' && $config['guildwar_enabled'] === true) { + if (!empty($_POST['warinvite'])) { + $targetGuild = get_guild_id($_POST['warinvite']); + if ($targetGuild) { + $status = false; + $war_invite = mysql_select_single("SELECT `id` FROM `guilds` WHERE `id` = '$gid';"); + if ($war_invite !== false) { + foreach ($war_invite as $inv) { + if ($inv['id'] == $targetGuild) $status = true; + } + } + + $check_guild = get_guild_name($gid); + foreach ($check_guild as $guild) { + if ($guild['name'] == $_POST['warinvite']) $status = true; + } + + if ((int)$gid === (int)$targetGuild) $status = true; + + $wars = mysql_select_multi("SELECT `id`, `guild1`, `guild2`, `status` FROM `guild_wars` WHERE (`guild1` = '$gid' OR `guild1` = '$targetGuild') AND (`guild2` = '$gid' OR `guild2` = '$targetGuild') AND `status` IN (0, 1);"); + if ($status == false && $wars == false) { + guild_war_invitation($gid, $targetGuild); + $limit = (empty($_POST['limit'])) ? 100 : (int)$_POST['limit']; + mysql_insert("INSERT INTO `znote_guild_wars` (`limit`) VALUES ('$limit');"); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } else echo 'This guild has already been invited to war(or you\'re trying to invite your own).'; + } else echo 'That guild name does not exist.'; + } + + if (!empty($_POST['cancel_war_invite'])) { + cancel_war_invitation($_POST['cancel_war_invite'], $gid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + + if (!empty($_POST['reject_war_invite'])) { + reject_war_invitation($_POST['reject_war_invite'], $gid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + + if (!empty($_POST['accept_war_invite'])) { + accept_war_invitation($_POST['accept_war_invite'], $gid); + header('Location: guilds.php?name='. $_GET['name']); + exit(); + } + } + + $members = count_guild_members($gid); + $ranks = get_guild_rank_data($gid); + ?> + + +
    +
      +
    • Create forum guild board:
      + + +
    +
    + + + +
    +
      +
    • Upload guild logo [.gif images only, 100x100px size]:
      + + +
    • +
    +
    + +
    '; + } + + } ?> + +
    +
      +
    • Invite Character to guild:
      + + +
    • +
    +
    + +
    +
      +
    • Change guild message:
    • +
    • +
      + +
    • +
    +
    + + +
    +
      +
    • + Change Guild Nick:
      + + + +
    • +
    +
    + + + 1) { ?> + +
    +
      +
    • + Promote Character:
      + + + +
    • +
    +
    + +
    +
      +
    • + Kick member from guild:
      + + +
    • +
    +
    + +

    + + +
    +
      +
    • Change rank titles:
      + '; + } + echo ''; + ?> + +
    • +
    +
    + +
    +
      +
    • DELETE GUILD (All members must be offline):
      + '; ?> + +
    • +
    +
    + + 1) { ?> +
    +
      +
    • Change Leadership with:
      + + +
    • +
    +
    + + +

    Guild War Management:

    +
    +
      +
    • Invite guild to war:
      + + + +
    • +
    +
    + + + '; + } + } + + if ($i == 0) + echo ''; + echo '
    AggressorInformationEnemy
    '.$war['name1'].''; + echo '
    Pending invitation
    Invited on ' . getClock($war['started'], true) . '.
    The frag limit is set to ' . $war['limit'] . ' frags.
    '; + if ($war['guild1'] == $gid) { + echo '
    '; + } else if ($war['guild2'] == $gid) { + echo '
    '; + echo '
    '; + } + echo '
    '.$war['name2'].'
    Currently there are no pending invitations.
    '; + } } ?> + + + +

    War overview:

    + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + } + ?> +
    Attacker:Defender:status:started:
    '. $wars['name1'] .''. $wars['name2'] .''. $config['war_status'][$wars['status']] .''. getClock($wars['started'], true) .'
    + + + - Visit Guild Board


    + +
    +
      +
    • + Leave Guild:
      + + +
    • +
    +
    + diff --git a/app/ZnoteAAC/guildwar.php b/app/ZnoteAAC/guildwar.php new file mode 100644 index 0000000..02e518b --- /dev/null +++ b/app/ZnoteAAC/guildwar.php @@ -0,0 +1,200 @@ + +

    - VERSUS -

    + + data: + $guild1 = $war['guild1']; + $g1c = 0; // kill count + + $guild2 = $war['guild2']; + $g2c = 0; // kill count + + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'OTHIRE' || $config['ServerEngine'] == 'TFS_10') { + foreach (($kills ? $kills : array()) as $kill) { + if ($kill['killerguild'] == $guild1) + $g1c++; + else + $g2c++; + } + + $green = false; + if ($g1c > $g2c) { + $leading = $war['name1']; + $green = true; + } else if ($g2c > $g1c) $leading = $war['name2']; + else $leading = "Tie"; + } + ?> +
      +
    • + War status: . +
    • + +
    • + Leading guild: . +
    • +
    • + '. $g1c .'-'. $g2c .''; + else if ($g1c == $g2c) + echo 'Score: '. $g1c .'-'. $g2c .''; + else + echo 'Score: '. $g1c .'-'. $g2c .''; + ?> +
    • + +
    + + + + + + + + + '; + //echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + ?> +
    Killer's guild:Killer:Victim:Time:
    '. get_guild_name($kill['killerguild']) .''. get_guild_name($kill['killerguild']) .''. $kill['killer'] .''. $kill['target'] .''. getClock($kill['time'], true) .'
    + + +".$death['name']." "; + foreach($killers as $killer) + { + $i++; + if($killer['is_war'] != 0) + { + if($i == 1) + $main_content .= "killed at level ".$death['level']." by "; + else if($i == $count && $others == false) + $main_content .= " and by "; + else + $main_content .= ", "; + if($killer['player_exists'] == 0) + $main_content .= ""; + + $main_content .= $killer['player_name']; + if($killer['player_exists'] == 0) + $main_content .= ""; + } + else + $others = true; + + if($i == $count) + { + if($others == true) + $main_content .= " and few others"; + $main_content .= ".
    "; + } + } + } + } + else + $main_content .= "
    There were no frags on this war so far.
    "; + echo $main_content; + // END BORROWED FROM GESIOR + } + } + +} else { + // Display current wars. + + // Fetch list of wars + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'TFS_10' || $config['ServerEngine'] == 'OTHIRE') $wardata = get_guild_wars(); + else if ($config['ServerEngine'] == 'TFS_03') $wardata = get_guild_wars03(); + else die("Can't recognize TFS version. It has to be either TFS_02 or TFS_03. Correct this in config.php"); + //echo $wardata[0]['name1']; + //die(var_dump($wardata)); + if ($wardata != false) { + // kills data + $killsdata = array(); // killsdata[guildid] => array(warid) => array info about the selected war entry + foreach ($wardata as $wars) { + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'TFS_10' || $config['ServerEngine'] == 'OTHIRE') $killsdata[$wars['id']] = get_war_kills($wars['id']); + else if ($config['ServerEngine'] == 'TFS_03') $killsdata[$wars['id']] = get_war_kills03($wars['id']); + } + ?> + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + ?> +
    Attacking Guild:Death Count:Defending Guild:
    '. $wars['name1'] .''. $guild_1_kills .' - ' . $guild_2_kills . ''. $wars['name2'] .'View
    + + diff --git a/app/ZnoteAAC/helpdesk.php b/app/ZnoteAAC/helpdesk.php new file mode 100644 index 0000000..51c7134 --- /dev/null +++ b/app/ZnoteAAC/helpdesk.php @@ -0,0 +1,225 @@ + 0) ? (int)$_GET['view'] : false; +if ($view !== false) { + if (!empty($_POST['reply_text'])) { + + // Save ticket reply on database + $query = array( + 'tid' => $view, + 'username'=> getValue($_POST['username']), + 'message' => getValue($_POST['reply_text']), + 'created' => time(), + ); + $fields = '`'. implode('`, `', array_keys($query)) .'`'; + $data = '\''. implode('\', \'', $query) .'\''; + mysql_insert("INSERT INTO `znote_tickets_replies` ($fields) VALUES ($data)"); + mysql_update("UPDATE `znote_tickets` SET `status`='Player-Reply' WHERE `id`='$view' LIMIT 1;"); + } + $ticketData = mysql_select_single("SELECT * FROM znote_tickets WHERE id='$view' LIMIT 1;"); + + if($ticketData['owner'] != $session_user_id) { + echo 'You can not view this ticket!'; + include 'layout/overall/footer.php'; + die; + } + ?> +

    View Ticket # + [CLOSED]'; + } + ?>

    + + + + + + + +
    + + - Created by: + +
    +

    +
    + + + + + + + + +
    + + - Posted by: + +
    +

    +
    +
    + + + +
    +
    +
    + +
    + + $value) { + if (empty($value) && in_array($key, $required_fields) === true) { + $errors[] = 'You need to fill in all fields.'; + break 1; + } + } + + // check errors (= user exist, pass long enough + if (empty($errors) === true) { + /* Token used for cross site scripting security */ + if (!Token::isValid($_POST['token'])) { + $errors[] = 'Token is invalid.'; + } + if ($config['use_captcha']) { + if(!verifyGoogleReCaptcha($_POST['g-recaptcha-response'])) { + $errors[] = "Please confirm that you're not a robot."; + } + } + // Reversed this if, so: first check if you need to validate, then validate. + if ($config['validate_IP'] === true && validate_ip(getIP()) === false) { + $errors[] = 'Failed to recognize your IP address. (Not a valid IPv4 address).'; + } + } + } + ?> +

    Latest Tickets

    + + + + + + + + + '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + ?> +
    ID:Subject:Creation:Status:
    '. $ticket['id'] .''. $ticket['subject'] .''. getClock($ticket['creation'], true) .''. $ticket['status'] .'
    + + +

    Helpdesk

    + $session_user_id, + 'username'=> getValue($_POST['username']), + 'subject' => getValue($_POST['subject']), + 'message' => getValue($_POST['message']), + 'ip' => getIPLong(), + 'creation' => time(), + 'status' => 'Open' + ); + + $fields = '`'. implode('`, `', array_keys($query)) .'`'; + $data = '\''. implode('\', \'', $query) .'\''; + mysql_insert("INSERT INTO `znote_tickets` ($fields) VALUES ($data)"); + + header('Location: helpdesk.php?success'); + exit(); + + } else if (empty($errors) === false) { + echo ''; + echo output_errors($errors); + echo ''; + } + ?> +
    +
      +
    • + Account Name:
      + +
    • +
    • + Email:
      + +
    • +
    • + Subject:
      + +
    • +
    • + Message:
      + +
    • + +
    • +
      +
    • + + +
    • + + +
    • +
    +
    + diff --git a/app/ZnoteAAC/highscores.php b/app/ZnoteAAC/highscores.php new file mode 100644 index 0000000..2e47a92 --- /dev/null +++ b/app/ZnoteAAC/highscores.php @@ -0,0 +1,152 @@ + 9) $type = 7; + +// Fetch highscore vocation +$configVocations = $config['vocations']; +//$debug['configVocations'] = $configVocations; + +$vocationIds = array_keys($configVocations); + +$vocation = 'all'; +if (isset($_GET['vocation']) && is_numeric($_GET['vocation'])) { + $vocation = (int)$_GET['vocation']; + if (!in_array($vocation, $vocationIds)) { + $vocation = "all"; + } +} + +// Fetch highscore page +$page = getValue(@$_GET['page']); +if (!$page || $page == 0) $page = 1; +else $page = (int)$page; + +$highscore = $config['highscore']; +$loadFlags = ($config['country_flags']['enabled'] && $config['country_flags']['highscores']) ? true : false; +$loadOutfits = ($config['show_outfits']['highscores']) ? true : false; + +$rows = $highscore['rows']; +$rowsPerPage = $highscore['rowsPerPage']; + +function skillName($type) { + $types = array( + 1 => "Club", + 2 => "Sword", + 3 => "Axe", + 4 => "Distance", + 5 => "Shield", + 6 => "Fish", + 7 => "Experience", // Hardcoded + 8 => "Magic Level", // Hardcoded + 9 => "Fist", // Since 0 returns false I will make 9 = 0. :) + ); + return $types[(int)$type]; +} + +function pageCheck($index, $page, $rowPerPage) { + return ($index < ($page * $rowPerPage) && $index >= ($page * $rowPerPage) - $rowPerPage) ? true : false; +} + +$cache = new Cache('engine/cache/highscores'); +if ($cache->hasExpired()) { + $vocGroups = fetchAllScores($rows, $config['ServerEngine'], $highscore['ignoreGroupId'], $configVocations, $vocation, $loadFlags, $loadOutfits); + $cache->setContent($vocGroups); + $cache->save(); +} else { + $vocGroups = $cache->load(); +} + +if ($vocGroups) { + $vocGroup = (is_array($vocGroups[$vocation])) ? $vocGroups[$vocation] : $vocGroups[$vocGroups[$vocation]]; + ?> + +

    Ranking for .

    + +
    + + + + + + + + +
    + + + + + Outfit"; ?> + + + + + Points"; ?> + + + + + + + 1) ? ' ' : ''; + ?> + + + + + + + + + ". $vocGroup[$type][$i]['experience'] .""; ?> + + +
    RankNameVocationLevel
    Nothing to show here yet.
    img
    + diff --git a/app/ZnoteAAC/house.php b/app/ZnoteAAC/house.php new file mode 100644 index 0000000..01ebec7 --- /dev/null +++ b/app/ZnoteAAC/house.php @@ -0,0 +1,259 @@ + 0) ? (int)$_GET['id'] : false; + +if ($house !== false && $config['ServerEngine'] === 'TFS_10') { + $house_SQL = "SELECT `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`, `beds`, `bid`, `bid_end`, `last_bid`, `highest_bidder` FROM `houses` WHERE `id`='$house';"; + $house = mysql_select_single($house_SQL); + $minbid = $config['houseConfig']['minimumBidSQM'] * $house['size']; + if ($house['owner'] > 0) $house['ownername'] = user_name($house['owner']); + + if ($config['houseConfig']['shopPoints']['enabled']) { + $house['points'] = $house['size']; + + foreach ($config['houseConfig']['shopPoints']['cost'] AS $cost_sqm => $cost_points) { + if ($cost_sqm < $house['size']) $house['points'] = $cost_points; + } + } + + //data_dump($house, false, "House data"); + + ////////////////////// + // Bid on house logic + $bid_char = &$_POST['char']; + $bid_amount = &$_POST['amount']; + if ($bid_amount && $bid_char) { + $bid_char = (int)$bid_char; + $bid_amount = (int)$bid_amount; + $player = mysql_select_single("SELECT `id`, `account_id`, `name`, `level`, `balance` FROM `players` WHERE `id`='$bid_char' LIMIT 1;"); + + if (user_logged_in() === true && $player['account_id'] == $session_user_id) { + // Does player have or need premium? + $premstatus = ($config['houseConfig']['requirePremium'] && $user_data['premdays'] == 0) ? false : true; + if ($premstatus) { + // Can player have or bid on more houses? + $pHouseCount = mysql_select_single("SELECT COUNT('id') AS `value` FROM `houses` WHERE ((`highest_bidder`='$bid_char' AND `owner`='$bid_char') OR (`highest_bidder`='$bid_char') OR (`owner`='$bid_char')) AND `id`!='".$house['id']."' LIMIT 1;"); + if ($pHouseCount['value'] < $config['houseConfig']['housesPerPlayer']) { + // Is character level high enough? + if ($player['level'] >= $config['houseConfig']['levelToBuyHouse']) { + // Can player afford this bid? + if ($player['balance'] > $bid_amount) { + // Is bid higher than previous bid? + if ($bid_amount > $house['bid']) { + // Is bid higher than lowest bid? + if ($bid_amount > $minbid) { + // Should only apply to external players, allowing a player to up his pledge without + // being forced to pay his full previous bid. + if ($house['highest_bidder'] != $player['id']) $lastbid = $house['bid'] + 1; + else { + $lastbid = $house['last_bid']; + echo "You have raised the house pledge to ".$bid_amount."gp!
    "; + } + // Has bid already started? + if ($house['bid_end'] > 0) { + if ($house['bid_end'] > time()) { + mysql_update("UPDATE `houses` SET `highest_bidder`='". $player['id'] ."', `bid`='$bid_amount', `last_bid`='$lastbid' WHERE `id`='". $house['id'] ."' LIMIT 1;"); + $house = mysql_select_single("SELECT `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`, `beds`, `bid`, `bid_end`, `last_bid`, `highest_bidder` FROM `houses` WHERE `id`='". $house['id'] ."';"); + } + } else { + $lastbid = $minbid + 1; + $bidend = time() + $config['houseConfig']['auctionPeriod']; + mysql_update("UPDATE `houses` SET `highest_bidder`='". $player['id'] ."', `bid`='$bid_amount', `last_bid`='$lastbid', `bid_end`='$bidend' WHERE `id`='". $house['id'] ."' LIMIT 1;"); + $house = mysql_select_single("SELECT `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`, `beds`, `bid`, `bid_end`, `last_bid`, `highest_bidder` FROM `houses` WHERE `id`='". $house['id'] ."';"); + } + echo "You have the highest bid on this house!"; + } else echo "You need to place a bid that is higher or equal to {$minbid}gp."; + } else { + // Check if current bid is higher than last_bid + if ($bid_amount > $house['last_bid']) { + // Should only apply to external players, allowing a player to up his pledge without + // being forced to pay his full previous bid. + if ($house['highest_bidder'] != $player['id']) { + $lastbid = $bid_amount + 1; + mysql_update("UPDATE `houses` SET `last_bid`='$lastbid' WHERE `id`='". $house['id'] ."' LIMIT 1;"); + $house = mysql_select_single("SELECT `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`, `beds`, `bid`, `bid_end`, `last_bid`, `highest_bidder` FROM `houses` WHERE `id`='". $house['id'] ."';"); + echo "Unfortunately your bid was not higher than previous bidder."; + } else { + echo "You already have a higher pledge on this house."; + } + } else { + echo "Too low bid amount, someone else has a higher bid active."; + } + } + } else echo "You don't have enough money to bid this high."; + } else echo "Your character is to low level, must be higher level than ", $config['houseConfig']['levelToBuyHouse']-1 ," to buy a house."; + } else echo "You cannot have more houses."; + } else echo "You need premium account to purchase houses."; + } else echo "You may only bid on houses for characters on your account."; + } + + //////////////////////////////////////// + // Instantly buy house with shop points + if ($config['houseConfig']['shopPoints']['enabled'] + && isset($_POST['instantbuy']) + && $bid_char + && $house['owner'] == 0 + && isset($house['points'])) { + + $account_points = (int)$user_znote_data['points']; + + if ($account_points >= $house['points']) { + + $bid_char = (int)$bid_char; + $player = mysql_select_single("SELECT `id`, `account_id`, `name`, `level` FROM `players` WHERE `id`='$bid_char' LIMIT 1;"); + $pHouseCount = mysql_select_single("SELECT COUNT('id') AS `value` FROM `houses` WHERE ((`highest_bidder`='$bid_char' AND `owner`='$bid_char') OR (`highest_bidder`='$bid_char') OR (`owner`='$bid_char')) AND `id`!='".$house['id']."' LIMIT 1;"); + + if (user_logged_in() === true + && $player['account_id'] == $session_user_id + && $player['level'] >= $config['houseConfig']['levelToBuyHouse'] + && $pHouseCount['value'] < $config['houseConfig']['housesPerPlayer']) { + + $house_points = (int)$house['points']; + $house_id = $house['id']; + + // Remove points from account + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`-{$house_points} + WHERE `account_id`={$session_user_id} + LIMIT 1; + "); + + // Give new ownership to house + mysql_update(" + UPDATE `houses` + SET `owner` = {$bid_char} + WHERE `id` = {$house_id} + LIMIT 1; + "); + + // Log purchase in znote_shop_logs and znote_shop_orders + $time = time(); + mysql_insert(" + INSERT INTO `znote_shop_logs` + (`account_id`, `player_id`, `type`, `itemid`, `count`, `points`, `time`) VALUES + ({$session_user_id}, {$bid_char}, 7, {$house_id}, 1, {$house_points}, {$time}) + "); + mysql_insert(" + INSERT INTO `znote_shop_orders` + (`account_id`, `type`, `itemid`, `count`, `time`) VALUES + ({$session_user_id}, 7, {$house_id}, {$bid_char}, {$time}) + "); + + // Reload house data + $house = mysql_select_single($house_SQL); + $minbid = $config['houseConfig']['minimumBidSQM'] * $house['size']; + if ($house['owner'] > 0) $house['ownername'] = user_name($house['owner']); + + // Congratulate user and tell them they still has to pay rent (if rent > 0) + ?> +

    Congratulations! +
    You now own this house! +
    Remember to say !shop in-game to process your ownership! + 0): ?> +
    Keep in mind you still need to pay rent on this house, make sure you have enough bank balance to cover it! + +

    + +

    Error: +
    Either your level is too low, or your player already have or is bidding on another house. +
    Your level: . Minimum level to buy house: +
    Your house/bid count: . Maximum house per player: . +

    + +

    House:

    +
      +
    • Town: + ". ($town_name ? $town_name : 'Specify town id ' . $house['town_id'] . ' name in config.php first.') .""; + ?>
    • +
    • Size:
    • +
    • Beds:
    • +
    • Owner: 0) echo "". $house['ownername'] .""; + else echo "Available for auction."; + ?>
    • +
    • Rent:
    • + +
    • Shop points:
    • + +
    + +

    This house is up on auction!

    + This house don't have any bidders yet."; + else { + $bidder = mysql_select_single("SELECT `name` FROM `players` WHERE `id`='". $house['highest_bidder'] ."' LIMIT 1;"); + echo "This house have bidders! If you want this house, now is your chance!"; + echo "
    Active bid: ". $house['last_bid'] ."gp"; + echo "
    Active bid by: ". $bidder['name'] .""; + echo "
    Bid will end on: ". getClock($house['bid_end'], true); + } + + if ($house['bid_end'] == 0 || $house['bid_end'] > time()) { + if (user_logged_in()) { + // Your characters, indexed by char_id + $yourChars = mysql_select_multi("SELECT `id`, `name`, `balance` FROM `players` WHERE `account_id`='". $user_data['id'] ."';"); + if ($yourChars !== false) { + $charData = array(); + foreach ($yourChars as $char) { + $charData[$char['id']] = $char; + } + ?> +
    + + + +
    + +
    + = $house['points']): ?> +
    +

    Your account has available shop points.

    + + +
    + +

    Your account has available shop points. +
    You don't have enough shop points to instantly buy this house.

    + + + You need a character to bid on this house."; + } else echo "
    You need to login before you can bid on houses."; + } else echo "
    Bid has ended! House transaction will proceed next server restart assuming active bidder have sufficient balance."; + } +} else { + ?> +

    No house selected.

    +

    Go back to the house list and select a house for further details.

    + diff --git a/app/ZnoteAAC/houses.php b/app/ZnoteAAC/houses.php new file mode 100644 index 0000000..378945b --- /dev/null +++ b/app/ZnoteAAC/houses.php @@ -0,0 +1,321 @@ +hasExpired()) { + $tmp = fetchAllHouses_03(); + $cache->setContent($tmp); + $cache->save(); + + foreach ($tmp as $t) { + if ($t['town'] == $townid) $array[] = $t; + } + $array = isset($array) ? $array : false; + } else { + $tmp = $cache->load(); + foreach ($tmp as $t) { + if ($t['town'] == $townid) $array[] = $t; + } + $array = isset($array) ? $array : false; + } + + // Design and present the list + if ($array) { + $guild_support = (isset($array[0]['guild'])) ? true : false; + ?> +

    + house list. +

    +
    +
    + Town list / houses +
    +
    +
    "> + + + +
    +
    +
    + + + + + + + + + + + '; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + if ($value['owner'] == 0) + echo ""; + else { + if ($guild_support && $value['guild'] == 1) { + $guild_name = get_guild_name($value['owner']); + echo ''; + } else { + $data = user_character_data($value['owner'], 'name'); + echo ''; + } + } + echo ''; + } + ?> +
    Name:Size:Doors:Beds:Price:Owner:
    ". $value['name'] ."". $value['size'] ."". $value['doors'] ."". $value['beds'] ."". $value['price'] ."None'. $guild_name .''. $data['name'] .'
    +
    '; + //Token::debug($_POST['token']); + echo 'Please clear your web cache/cookies OR use another web browser
    '; + } +} else { + if (empty($_POST) === true && $config['ServerEngine'] === 'TFS_03') { + ?> +
    +
    + Town list / houses +
    +
    +
    "> + + + +
    +
    +
    + House file not found

    FAILED TO LOCATE/READ FILE AT:
    ". $house['house_file'] ."

    LINUX users: Make sure www-data have read access to file.
    WINDOWS users: Learn to write correct file path.

    "); + exit(); + } + + // Load and cache SQL house data: + $cache = new Cache('engine/cache/houses/sqldata'); + if ($cache->hasExpired()) { + $house_query = mysql_select_multi('SELECT `players`.`name`, `houses`.`id` FROM `players`, `houses` WHERE `houses`.`owner` = `players`.`id`;'); + + $cache->setContent($house_query); + $cache->save(); + } else + $house_query = $cache->load(); + + $sqmPrice = $house['price_sqm']; + $house_load = simplexml_load_file($house['house_file']); + if ($house_query !== false && $house_load !== false) { + ?> +

    House list

    + + + + + + + + + + '. $row['name'] .''; + + foreach ($house_load as $house_fetch){ + $house_price = (int)$house_fetch['size'] * $sqmPrice; + ?> + + + + + + + + +
    HouseLocationOwnerSizeRent
    + + + +
    + Something is wrong with the cache.

    '; + } else if ($config['ServerEngine'] === 'TFS_10') { + // Fetch values + $querystring_id = &$_GET['id']; + $townid = ($querystring_id) ? (int)$_GET['id'] : $config['houseConfig']['HouseListDefaultTown']; + $towns = $config['towns']; + + $order = &$_GET['order']; + $type = &$_GET['type']; + + // Create Search house box + ?> +
    + + + + + + + + + + + + + + +
    TownOrderSort
    + + + + + +
    + +
    +
    + hasExpired()) { + $houses = mysql_select_multi("SELECT `id`, `owner`, `paid`, `warnings`, `name`, `rent`, `town_id`, `size`, `beds`, `bid`, `bid_end`, `last_bid`, `highest_bidder` FROM `houses` ORDER BY {$order} {$type};"); + if ($houses !== false) { + // Fetch player names + $playerlist = array(); + + foreach ($houses as $h) + if ($h['owner'] > 0) + $playerlist[] = $h['owner']; + + if (!empty($playerlist)) { + $ids = join(',', $playerlist); + $tmpPlayers = mysql_select_multi("SELECT `id`, `name` FROM players WHERE `id` IN ($ids);"); + + // Sort $tmpPlayers by player id + $tmpById = array(); + foreach ($tmpPlayers as $p) + $tmpById[$p['id']] = $p['name']; + + for ($i = 0; $i < count($houses); $i++) + if ($houses[$i]['owner'] > 0) + $houses[$i]['ownername'] = $tmpById[$houses[$i]['owner']]; + } + + $cache->setContent($houses); + $cache->save(); + } + } else + $houses = $cache->load(); + + if ($houses !== false || !empty($houses)) { + // Intialize stuff + //data_dump($houses, false, "House data"); + ?> + + + + + + + + + + + + + + + + ". $house['ownername'] .""; + else + echo ($house['highest_bidder'] == 0 ? '' : ''); + ?> + + + +
    NameSizeBedsRentOwnerTown
    ". $house['name'] .""; ?>NoneSelling
    + + Failed to fetch data from sql->houses table.

    Is the table empty?

    "; + } // End TFS 1.0 logic +} +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/index.php b/app/ZnoteAAC/index.php new file mode 100644 index 0000000..dab0d79 --- /dev/null +++ b/app/ZnoteAAC/index.php @@ -0,0 +1,155 @@ +useMemory(false); + $changelogs = $changelogCache->load(); + + if (isset($changelogs) && !empty($changelogs) && $changelogs !== false) { + ?> + + + + + + + + + + +
    Latest Changelog Updates (Click here to see full changelog)
    + hasExpired()) { + $news = fetchAllNews(); + $cache->setContent($news); + $cache->save(); + } else { + $news = $cache->load(); + } + + // Design and present the list + if ($news) { + + $total_news = count($news); + $row_news = $total_news / $config['news_per_page']; + $page_amount = ceil($total_news / $config['news_per_page']); + $current = $config['news_per_page'] * $page; + + function TransformToBBCode($string) { + $tags = array( + '[center]{$1}[/center]' => '
    $1
    ', + '[b]{$1}[/b]' => '$1', + '[size={$1}]{$2}[/size]' => '$2', + '[img]{$1}[/img]' => 'image', + '[link]{$1}[/link]' => '$1', + '[link={$1}]{$2}[/link]' => '$2', + '[color={$1}]{$2}[/color]' => '$2', + '[*]{$1}[/*]' => '
  • $1
  • ', + '[youtube]{$1}[/youtube]' => '
    ', + ); + foreach ($tags as $tag => $value) { + $code = preg_replace('/placeholder([0-9]+)/', '(.*?)', preg_quote(preg_replace('/\{\$([0-9]+)\}/', 'placeholder$1', $tag), '/')); + $string = preg_replace('/'.$code.'/i', $value, $string); + } + return $string; + } + + if ($view !== "") { // We want to view a specific news post + $si = false; + if (ctype_digit($view) === false) { + for ($i = 0; $i < count($news); $i++) if ($view === urlencode($news[$i]['title'])) $si = $i; + } else { + for ($i = 0; $i < count($news); $i++) if ((int)$view === (int)$news[$i]['id']) $si = $i; + } + + if ($si !== false) { + ?> + + + + + + + +
    [#'.$news[$si]['id'].'] '. getClock($news[$si]['date'], true) .' by '. $news[$si]['name'] .' - '. TransformToBBCode($news[$si]['title']) .''; ?>
    +

    +
    + + + + + + + + +
    News post not found.
    +

    We failed to find the post you where looking for.

    +
    + + + + + + + + +
    '.getClock($news[$i]['date'], true).' by '. $news[$i]['name'] .' - '. TransformToBBCode($news[$i]['title']) .''; ?>
    +

    +
    + '; + + for ($i = 0; $i < $page_amount; $i++) { + + if ($i == $page) { + + echo ''; + + } else { + + echo ''; + } + } + + echo ''; + + } + + } else { + echo '

    No news exist.

    '; + } + } +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/ipn.php b/app/ZnoteAAC/ipn.php new file mode 100644 index 0000000..fc6597c --- /dev/null +++ b/app/ZnoteAAC/ipn.php @@ -0,0 +1,171 @@ + $pointsValue) { + if ($priceValue == $payment_amount) { + $paidMoney = $priceValue; + $paidPoints = $pointsValue; + } + } + + if ($paidMoney == 0) $status = false; // Wrong ammount of money + if ($payment_currency != $paypal['currency']) $status = false; // Wrong currency + + // Verify that the user havent messed around with POST data + if ($status) { + // transaction log + mysql_insert("INSERT INTO `znote_paypal` VALUES ('0', '$txn_id', '$payer_email', '$custom', '".$paidMoney."', '".$paidPoints."')"); + + // Process payment + $data = mysql_select_single("SELECT `points` AS `old_points` FROM `znote_accounts` WHERE `account_id`='$custom';"); + + // Give points to user + $new_points = $data['old_points'] + $paidPoints; + mysql_update("UPDATE `znote_accounts` SET `points`='$new_points' WHERE `account_id`='$custom'"); + } + } else { + $pmail = $paypal['email']; + mysql_insert("INSERT INTO `znote_paypal` VALUES ('0', '$txn_id', 'ERROR: Wrong mail. Received: $receiver_email, configured: $pmail', '0', '0', '0')"); + } + } + } +} else { + // Something is wrong + mysql_insert("INSERT INTO `znote_paypal` VALUES ('0', '$txn_id', 'ERROR: Invalid data. $postdata', '0', '0', '0')"); +} +?> diff --git a/app/ZnoteAAC/items.php b/app/ZnoteAAC/items.php new file mode 100644 index 0000000..344b49f --- /dev/null +++ b/app/ZnoteAAC/items.php @@ -0,0 +1,394 @@ +Logged in as admin, loading engine/XML/items.xml file and updating cache.

    "; + // ITEMS XML TO PHP ARRAY + $itemsXML = simplexml_load_file("engine/XML/items.xml"); + if ($itemsXML !== false) { + $types = array(); + $type_attr = array(); + $groups = array(); + + // This empty array will eventually contain all items grouped by type and indexed by item type + $items = array(); + + // Loop through each XML item object + foreach ($itemsXML as $type => $item) { + // Get item types + if (!in_array($type, $types)) { + $types[] = $type; + $type_attr[$type] = array(); + } + // Get item attributes + $attributes = array(); + // Extract attribute values from the XML object and store it in a more manage friendly way $attributes + foreach ($item->attributes() as $aName => $aValue) + $attributes["$aName"] = "$aValue"; + // Remove unececsary attributes + if (isset($attributes['plural'])) unset($attributes['plural']); + //if (isset($attributes['id'])) unset($attributes['id']); + //if (isset($attributes['fromid'])) unset($attributes['fromid']); + //if (isset($attributes['toid'])) unset($attributes['toid']); + if (isset($attributes['editorsuffix'])) unset($attributes['editorsuffix']); + if (isset($attributes['article'])) unset($attributes['article']); + // Populate type attributes + foreach (array_keys($attributes) as $attr) { + if (!in_array($attr, $type_attr[$type])) + $type_attr[$type][] = $attr; + } + + // Loop through every object inside the object + $item_attributes = array(); + $iai = array(); + + foreach ($item as $attribute) { + foreach ($attribute->attributes() as $aName => $aValue) { + if($aName == 'key') { + $attribute_attributes["$aName"] = "$aValue"; + $iai[] = $attribute_attributes[$aName]; + } + } + } + foreach ($item as $attribute) { + foreach ($attribute->attributes() as $aName => $aValue) { + $attribute_attributes["$aName"] = "$aValue"; + if(in_array($attribute_attributes[$aName], $iai)) { + $whatis = $attribute_attributes[$aName]; + } else { + $item_attributes[$whatis] = (isset($attribute_attributes[$aName])) ? $attribute_attributes[$aName] : false; + } + } + } + foreach (array_keys($attributes) as $attr) { + if (!in_array($attr, $type_attr[$type])) + $type_attr[$type][] = $attr; + } + + // Add items with slotType or weaponType (TFS 1.x default) + if(isset($attributes['id'])) $id = (isset($attributes['id'])) ? $attributes['id'] : false; + if(isset($attributes['fromid'])) $id = (isset($attributes['name'])) ? $attributes['name'] : false; + if (isset($item_attributes['slotType']) || isset($item_attributes['weaponType'])) { + $items[$type][$id] = array('attributes' => $item_attributes); + + // Populate item array with potential relevant attributes for the item type + foreach ($type_attr[$type] as $att) + $items[$type][$id][$att] = (isset($attributes[$att])) ? $attributes[$att] : false; + } + + + $save = array($items); + + + } + $itemsCache->setContent($items); + $itemsCache->save(); + } else { + echo "

    Failed to load engine/XML/items.xml file.

    "; + } + } else { + $items = $itemsCache->load(); + ?> +
    + +
    + load(); +} +// End loading items list + +if ($items) { + // Preparing data + $types = array_keys($items); + $itemServer = 'http://'.$config['shop']['imageServer'].'/'; + + //slotType values and names + if(isset($_GET['slot'])) { + switch($_GET['slot']) { + case 'helmet': + $slottype = 'head'; + $slottype_name = 'Helmets'; + break; + case 'sword': + $slottype = 'sword'; + $slottype_name = 'Swords'; + break; + case 'distance': + $slottype = 'distance'; + $slottype_name = 'Distance Weapons'; + break; + case 'wand': + $slottype = 'wand'; + $slottype_name = 'Wands & Rods'; + break; + case 'armor': + $slottype = 'body'; + $slottype_name = 'Armors'; + break; + case 'club': + $slottype = 'club'; + $slottype_name = 'Clubs'; + break; + case 'ammunition': + $slottype = 'ammunition'; + $slottype_name = 'Ammunition'; + break; + case 'book': + $slottype = 'shield'; + $slottype_name = 'Spellbooks'; + break; + case 'legs': + $slottype = 'legs'; + $slottype_name = 'Legs'; + break; + case 'axe': + $slottype = 'axe'; + $slottype_name = 'Axes'; + break; + case 'necklace': + $slottype = 'necklace'; + $slottype_name = 'Amulets & Necklaces'; + break; + case 'feet': + $slottype = 'feet'; + $slottype_name = 'Boots'; + break; + case 'shield': + $slottype = 'shield'; + $slottype_name = 'Shields & Spellbooks'; + break; + case 'backpack': + $slottype = 'backpack'; + $slottype_name = 'Backpacks'; + break; + case 'ring': + $slottype = 'ring'; + $slottype_name = 'Rings'; + break; + default: + $slottype_name = 'null'; + break; + } + } + + // Render HTML + if(isset($_GET['slot']) && ($slottype_name == 'null')) header("Location:items.php"); + ?> + +

    Items

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Helmets
    Swords
    Shields & Spellbooks
    Amulets
    Armors
    Clubs
    Wands & Rods
    Ammunition
    Legs
    Axes
    Rings
    Boots
    Distance
    Backpacks
    + + + + + + + + + + $value) { + if($att == 'slotType' || $att == 'weaponType') $slotType = $value; + if(!empty($slotType) && $slotType == $slottype) $show = true; + else $show = false; + } + } + + if($show == true) { ?> + + + + + + + + + + +
    NameAttributes
    $value) { + + $extra = NULL; + if($value > 0) $extra = '+'; + switch ($array) { + case 'weight': + echo ucwords($array).': '.intval($value/100).'.'.substr($value, -2).' oz
    '; + break; + case 'containerSize': + echo 'Slots: '.$value.'
    '; + break; + case 'armor': + echo ucwords($array).': '.$value.'
    '; + break; + case 'attack': + echo ucwords($array).': '.$value; + if($element != NULL) echo ' ('.$element.')'; + echo '
    '; + break; + case 'defense': + echo ucwords($array).': '.$value; + if($extradef != NULL) echo ' ('.$extradef.')'; + echo '
    '; + break; + case 'skillFist': + echo 'Fist Fighting: '.$extra.$value.'
    '; + break; + case 'skillAxe': + echo 'Axe Fighting: '.$extra.$value.'
    '; + break; + case 'skillSword': + echo 'Sword Fighting: '.$extra.$value.'
    '; + break; + case 'skillClub': + echo 'Club Fighting: '.$extra.$value.'
    '; + break; + case 'skillAxe': + echo 'Axe Fighting: '.$extra.$value.'
    '; + break; + case 'skillDist': + echo 'Distance Fighting: '.$extra.$value.'
    '; + break; + case 'skillShield': + echo 'Shielding: '.$extra.$value.'
    '; + break; + case 'range': + echo ucwords($array).': '.$value.'
    '; + break; + case 'shootType': + echo 'Shoot Type: '.ucwords($value).'
    '; + break; + case 'hitChance': + echo 'Hit: '.$extra.$value.'%
    '; + break; + case 'magiclevelpoints': + echo 'Magic Level: '.$extra.$value.'
    '; + break; + case 'absorbPercentEnergy': + echo 'Energy Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentFire': + echo 'Fire Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentEarth': + echo 'Earth Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentPoison': + echo 'Poison Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentIce': + echo 'Ice Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentHoly': + echo 'Holy Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentDeath': + echo 'Death Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentLifeDrain': + echo 'Life Drain Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentManaDrain': + echo 'Mana Drain Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentDrown': + echo 'Drown Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentPhysical': + echo 'Physical Protection: '.$extra.$value.'%
    '; + break; + case 'absorbPercentIce': + echo 'Ice Protection: '.$extra.$value.'%
    '; + break; + /**case 'suppressDrunk': + echo 'Suppress Drunk: Yes
    '; + break; + case 'suppressEnergy': + echo 'Suppress Energy: Yes
    '; + break; + case 'suppressFire': + echo 'Suppress Fire: Yes
    '; + break; + case 'suppressPoison': + echo 'Suppress Poison: Yes
    '; + break; + case 'suppressDrown': + echo 'Suppress Drown: Yes
    '; + break; + case 'suppressPhysical': + echo 'Suppress Bleeding: Yes
    '; + break; + case 'suppressFreeze': + echo 'Suppress Freeze: Yes
    '; + break; + case 'suppressDazzle': + echo 'Suppress Dazzle: Yes
    '; + break; + case 'suppressCurse': + echo 'Suppress Curse: Yes
    '; + break; + Those are not necessary in my opinion, but if you want to show + **/ + case 'speed': + echo 'Speed: '.$extra.($value/2).'
    '; + break; + case 'charges': + echo 'Charges: '.$value.'
    '; + break; + } + } + ?> +
    + + +

    Items

    +

    Items have currently not been loaded into the website by the server admin.

    + diff --git a/app/ZnoteAAC/killers.php b/app/ZnoteAAC/killers.php new file mode 100644 index 0000000..88c0f17 --- /dev/null +++ b/app/ZnoteAAC/killers.php @@ -0,0 +1,117 @@ +hasExpired()) { + $killers = fetchMurders(); + + $cache->setContent($killers); + $cache->save(); +} else { + $killers = $cache->load(); +} +$cache = new Cache('engine/cache/victims'); +if ($cache->hasExpired()) { + $victims = fetchLoosers(); + + $cache->setContent($victims); + $cache->save(); +} else { + $victims = $cache->load(); +} +$cache = new Cache('engine/cache/lastkillers'); +if ($cache->hasExpired()) { + $latests = mysql_select_multi("SELECT `p`.`name` AS `victim`, `d`.`killed_by` as `killed_by`, `d`.`time` as `time` FROM `player_deaths` as `d` INNER JOIN `players` as `p` ON d.player_id = p.id WHERE d.`is_player`='1' ORDER BY `time` DESC LIMIT 20;"); + if ($latests !== false) { + $cache->setContent($latests); + $cache->save(); + } +} else { + $latests = $cache->load(); +} +if ($killers) { +?> +

    Biggest Murders

    + + + + + + '; + echo ""; + echo ""; + echo ''; + } ?> +
    NameKills
    ". $killer['killed_by'] ."". $killer['kills'] ."
    + +

    Biggest Victims

    + + + + + + '; + echo ""; + echo ""; + echo ''; + } ?> +
    NameDeaths
    ". $victim['name'] ."". $victim['Deaths'] ."
    + +

    Latest kills

    + + + + + + + '; + echo ""; + echo ""; + echo ""; + echo ''; + } ?> +
    KillerTimeVictim
    ". $last['killed_by'] ."". getClock($last['time'], true) ."". $last['victim'] ."
    +hasExpired()) { + $deaths = fetchLatestDeaths_03(30, true); + $cache->setContent($deaths); + $cache->save(); + } else { + $deaths = $cache->load(); + } + + if ($deaths && !empty($deaths)) { + ?> +

    Latest Killers

    + + + + + + + '; + echo ""; + echo ""; + echo ""; + echo ''; + } ?> +
    KillerTimeVictim
    ". $death['killed_by'] ."". getClock($death['time'], true) ."At level ". $death['level'] .": ". $death['victim'] ."
    + diff --git a/app/ZnoteAAC/layout/aside.php b/app/ZnoteAAC/layout/aside.php new file mode 100644 index 0000000..cc07d98 --- /dev/null +++ b/app/ZnoteAAC/layout/aside.php @@ -0,0 +1,72 @@ + +
    + + + +
    + Events +
    +
    + + + + + + +
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    +
    +
    + +
    +
    + Top 10 Players +
    +
    + + + + + + + + + + + + +
    #Name
    1Name
    2Name
    3Name
    4Name
    5Name
    6Name
    7Name
    8Name
    9Name
    10Name
    +
    +
    + */ + ?> + + \ No newline at end of file diff --git a/app/ZnoteAAC/layout/css/resp.css b/app/ZnoteAAC/layout/css/resp.css new file mode 100644 index 0000000..30a6442 --- /dev/null +++ b/app/ZnoteAAC/layout/css/resp.css @@ -0,0 +1,29 @@ +@media screen and (max-width:1300px){ + .main { + width: 1100px; + } + .banner { + height: 214px; + } +} + +@media screen and (max-width:1200px){ + .main { + width: 1000px; + } + .banner { + height: 192px; + } +} + +@media screen and (max-width:1100px){ + .main { + width: 900px; + } + nav .container > div > ul > li > a { + padding: 20px 10px; + } + .banner { + height: 170px; + } +} diff --git a/app/ZnoteAAC/layout/css/style.css b/app/ZnoteAAC/layout/css/style.css new file mode 100644 index 0000000..5732e3a --- /dev/null +++ b/app/ZnoteAAC/layout/css/style.css @@ -0,0 +1,356 @@ +:root { + /* backgrounds */ + --primary: rgb(30,33,40); + --secondary: rgb(25,28,33); + --third: #ddd; + --border: rgb(19,20,23); + + /* text */ + --font-color: rgb(155,162,177); + + /* Links / anchors */ + --anchor:#b39062; + --anchor-hover:#e79424; + + /* buttons or alert boxes with different colors */ + --bg-danger: #3c0e0e; + --color-danger: #9e5858; + --border-danger: #350505; + + --bg-warning: #905c00; + --color-warning: #39280a; + --border-warning: #322001; + + --bg-info: #005d90; + --color-info: #00263e; + --border-info: #022530; + + --bg-success: #009039; + --color-success: #003f0c; + --border-success: #00380c; + + --bg-default: rgb(15,17,20); + --color-default: #968452; + --border-default: #000; +} + + + +body { + font-family: 'arial', sans-serif; + color:var(--font-color); +} + + +body, ul { margin: 0; padding: 0; } +li { list-style: none; } +a { text-decoration: none;color:var(--anchor); } +a:hover {color:var(--anchor-hover);} +a:hover, button:hover, input[type="submit"]:hover { cursor: pointer; } +* {-webkit-transition-duration: 0.2s;-moz-transition-duration: 0.2s;-o-transition-duration: 0.2s;transition-duration: 0.2s;} +*:hover {-webkit-transition-duration: 0s;-moz-transition-duration: 0s;-o-transition-duration: 0s;transition-duration: 0s;} + +nav .container > div > ul > li > ul { + position: absolute; + border: 2px solid; + border-top:none !important; + border-color: var(--border); + width: 200px; +} + +nav { border: 10px solid var(--primary); } +nav .container { padding-left: 10px; } +nav .container > div > ul > li, +.modIcon:hover > i:nth-child(2) { display: inline-block; } + +nav .container > div > ul > li > a { padding: 20px; } +nav .container > div > ul > li:hover > ul, +nav .container > div > ul > li > a, +nav .container > div > ul > li > ul > li > a { display: block; z-index: 1; } +nav .container > div > ul > li > ul > li > a { padding: 10px 20px; } +nav .container, .preventCollapse, .ellipsis { overflow: hidden; } + +.topPane { margin-bottom: 10px; } +.leftPane { width: 70%; } +.rightPane { width: 29%; } +.searchForm { width: 200px; } +.body { padding: 10px 0px; } +table { width: 100%; } +td { padding: 10px 5px; } +.header { color: #d1a233; } +.feedContainer { margin:2rem 0; } +.pull-left { float:left; } +.pull-right { float:right; } +.well, .header { width: auto; } +.centralizeContent { text-align: center; } +.smedia { font-size: 2em; } + +.banner { + background: url("../img/header.png"); + height: 240px; + background-size: 100%; + background-repeat: no-repeat; + margin-top: 30px; + border: 10px solid var(--primary); +} + + + +.searchForm input { + width: 100%; + height: 25px; + border: 1px solid var(--border); + color: var(--font-color); + font-size: 1em; +} + +#countDownTimer { + line-height: 2.3; + padding: 0px 10px; + color: #5390a8; +} + +.modIcon > i:nth-child(2), +.modIcon:hover > i:nth-child(1), +/*.loginContainer, */ +nav .container > div > ul > li > ul { + display: none; +} + +.loginForm { + display: flex; + flex-wrap: wrap; +} +.loginForm button:hover { + background: #0000004d; +} +.loginForm button { + width: 100%; + margin-top: 10px; + background: rgb(15,17,20); + color: var(--font-color); + height: 40px; + border: 1px solid var(--border); + font-size: 1em; +} + +.loginForm input { + width: 100%; + height: 30px; + border: none; + color: #fff; + font-size: 1em; + border-bottom: 1px solid var(--border); +} + +.main { + width: 1220px; + margin: 2rem auto; + position: relative; + z-index: 1; +} + +.ellipsis { + text-overflow: ellipsis; + white-space: nowrap; +} + +.alert-box { + max-width: 500px; + font-size: 14px; + border-radius: 5px; + border: 1px solid var(--third); + margin: 0 auto; + margin-bottom: 15px; + text-align: center; +} + +.alert-default { + background: var(--bg-default); + color: var(--color-default); + border-color: var(--border-default); +} +.alert-info { + background: var(--bg-info); + color: var(--color-info); + border-color: var(--border-info); +} +.alert-success { + background: var(--bg-success); + color: var(--color-success); + border-color: var(--border-success); +} +.alert-warning { + background: var(--bg-warning); + color: var(--color-warning); + border-color: var(--border-warning); +} +.alert-danger { + background: var(--bg-danger); + color: var(--color-danger); + border-color: var(--border-danger); +} +.alert-collapse {display:inline-block;} +.alert-size1 { font-size: 12px; } +.alert-size2 { font-size: 15px; } +.alert-size3 { font-size: 18px; } +.alert-size4 { font-size: 20px; } + + +nav .container > div > ul > li > ul > li > a:hover, +.searchForm input, #countDownTimer, .loginForm input, +nav, footer, table, .header, .feedContainer { + background: var(--primary); +} +.loginForm .well { + width: 100%; +} + +body, nav .container, +nav .container > div > ul > li > ul, +.leftPane, .rightPane, .topPane, +tr:nth-child(2n+1) { + background: var(--secondary); +} + +footer, .feedContainer, +nav .container, .topPane { + border-bottom: 2px solid var(--border) +} + +table, .header, .well, .smedia a { + padding: 10px; +} + +.searchForm input, +.loginForm input, +.alert-box { + padding: 5px; +} + +/* Znote AAC */ +.leftPane img { + max-width: 100%; +} +/* adding button style to select */ +select { + background: rgb(15,17,20); + color: var(--font-color); + height: 40px; + border: 1px solid var(--border); + font-size: 1em; +} +input { + background: rgb(15,17,20); + color: var(--font-color); + height: 40px; + border: 1px solid var(--border); + font-size: 1em; +} +#loginContainer li { + text-align: right; +} + +ul.linkbuttons { + margin-top: 8px; + padding: 0 8px; +} +ul.linkbuttons li { + display: inline-block; + border: 1px solid #d1a233; + width: calc(50% - 10px); + float: left; + margin-bottom: 16px; + text-align: center; +} +ul.linkbuttons li:nth-child(odd) { + margin-right: 16px; +} +ul.linkbuttons li a { + padding: 5px 0; + display: inline-block; + width: 100%; + text-align: center; +} +ul.linkbuttons:after { + content: ''; + display: block; + clear: both; +} +.widget, +.widget .body, +.search_widget, +.search_widget .body { + padding-bottom: 0; +} +.widget center { + margin: auto; +} +.widget h3 { + margin-bottom: 0; +} +.search_widget form { + margin: auto; +} +.search_widget input { + width: 50%; + float: left; +} +.search_widget label { + padding: 9px; + float: left; +} +.search_widget form:after { + display: block; + content: ''; + clear: both; +} +div.relative { + position: relative; +} +.search_widget #name_suggestion { + position: absolute; + width: 280px; + left: -290px; + display: none; +} +.search_widget #name_suggestion.show { + display: block; +} +.search_widget .sname { + text-align: right; +} +.search_widget .sname a { + display: inline-block; + background-color: black; + padding: 10px 20px; + border-bottom: 1px solid rgb(30,33,40); +} +.page_credits .feedContainer .pull-left.leftPane { + box-sizing: border-box; + padding-left: 8px; + padding-right: 8px; +} + +.page_characterprofile #characterProfileTable thead th:first-of-type { + position: relative; + width: 28%; +} +.page_characterprofile #characterProfileTable thead th:last-of-type { + text-align: left; + padding-left: 16px; +} +.page_characterprofile .outfit { + position: absolute; + top: 0; + left: 0; +} +.page_characterprofile .flag { + position: absolute; + top: 16px; + right: 16px; +} +.postHolder iframe { + display: block; + margin: auto; +} diff --git a/app/ZnoteAAC/layout/fontawesome/css/font-awesome.css b/app/ZnoteAAC/layout/fontawesome/css/font-awesome.css new file mode 100644 index 0000000..ee906a8 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/css/font-awesome.css @@ -0,0 +1,2337 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: .3em; +} +.fa.fa-pull-right { + margin-left: .3em; +} +/* Deprecated as of 4.4.0 */ +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: .3em; +} +.fa.pull-right { + margin-left: .3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-feed:before, +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper-pp:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/app/ZnoteAAC/layout/fontawesome/css/font-awesome.min.css b/app/ZnoteAAC/layout/fontawesome/css/font-awesome.min.css new file mode 100644 index 0000000..540440c --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/FontAwesome.otf b/app/ZnoteAAC/layout/fontawesome/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/app/ZnoteAAC/layout/fontawesome/fonts/FontAwesome.otf differ diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.eot b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.eot differ diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.svg b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.ttf b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.ttf differ diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff differ diff --git a/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff2 b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/app/ZnoteAAC/layout/fontawesome/fonts/fontawesome-webfont.woff2 differ diff --git a/app/ZnoteAAC/layout/fontawesome/less/animated.less b/app/ZnoteAAC/layout/fontawesome/less/animated.less new file mode 100644 index 0000000..66ad52a --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/animated.less @@ -0,0 +1,34 @@ +// Animated Icons +// -------------------------- + +.@{fa-css-prefix}-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} + +.@{fa-css-prefix}-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/bordered-pulled.less b/app/ZnoteAAC/layout/fontawesome/less/bordered-pulled.less new file mode 100644 index 0000000..f1c8ad7 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/bordered-pulled.less @@ -0,0 +1,25 @@ +// Bordered & Pulled +// ------------------------- + +.@{fa-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em @fa-border-color; + border-radius: .1em; +} + +.@{fa-css-prefix}-pull-left { float: left; } +.@{fa-css-prefix}-pull-right { float: right; } + +.@{fa-css-prefix} { + &.@{fa-css-prefix}-pull-left { margin-right: .3em; } + &.@{fa-css-prefix}-pull-right { margin-left: .3em; } +} + +/* Deprecated as of 4.4.0 */ +.pull-right { float: right; } +.pull-left { float: left; } + +.@{fa-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/core.less b/app/ZnoteAAC/layout/fontawesome/less/core.less new file mode 100644 index 0000000..c577ac8 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/core.less @@ -0,0 +1,12 @@ +// Base Class Definition +// ------------------------- + +.@{fa-css-prefix} { + display: inline-block; + font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/fixed-width.less b/app/ZnoteAAC/layout/fontawesome/less/fixed-width.less new file mode 100644 index 0000000..110289f --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/fixed-width.less @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.@{fa-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/font-awesome.less b/app/ZnoteAAC/layout/fontawesome/less/font-awesome.less new file mode 100644 index 0000000..c3677de --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/font-awesome.less @@ -0,0 +1,18 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables.less"; +@import "mixins.less"; +@import "path.less"; +@import "core.less"; +@import "larger.less"; +@import "fixed-width.less"; +@import "list.less"; +@import "bordered-pulled.less"; +@import "animated.less"; +@import "rotated-flipped.less"; +@import "stacked.less"; +@import "icons.less"; +@import "screen-reader.less"; diff --git a/app/ZnoteAAC/layout/fontawesome/less/icons.less b/app/ZnoteAAC/layout/fontawesome/less/icons.less new file mode 100644 index 0000000..159d600 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/icons.less @@ -0,0 +1,789 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.@{fa-css-prefix}-glass:before { content: @fa-var-glass; } +.@{fa-css-prefix}-music:before { content: @fa-var-music; } +.@{fa-css-prefix}-search:before { content: @fa-var-search; } +.@{fa-css-prefix}-envelope-o:before { content: @fa-var-envelope-o; } +.@{fa-css-prefix}-heart:before { content: @fa-var-heart; } +.@{fa-css-prefix}-star:before { content: @fa-var-star; } +.@{fa-css-prefix}-star-o:before { content: @fa-var-star-o; } +.@{fa-css-prefix}-user:before { content: @fa-var-user; } +.@{fa-css-prefix}-film:before { content: @fa-var-film; } +.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; } +.@{fa-css-prefix}-th:before { content: @fa-var-th; } +.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; } +.@{fa-css-prefix}-check:before { content: @fa-var-check; } +.@{fa-css-prefix}-remove:before, +.@{fa-css-prefix}-close:before, +.@{fa-css-prefix}-times:before { content: @fa-var-times; } +.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; } +.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; } +.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; } +.@{fa-css-prefix}-signal:before { content: @fa-var-signal; } +.@{fa-css-prefix}-gear:before, +.@{fa-css-prefix}-cog:before { content: @fa-var-cog; } +.@{fa-css-prefix}-trash-o:before { content: @fa-var-trash-o; } +.@{fa-css-prefix}-home:before { content: @fa-var-home; } +.@{fa-css-prefix}-file-o:before { content: @fa-var-file-o; } +.@{fa-css-prefix}-clock-o:before { content: @fa-var-clock-o; } +.@{fa-css-prefix}-road:before { content: @fa-var-road; } +.@{fa-css-prefix}-download:before { content: @fa-var-download; } +.@{fa-css-prefix}-arrow-circle-o-down:before { content: @fa-var-arrow-circle-o-down; } +.@{fa-css-prefix}-arrow-circle-o-up:before { content: @fa-var-arrow-circle-o-up; } +.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; } +.@{fa-css-prefix}-play-circle-o:before { content: @fa-var-play-circle-o; } +.@{fa-css-prefix}-rotate-right:before, +.@{fa-css-prefix}-repeat:before { content: @fa-var-repeat; } +.@{fa-css-prefix}-refresh:before { content: @fa-var-refresh; } +.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; } +.@{fa-css-prefix}-lock:before { content: @fa-var-lock; } +.@{fa-css-prefix}-flag:before { content: @fa-var-flag; } +.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; } +.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; } +.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; } +.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; } +.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; } +.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; } +.@{fa-css-prefix}-tag:before { content: @fa-var-tag; } +.@{fa-css-prefix}-tags:before { content: @fa-var-tags; } +.@{fa-css-prefix}-book:before { content: @fa-var-book; } +.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; } +.@{fa-css-prefix}-print:before { content: @fa-var-print; } +.@{fa-css-prefix}-camera:before { content: @fa-var-camera; } +.@{fa-css-prefix}-font:before { content: @fa-var-font; } +.@{fa-css-prefix}-bold:before { content: @fa-var-bold; } +.@{fa-css-prefix}-italic:before { content: @fa-var-italic; } +.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; } +.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; } +.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; } +.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; } +.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; } +.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; } +.@{fa-css-prefix}-list:before { content: @fa-var-list; } +.@{fa-css-prefix}-dedent:before, +.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; } +.@{fa-css-prefix}-indent:before { content: @fa-var-indent; } +.@{fa-css-prefix}-video-camera:before { content: @fa-var-video-camera; } +.@{fa-css-prefix}-photo:before, +.@{fa-css-prefix}-image:before, +.@{fa-css-prefix}-picture-o:before { content: @fa-var-picture-o; } +.@{fa-css-prefix}-pencil:before { content: @fa-var-pencil; } +.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; } +.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; } +.@{fa-css-prefix}-tint:before { content: @fa-var-tint; } +.@{fa-css-prefix}-edit:before, +.@{fa-css-prefix}-pencil-square-o:before { content: @fa-var-pencil-square-o; } +.@{fa-css-prefix}-share-square-o:before { content: @fa-var-share-square-o; } +.@{fa-css-prefix}-check-square-o:before { content: @fa-var-check-square-o; } +.@{fa-css-prefix}-arrows:before { content: @fa-var-arrows; } +.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; } +.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; } +.@{fa-css-prefix}-backward:before { content: @fa-var-backward; } +.@{fa-css-prefix}-play:before { content: @fa-var-play; } +.@{fa-css-prefix}-pause:before { content: @fa-var-pause; } +.@{fa-css-prefix}-stop:before { content: @fa-var-stop; } +.@{fa-css-prefix}-forward:before { content: @fa-var-forward; } +.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; } +.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; } +.@{fa-css-prefix}-eject:before { content: @fa-var-eject; } +.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; } +.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; } +.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; } +.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; } +.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; } +.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; } +.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; } +.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; } +.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; } +.@{fa-css-prefix}-times-circle-o:before { content: @fa-var-times-circle-o; } +.@{fa-css-prefix}-check-circle-o:before { content: @fa-var-check-circle-o; } +.@{fa-css-prefix}-ban:before { content: @fa-var-ban; } +.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; } +.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; } +.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; } +.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; } +.@{fa-css-prefix}-mail-forward:before, +.@{fa-css-prefix}-share:before { content: @fa-var-share; } +.@{fa-css-prefix}-expand:before { content: @fa-var-expand; } +.@{fa-css-prefix}-compress:before { content: @fa-var-compress; } +.@{fa-css-prefix}-plus:before { content: @fa-var-plus; } +.@{fa-css-prefix}-minus:before { content: @fa-var-minus; } +.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; } +.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; } +.@{fa-css-prefix}-gift:before { content: @fa-var-gift; } +.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; } +.@{fa-css-prefix}-fire:before { content: @fa-var-fire; } +.@{fa-css-prefix}-eye:before { content: @fa-var-eye; } +.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; } +.@{fa-css-prefix}-warning:before, +.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; } +.@{fa-css-prefix}-plane:before { content: @fa-var-plane; } +.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; } +.@{fa-css-prefix}-random:before { content: @fa-var-random; } +.@{fa-css-prefix}-comment:before { content: @fa-var-comment; } +.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; } +.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; } +.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; } +.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; } +.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; } +.@{fa-css-prefix}-folder:before { content: @fa-var-folder; } +.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; } +.@{fa-css-prefix}-arrows-v:before { content: @fa-var-arrows-v; } +.@{fa-css-prefix}-arrows-h:before { content: @fa-var-arrows-h; } +.@{fa-css-prefix}-bar-chart-o:before, +.@{fa-css-prefix}-bar-chart:before { content: @fa-var-bar-chart; } +.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; } +.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; } +.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; } +.@{fa-css-prefix}-key:before { content: @fa-var-key; } +.@{fa-css-prefix}-gears:before, +.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; } +.@{fa-css-prefix}-comments:before { content: @fa-var-comments; } +.@{fa-css-prefix}-thumbs-o-up:before { content: @fa-var-thumbs-o-up; } +.@{fa-css-prefix}-thumbs-o-down:before { content: @fa-var-thumbs-o-down; } +.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; } +.@{fa-css-prefix}-heart-o:before { content: @fa-var-heart-o; } +.@{fa-css-prefix}-sign-out:before { content: @fa-var-sign-out; } +.@{fa-css-prefix}-linkedin-square:before { content: @fa-var-linkedin-square; } +.@{fa-css-prefix}-thumb-tack:before { content: @fa-var-thumb-tack; } +.@{fa-css-prefix}-external-link:before { content: @fa-var-external-link; } +.@{fa-css-prefix}-sign-in:before { content: @fa-var-sign-in; } +.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; } +.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; } +.@{fa-css-prefix}-upload:before { content: @fa-var-upload; } +.@{fa-css-prefix}-lemon-o:before { content: @fa-var-lemon-o; } +.@{fa-css-prefix}-phone:before { content: @fa-var-phone; } +.@{fa-css-prefix}-square-o:before { content: @fa-var-square-o; } +.@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; } +.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; } +.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; } +.@{fa-css-prefix}-facebook-f:before, +.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; } +.@{fa-css-prefix}-github:before { content: @fa-var-github; } +.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; } +.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; } +.@{fa-css-prefix}-feed:before, +.@{fa-css-prefix}-rss:before { content: @fa-var-rss; } +.@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; } +.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; } +.@{fa-css-prefix}-bell:before { content: @fa-var-bell; } +.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; } +.@{fa-css-prefix}-hand-o-right:before { content: @fa-var-hand-o-right; } +.@{fa-css-prefix}-hand-o-left:before { content: @fa-var-hand-o-left; } +.@{fa-css-prefix}-hand-o-up:before { content: @fa-var-hand-o-up; } +.@{fa-css-prefix}-hand-o-down:before { content: @fa-var-hand-o-down; } +.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; } +.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; } +.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; } +.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; } +.@{fa-css-prefix}-globe:before { content: @fa-var-globe; } +.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; } +.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; } +.@{fa-css-prefix}-filter:before { content: @fa-var-filter; } +.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; } +.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; } +.@{fa-css-prefix}-group:before, +.@{fa-css-prefix}-users:before { content: @fa-var-users; } +.@{fa-css-prefix}-chain:before, +.@{fa-css-prefix}-link:before { content: @fa-var-link; } +.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; } +.@{fa-css-prefix}-flask:before { content: @fa-var-flask; } +.@{fa-css-prefix}-cut:before, +.@{fa-css-prefix}-scissors:before { content: @fa-var-scissors; } +.@{fa-css-prefix}-copy:before, +.@{fa-css-prefix}-files-o:before { content: @fa-var-files-o; } +.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; } +.@{fa-css-prefix}-save:before, +.@{fa-css-prefix}-floppy-o:before { content: @fa-var-floppy-o; } +.@{fa-css-prefix}-square:before { content: @fa-var-square; } +.@{fa-css-prefix}-navicon:before, +.@{fa-css-prefix}-reorder:before, +.@{fa-css-prefix}-bars:before { content: @fa-var-bars; } +.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; } +.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; } +.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; } +.@{fa-css-prefix}-underline:before { content: @fa-var-underline; } +.@{fa-css-prefix}-table:before { content: @fa-var-table; } +.@{fa-css-prefix}-magic:before { content: @fa-var-magic; } +.@{fa-css-prefix}-truck:before { content: @fa-var-truck; } +.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; } +.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; } +.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; } +.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; } +.@{fa-css-prefix}-money:before { content: @fa-var-money; } +.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; } +.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; } +.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; } +.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; } +.@{fa-css-prefix}-columns:before { content: @fa-var-columns; } +.@{fa-css-prefix}-unsorted:before, +.@{fa-css-prefix}-sort:before { content: @fa-var-sort; } +.@{fa-css-prefix}-sort-down:before, +.@{fa-css-prefix}-sort-desc:before { content: @fa-var-sort-desc; } +.@{fa-css-prefix}-sort-up:before, +.@{fa-css-prefix}-sort-asc:before { content: @fa-var-sort-asc; } +.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; } +.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; } +.@{fa-css-prefix}-rotate-left:before, +.@{fa-css-prefix}-undo:before { content: @fa-var-undo; } +.@{fa-css-prefix}-legal:before, +.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; } +.@{fa-css-prefix}-dashboard:before, +.@{fa-css-prefix}-tachometer:before { content: @fa-var-tachometer; } +.@{fa-css-prefix}-comment-o:before { content: @fa-var-comment-o; } +.@{fa-css-prefix}-comments-o:before { content: @fa-var-comments-o; } +.@{fa-css-prefix}-flash:before, +.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; } +.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; } +.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; } +.@{fa-css-prefix}-paste:before, +.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; } +.@{fa-css-prefix}-lightbulb-o:before { content: @fa-var-lightbulb-o; } +.@{fa-css-prefix}-exchange:before { content: @fa-var-exchange; } +.@{fa-css-prefix}-cloud-download:before { content: @fa-var-cloud-download; } +.@{fa-css-prefix}-cloud-upload:before { content: @fa-var-cloud-upload; } +.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; } +.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; } +.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; } +.@{fa-css-prefix}-bell-o:before { content: @fa-var-bell-o; } +.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; } +.@{fa-css-prefix}-cutlery:before { content: @fa-var-cutlery; } +.@{fa-css-prefix}-file-text-o:before { content: @fa-var-file-text-o; } +.@{fa-css-prefix}-building-o:before { content: @fa-var-building-o; } +.@{fa-css-prefix}-hospital-o:before { content: @fa-var-hospital-o; } +.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; } +.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; } +.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; } +.@{fa-css-prefix}-beer:before { content: @fa-var-beer; } +.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; } +.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; } +.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; } +.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; } +.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; } +.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; } +.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; } +.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; } +.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; } +.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; } +.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; } +.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; } +.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; } +.@{fa-css-prefix}-mobile-phone:before, +.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; } +.@{fa-css-prefix}-circle-o:before { content: @fa-var-circle-o; } +.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; } +.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; } +.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; } +.@{fa-css-prefix}-circle:before { content: @fa-var-circle; } +.@{fa-css-prefix}-mail-reply:before, +.@{fa-css-prefix}-reply:before { content: @fa-var-reply; } +.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; } +.@{fa-css-prefix}-folder-o:before { content: @fa-var-folder-o; } +.@{fa-css-prefix}-folder-open-o:before { content: @fa-var-folder-open-o; } +.@{fa-css-prefix}-smile-o:before { content: @fa-var-smile-o; } +.@{fa-css-prefix}-frown-o:before { content: @fa-var-frown-o; } +.@{fa-css-prefix}-meh-o:before { content: @fa-var-meh-o; } +.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; } +.@{fa-css-prefix}-keyboard-o:before { content: @fa-var-keyboard-o; } +.@{fa-css-prefix}-flag-o:before { content: @fa-var-flag-o; } +.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; } +.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; } +.@{fa-css-prefix}-code:before { content: @fa-var-code; } +.@{fa-css-prefix}-mail-reply-all:before, +.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; } +.@{fa-css-prefix}-star-half-empty:before, +.@{fa-css-prefix}-star-half-full:before, +.@{fa-css-prefix}-star-half-o:before { content: @fa-var-star-half-o; } +.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; } +.@{fa-css-prefix}-crop:before { content: @fa-var-crop; } +.@{fa-css-prefix}-code-fork:before { content: @fa-var-code-fork; } +.@{fa-css-prefix}-unlink:before, +.@{fa-css-prefix}-chain-broken:before { content: @fa-var-chain-broken; } +.@{fa-css-prefix}-question:before { content: @fa-var-question; } +.@{fa-css-prefix}-info:before { content: @fa-var-info; } +.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; } +.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; } +.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; } +.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; } +.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; } +.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; } +.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; } +.@{fa-css-prefix}-shield:before { content: @fa-var-shield; } +.@{fa-css-prefix}-calendar-o:before { content: @fa-var-calendar-o; } +.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; } +.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; } +.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; } +.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; } +.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; } +.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; } +.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; } +.@{fa-css-prefix}-html5:before { content: @fa-var-html5; } +.@{fa-css-prefix}-css3:before { content: @fa-var-css3; } +.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; } +.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; } +.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; } +.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; } +.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; } +.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; } +.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; } +.@{fa-css-prefix}-ticket:before { content: @fa-var-ticket; } +.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; } +.@{fa-css-prefix}-minus-square-o:before { content: @fa-var-minus-square-o; } +.@{fa-css-prefix}-level-up:before { content: @fa-var-level-up; } +.@{fa-css-prefix}-level-down:before { content: @fa-var-level-down; } +.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; } +.@{fa-css-prefix}-pencil-square:before { content: @fa-var-pencil-square; } +.@{fa-css-prefix}-external-link-square:before { content: @fa-var-external-link-square; } +.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; } +.@{fa-css-prefix}-compass:before { content: @fa-var-compass; } +.@{fa-css-prefix}-toggle-down:before, +.@{fa-css-prefix}-caret-square-o-down:before { content: @fa-var-caret-square-o-down; } +.@{fa-css-prefix}-toggle-up:before, +.@{fa-css-prefix}-caret-square-o-up:before { content: @fa-var-caret-square-o-up; } +.@{fa-css-prefix}-toggle-right:before, +.@{fa-css-prefix}-caret-square-o-right:before { content: @fa-var-caret-square-o-right; } +.@{fa-css-prefix}-euro:before, +.@{fa-css-prefix}-eur:before { content: @fa-var-eur; } +.@{fa-css-prefix}-gbp:before { content: @fa-var-gbp; } +.@{fa-css-prefix}-dollar:before, +.@{fa-css-prefix}-usd:before { content: @fa-var-usd; } +.@{fa-css-prefix}-rupee:before, +.@{fa-css-prefix}-inr:before { content: @fa-var-inr; } +.@{fa-css-prefix}-cny:before, +.@{fa-css-prefix}-rmb:before, +.@{fa-css-prefix}-yen:before, +.@{fa-css-prefix}-jpy:before { content: @fa-var-jpy; } +.@{fa-css-prefix}-ruble:before, +.@{fa-css-prefix}-rouble:before, +.@{fa-css-prefix}-rub:before { content: @fa-var-rub; } +.@{fa-css-prefix}-won:before, +.@{fa-css-prefix}-krw:before { content: @fa-var-krw; } +.@{fa-css-prefix}-bitcoin:before, +.@{fa-css-prefix}-btc:before { content: @fa-var-btc; } +.@{fa-css-prefix}-file:before { content: @fa-var-file; } +.@{fa-css-prefix}-file-text:before { content: @fa-var-file-text; } +.@{fa-css-prefix}-sort-alpha-asc:before { content: @fa-var-sort-alpha-asc; } +.@{fa-css-prefix}-sort-alpha-desc:before { content: @fa-var-sort-alpha-desc; } +.@{fa-css-prefix}-sort-amount-asc:before { content: @fa-var-sort-amount-asc; } +.@{fa-css-prefix}-sort-amount-desc:before { content: @fa-var-sort-amount-desc; } +.@{fa-css-prefix}-sort-numeric-asc:before { content: @fa-var-sort-numeric-asc; } +.@{fa-css-prefix}-sort-numeric-desc:before { content: @fa-var-sort-numeric-desc; } +.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; } +.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; } +.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; } +.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; } +.@{fa-css-prefix}-xing:before { content: @fa-var-xing; } +.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; } +.@{fa-css-prefix}-youtube-play:before { content: @fa-var-youtube-play; } +.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; } +.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; } +.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; } +.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; } +.@{fa-css-prefix}-adn:before { content: @fa-var-adn; } +.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; } +.@{fa-css-prefix}-bitbucket-square:before { content: @fa-var-bitbucket-square; } +.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; } +.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; } +.@{fa-css-prefix}-long-arrow-down:before { content: @fa-var-long-arrow-down; } +.@{fa-css-prefix}-long-arrow-up:before { content: @fa-var-long-arrow-up; } +.@{fa-css-prefix}-long-arrow-left:before { content: @fa-var-long-arrow-left; } +.@{fa-css-prefix}-long-arrow-right:before { content: @fa-var-long-arrow-right; } +.@{fa-css-prefix}-apple:before { content: @fa-var-apple; } +.@{fa-css-prefix}-windows:before { content: @fa-var-windows; } +.@{fa-css-prefix}-android:before { content: @fa-var-android; } +.@{fa-css-prefix}-linux:before { content: @fa-var-linux; } +.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; } +.@{fa-css-prefix}-skype:before { content: @fa-var-skype; } +.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; } +.@{fa-css-prefix}-trello:before { content: @fa-var-trello; } +.@{fa-css-prefix}-female:before { content: @fa-var-female; } +.@{fa-css-prefix}-male:before { content: @fa-var-male; } +.@{fa-css-prefix}-gittip:before, +.@{fa-css-prefix}-gratipay:before { content: @fa-var-gratipay; } +.@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; } +.@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; } +.@{fa-css-prefix}-archive:before { content: @fa-var-archive; } +.@{fa-css-prefix}-bug:before { content: @fa-var-bug; } +.@{fa-css-prefix}-vk:before { content: @fa-var-vk; } +.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; } +.@{fa-css-prefix}-renren:before { content: @fa-var-renren; } +.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; } +.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; } +.@{fa-css-prefix}-arrow-circle-o-right:before { content: @fa-var-arrow-circle-o-right; } +.@{fa-css-prefix}-arrow-circle-o-left:before { content: @fa-var-arrow-circle-o-left; } +.@{fa-css-prefix}-toggle-left:before, +.@{fa-css-prefix}-caret-square-o-left:before { content: @fa-var-caret-square-o-left; } +.@{fa-css-prefix}-dot-circle-o:before { content: @fa-var-dot-circle-o; } +.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; } +.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; } +.@{fa-css-prefix}-turkish-lira:before, +.@{fa-css-prefix}-try:before { content: @fa-var-try; } +.@{fa-css-prefix}-plus-square-o:before { content: @fa-var-plus-square-o; } +.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; } +.@{fa-css-prefix}-slack:before { content: @fa-var-slack; } +.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; } +.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; } +.@{fa-css-prefix}-openid:before { content: @fa-var-openid; } +.@{fa-css-prefix}-institution:before, +.@{fa-css-prefix}-bank:before, +.@{fa-css-prefix}-university:before { content: @fa-var-university; } +.@{fa-css-prefix}-mortar-board:before, +.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; } +.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; } +.@{fa-css-prefix}-google:before { content: @fa-var-google; } +.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; } +.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; } +.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; } +.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; } +.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; } +.@{fa-css-prefix}-digg:before { content: @fa-var-digg; } +.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; } +.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; } +.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; } +.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; } +.@{fa-css-prefix}-language:before { content: @fa-var-language; } +.@{fa-css-prefix}-fax:before { content: @fa-var-fax; } +.@{fa-css-prefix}-building:before { content: @fa-var-building; } +.@{fa-css-prefix}-child:before { content: @fa-var-child; } +.@{fa-css-prefix}-paw:before { content: @fa-var-paw; } +.@{fa-css-prefix}-spoon:before { content: @fa-var-spoon; } +.@{fa-css-prefix}-cube:before { content: @fa-var-cube; } +.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; } +.@{fa-css-prefix}-behance:before { content: @fa-var-behance; } +.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; } +.@{fa-css-prefix}-steam:before { content: @fa-var-steam; } +.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; } +.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; } +.@{fa-css-prefix}-automobile:before, +.@{fa-css-prefix}-car:before { content: @fa-var-car; } +.@{fa-css-prefix}-cab:before, +.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; } +.@{fa-css-prefix}-tree:before { content: @fa-var-tree; } +.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; } +.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; } +.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; } +.@{fa-css-prefix}-database:before { content: @fa-var-database; } +.@{fa-css-prefix}-file-pdf-o:before { content: @fa-var-file-pdf-o; } +.@{fa-css-prefix}-file-word-o:before { content: @fa-var-file-word-o; } +.@{fa-css-prefix}-file-excel-o:before { content: @fa-var-file-excel-o; } +.@{fa-css-prefix}-file-powerpoint-o:before { content: @fa-var-file-powerpoint-o; } +.@{fa-css-prefix}-file-photo-o:before, +.@{fa-css-prefix}-file-picture-o:before, +.@{fa-css-prefix}-file-image-o:before { content: @fa-var-file-image-o; } +.@{fa-css-prefix}-file-zip-o:before, +.@{fa-css-prefix}-file-archive-o:before { content: @fa-var-file-archive-o; } +.@{fa-css-prefix}-file-sound-o:before, +.@{fa-css-prefix}-file-audio-o:before { content: @fa-var-file-audio-o; } +.@{fa-css-prefix}-file-movie-o:before, +.@{fa-css-prefix}-file-video-o:before { content: @fa-var-file-video-o; } +.@{fa-css-prefix}-file-code-o:before { content: @fa-var-file-code-o; } +.@{fa-css-prefix}-vine:before { content: @fa-var-vine; } +.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; } +.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; } +.@{fa-css-prefix}-life-bouy:before, +.@{fa-css-prefix}-life-buoy:before, +.@{fa-css-prefix}-life-saver:before, +.@{fa-css-prefix}-support:before, +.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; } +.@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; } +.@{fa-css-prefix}-ra:before, +.@{fa-css-prefix}-resistance:before, +.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; } +.@{fa-css-prefix}-ge:before, +.@{fa-css-prefix}-empire:before { content: @fa-var-empire; } +.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; } +.@{fa-css-prefix}-git:before { content: @fa-var-git; } +.@{fa-css-prefix}-y-combinator-square:before, +.@{fa-css-prefix}-yc-square:before, +.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; } +.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; } +.@{fa-css-prefix}-qq:before { content: @fa-var-qq; } +.@{fa-css-prefix}-wechat:before, +.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; } +.@{fa-css-prefix}-send:before, +.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; } +.@{fa-css-prefix}-send-o:before, +.@{fa-css-prefix}-paper-plane-o:before { content: @fa-var-paper-plane-o; } +.@{fa-css-prefix}-history:before { content: @fa-var-history; } +.@{fa-css-prefix}-circle-thin:before { content: @fa-var-circle-thin; } +.@{fa-css-prefix}-header:before { content: @fa-var-header; } +.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; } +.@{fa-css-prefix}-sliders:before { content: @fa-var-sliders; } +.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; } +.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; } +.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; } +.@{fa-css-prefix}-soccer-ball-o:before, +.@{fa-css-prefix}-futbol-o:before { content: @fa-var-futbol-o; } +.@{fa-css-prefix}-tty:before { content: @fa-var-tty; } +.@{fa-css-prefix}-binoculars:before { content: @fa-var-binoculars; } +.@{fa-css-prefix}-plug:before { content: @fa-var-plug; } +.@{fa-css-prefix}-slideshare:before { content: @fa-var-slideshare; } +.@{fa-css-prefix}-twitch:before { content: @fa-var-twitch; } +.@{fa-css-prefix}-yelp:before { content: @fa-var-yelp; } +.@{fa-css-prefix}-newspaper-o:before { content: @fa-var-newspaper-o; } +.@{fa-css-prefix}-wifi:before { content: @fa-var-wifi; } +.@{fa-css-prefix}-calculator:before { content: @fa-var-calculator; } +.@{fa-css-prefix}-paypal:before { content: @fa-var-paypal; } +.@{fa-css-prefix}-google-wallet:before { content: @fa-var-google-wallet; } +.@{fa-css-prefix}-cc-visa:before { content: @fa-var-cc-visa; } +.@{fa-css-prefix}-cc-mastercard:before { content: @fa-var-cc-mastercard; } +.@{fa-css-prefix}-cc-discover:before { content: @fa-var-cc-discover; } +.@{fa-css-prefix}-cc-amex:before { content: @fa-var-cc-amex; } +.@{fa-css-prefix}-cc-paypal:before { content: @fa-var-cc-paypal; } +.@{fa-css-prefix}-cc-stripe:before { content: @fa-var-cc-stripe; } +.@{fa-css-prefix}-bell-slash:before { content: @fa-var-bell-slash; } +.@{fa-css-prefix}-bell-slash-o:before { content: @fa-var-bell-slash-o; } +.@{fa-css-prefix}-trash:before { content: @fa-var-trash; } +.@{fa-css-prefix}-copyright:before { content: @fa-var-copyright; } +.@{fa-css-prefix}-at:before { content: @fa-var-at; } +.@{fa-css-prefix}-eyedropper:before { content: @fa-var-eyedropper; } +.@{fa-css-prefix}-paint-brush:before { content: @fa-var-paint-brush; } +.@{fa-css-prefix}-birthday-cake:before { content: @fa-var-birthday-cake; } +.@{fa-css-prefix}-area-chart:before { content: @fa-var-area-chart; } +.@{fa-css-prefix}-pie-chart:before { content: @fa-var-pie-chart; } +.@{fa-css-prefix}-line-chart:before { content: @fa-var-line-chart; } +.@{fa-css-prefix}-lastfm:before { content: @fa-var-lastfm; } +.@{fa-css-prefix}-lastfm-square:before { content: @fa-var-lastfm-square; } +.@{fa-css-prefix}-toggle-off:before { content: @fa-var-toggle-off; } +.@{fa-css-prefix}-toggle-on:before { content: @fa-var-toggle-on; } +.@{fa-css-prefix}-bicycle:before { content: @fa-var-bicycle; } +.@{fa-css-prefix}-bus:before { content: @fa-var-bus; } +.@{fa-css-prefix}-ioxhost:before { content: @fa-var-ioxhost; } +.@{fa-css-prefix}-angellist:before { content: @fa-var-angellist; } +.@{fa-css-prefix}-cc:before { content: @fa-var-cc; } +.@{fa-css-prefix}-shekel:before, +.@{fa-css-prefix}-sheqel:before, +.@{fa-css-prefix}-ils:before { content: @fa-var-ils; } +.@{fa-css-prefix}-meanpath:before { content: @fa-var-meanpath; } +.@{fa-css-prefix}-buysellads:before { content: @fa-var-buysellads; } +.@{fa-css-prefix}-connectdevelop:before { content: @fa-var-connectdevelop; } +.@{fa-css-prefix}-dashcube:before { content: @fa-var-dashcube; } +.@{fa-css-prefix}-forumbee:before { content: @fa-var-forumbee; } +.@{fa-css-prefix}-leanpub:before { content: @fa-var-leanpub; } +.@{fa-css-prefix}-sellsy:before { content: @fa-var-sellsy; } +.@{fa-css-prefix}-shirtsinbulk:before { content: @fa-var-shirtsinbulk; } +.@{fa-css-prefix}-simplybuilt:before { content: @fa-var-simplybuilt; } +.@{fa-css-prefix}-skyatlas:before { content: @fa-var-skyatlas; } +.@{fa-css-prefix}-cart-plus:before { content: @fa-var-cart-plus; } +.@{fa-css-prefix}-cart-arrow-down:before { content: @fa-var-cart-arrow-down; } +.@{fa-css-prefix}-diamond:before { content: @fa-var-diamond; } +.@{fa-css-prefix}-ship:before { content: @fa-var-ship; } +.@{fa-css-prefix}-user-secret:before { content: @fa-var-user-secret; } +.@{fa-css-prefix}-motorcycle:before { content: @fa-var-motorcycle; } +.@{fa-css-prefix}-street-view:before { content: @fa-var-street-view; } +.@{fa-css-prefix}-heartbeat:before { content: @fa-var-heartbeat; } +.@{fa-css-prefix}-venus:before { content: @fa-var-venus; } +.@{fa-css-prefix}-mars:before { content: @fa-var-mars; } +.@{fa-css-prefix}-mercury:before { content: @fa-var-mercury; } +.@{fa-css-prefix}-intersex:before, +.@{fa-css-prefix}-transgender:before { content: @fa-var-transgender; } +.@{fa-css-prefix}-transgender-alt:before { content: @fa-var-transgender-alt; } +.@{fa-css-prefix}-venus-double:before { content: @fa-var-venus-double; } +.@{fa-css-prefix}-mars-double:before { content: @fa-var-mars-double; } +.@{fa-css-prefix}-venus-mars:before { content: @fa-var-venus-mars; } +.@{fa-css-prefix}-mars-stroke:before { content: @fa-var-mars-stroke; } +.@{fa-css-prefix}-mars-stroke-v:before { content: @fa-var-mars-stroke-v; } +.@{fa-css-prefix}-mars-stroke-h:before { content: @fa-var-mars-stroke-h; } +.@{fa-css-prefix}-neuter:before { content: @fa-var-neuter; } +.@{fa-css-prefix}-genderless:before { content: @fa-var-genderless; } +.@{fa-css-prefix}-facebook-official:before { content: @fa-var-facebook-official; } +.@{fa-css-prefix}-pinterest-p:before { content: @fa-var-pinterest-p; } +.@{fa-css-prefix}-whatsapp:before { content: @fa-var-whatsapp; } +.@{fa-css-prefix}-server:before { content: @fa-var-server; } +.@{fa-css-prefix}-user-plus:before { content: @fa-var-user-plus; } +.@{fa-css-prefix}-user-times:before { content: @fa-var-user-times; } +.@{fa-css-prefix}-hotel:before, +.@{fa-css-prefix}-bed:before { content: @fa-var-bed; } +.@{fa-css-prefix}-viacoin:before { content: @fa-var-viacoin; } +.@{fa-css-prefix}-train:before { content: @fa-var-train; } +.@{fa-css-prefix}-subway:before { content: @fa-var-subway; } +.@{fa-css-prefix}-medium:before { content: @fa-var-medium; } +.@{fa-css-prefix}-yc:before, +.@{fa-css-prefix}-y-combinator:before { content: @fa-var-y-combinator; } +.@{fa-css-prefix}-optin-monster:before { content: @fa-var-optin-monster; } +.@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; } +.@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; } +.@{fa-css-prefix}-battery-4:before, +.@{fa-css-prefix}-battery:before, +.@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; } +.@{fa-css-prefix}-battery-3:before, +.@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; } +.@{fa-css-prefix}-battery-2:before, +.@{fa-css-prefix}-battery-half:before { content: @fa-var-battery-half; } +.@{fa-css-prefix}-battery-1:before, +.@{fa-css-prefix}-battery-quarter:before { content: @fa-var-battery-quarter; } +.@{fa-css-prefix}-battery-0:before, +.@{fa-css-prefix}-battery-empty:before { content: @fa-var-battery-empty; } +.@{fa-css-prefix}-mouse-pointer:before { content: @fa-var-mouse-pointer; } +.@{fa-css-prefix}-i-cursor:before { content: @fa-var-i-cursor; } +.@{fa-css-prefix}-object-group:before { content: @fa-var-object-group; } +.@{fa-css-prefix}-object-ungroup:before { content: @fa-var-object-ungroup; } +.@{fa-css-prefix}-sticky-note:before { content: @fa-var-sticky-note; } +.@{fa-css-prefix}-sticky-note-o:before { content: @fa-var-sticky-note-o; } +.@{fa-css-prefix}-cc-jcb:before { content: @fa-var-cc-jcb; } +.@{fa-css-prefix}-cc-diners-club:before { content: @fa-var-cc-diners-club; } +.@{fa-css-prefix}-clone:before { content: @fa-var-clone; } +.@{fa-css-prefix}-balance-scale:before { content: @fa-var-balance-scale; } +.@{fa-css-prefix}-hourglass-o:before { content: @fa-var-hourglass-o; } +.@{fa-css-prefix}-hourglass-1:before, +.@{fa-css-prefix}-hourglass-start:before { content: @fa-var-hourglass-start; } +.@{fa-css-prefix}-hourglass-2:before, +.@{fa-css-prefix}-hourglass-half:before { content: @fa-var-hourglass-half; } +.@{fa-css-prefix}-hourglass-3:before, +.@{fa-css-prefix}-hourglass-end:before { content: @fa-var-hourglass-end; } +.@{fa-css-prefix}-hourglass:before { content: @fa-var-hourglass; } +.@{fa-css-prefix}-hand-grab-o:before, +.@{fa-css-prefix}-hand-rock-o:before { content: @fa-var-hand-rock-o; } +.@{fa-css-prefix}-hand-stop-o:before, +.@{fa-css-prefix}-hand-paper-o:before { content: @fa-var-hand-paper-o; } +.@{fa-css-prefix}-hand-scissors-o:before { content: @fa-var-hand-scissors-o; } +.@{fa-css-prefix}-hand-lizard-o:before { content: @fa-var-hand-lizard-o; } +.@{fa-css-prefix}-hand-spock-o:before { content: @fa-var-hand-spock-o; } +.@{fa-css-prefix}-hand-pointer-o:before { content: @fa-var-hand-pointer-o; } +.@{fa-css-prefix}-hand-peace-o:before { content: @fa-var-hand-peace-o; } +.@{fa-css-prefix}-trademark:before { content: @fa-var-trademark; } +.@{fa-css-prefix}-registered:before { content: @fa-var-registered; } +.@{fa-css-prefix}-creative-commons:before { content: @fa-var-creative-commons; } +.@{fa-css-prefix}-gg:before { content: @fa-var-gg; } +.@{fa-css-prefix}-gg-circle:before { content: @fa-var-gg-circle; } +.@{fa-css-prefix}-tripadvisor:before { content: @fa-var-tripadvisor; } +.@{fa-css-prefix}-odnoklassniki:before { content: @fa-var-odnoklassniki; } +.@{fa-css-prefix}-odnoklassniki-square:before { content: @fa-var-odnoklassniki-square; } +.@{fa-css-prefix}-get-pocket:before { content: @fa-var-get-pocket; } +.@{fa-css-prefix}-wikipedia-w:before { content: @fa-var-wikipedia-w; } +.@{fa-css-prefix}-safari:before { content: @fa-var-safari; } +.@{fa-css-prefix}-chrome:before { content: @fa-var-chrome; } +.@{fa-css-prefix}-firefox:before { content: @fa-var-firefox; } +.@{fa-css-prefix}-opera:before { content: @fa-var-opera; } +.@{fa-css-prefix}-internet-explorer:before { content: @fa-var-internet-explorer; } +.@{fa-css-prefix}-tv:before, +.@{fa-css-prefix}-television:before { content: @fa-var-television; } +.@{fa-css-prefix}-contao:before { content: @fa-var-contao; } +.@{fa-css-prefix}-500px:before { content: @fa-var-500px; } +.@{fa-css-prefix}-amazon:before { content: @fa-var-amazon; } +.@{fa-css-prefix}-calendar-plus-o:before { content: @fa-var-calendar-plus-o; } +.@{fa-css-prefix}-calendar-minus-o:before { content: @fa-var-calendar-minus-o; } +.@{fa-css-prefix}-calendar-times-o:before { content: @fa-var-calendar-times-o; } +.@{fa-css-prefix}-calendar-check-o:before { content: @fa-var-calendar-check-o; } +.@{fa-css-prefix}-industry:before { content: @fa-var-industry; } +.@{fa-css-prefix}-map-pin:before { content: @fa-var-map-pin; } +.@{fa-css-prefix}-map-signs:before { content: @fa-var-map-signs; } +.@{fa-css-prefix}-map-o:before { content: @fa-var-map-o; } +.@{fa-css-prefix}-map:before { content: @fa-var-map; } +.@{fa-css-prefix}-commenting:before { content: @fa-var-commenting; } +.@{fa-css-prefix}-commenting-o:before { content: @fa-var-commenting-o; } +.@{fa-css-prefix}-houzz:before { content: @fa-var-houzz; } +.@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; } +.@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; } +.@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; } +.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; } +.@{fa-css-prefix}-edge:before { content: @fa-var-edge; } +.@{fa-css-prefix}-credit-card-alt:before { content: @fa-var-credit-card-alt; } +.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; } +.@{fa-css-prefix}-modx:before { content: @fa-var-modx; } +.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; } +.@{fa-css-prefix}-usb:before { content: @fa-var-usb; } +.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; } +.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; } +.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; } +.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; } +.@{fa-css-prefix}-pause-circle-o:before { content: @fa-var-pause-circle-o; } +.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; } +.@{fa-css-prefix}-stop-circle-o:before { content: @fa-var-stop-circle-o; } +.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; } +.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; } +.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; } +.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; } +.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; } +.@{fa-css-prefix}-percent:before { content: @fa-var-percent; } +.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; } +.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; } +.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; } +.@{fa-css-prefix}-envira:before { content: @fa-var-envira; } +.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; } +.@{fa-css-prefix}-wheelchair-alt:before { content: @fa-var-wheelchair-alt; } +.@{fa-css-prefix}-question-circle-o:before { content: @fa-var-question-circle-o; } +.@{fa-css-prefix}-blind:before { content: @fa-var-blind; } +.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; } +.@{fa-css-prefix}-volume-control-phone:before { content: @fa-var-volume-control-phone; } +.@{fa-css-prefix}-braille:before { content: @fa-var-braille; } +.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; } +.@{fa-css-prefix}-asl-interpreting:before, +.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; } +.@{fa-css-prefix}-deafness:before, +.@{fa-css-prefix}-hard-of-hearing:before, +.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; } +.@{fa-css-prefix}-glide:before { content: @fa-var-glide; } +.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; } +.@{fa-css-prefix}-signing:before, +.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; } +.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; } +.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; } +.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; } +.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; } +.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; } +.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; } +.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } +.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; } +.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; } +.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; } +.@{fa-css-prefix}-google-plus-circle:before, +.@{fa-css-prefix}-google-plus-official:before { content: @fa-var-google-plus-official; } +.@{fa-css-prefix}-fa:before, +.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; } +.@{fa-css-prefix}-handshake-o:before { content: @fa-var-handshake-o; } +.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; } +.@{fa-css-prefix}-envelope-open-o:before { content: @fa-var-envelope-open-o; } +.@{fa-css-prefix}-linode:before { content: @fa-var-linode; } +.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; } +.@{fa-css-prefix}-address-book-o:before { content: @fa-var-address-book-o; } +.@{fa-css-prefix}-vcard:before, +.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; } +.@{fa-css-prefix}-vcard-o:before, +.@{fa-css-prefix}-address-card-o:before { content: @fa-var-address-card-o; } +.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; } +.@{fa-css-prefix}-user-circle-o:before { content: @fa-var-user-circle-o; } +.@{fa-css-prefix}-user-o:before { content: @fa-var-user-o; } +.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; } +.@{fa-css-prefix}-drivers-license:before, +.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; } +.@{fa-css-prefix}-drivers-license-o:before, +.@{fa-css-prefix}-id-card-o:before { content: @fa-var-id-card-o; } +.@{fa-css-prefix}-quora:before { content: @fa-var-quora; } +.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; } +.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; } +.@{fa-css-prefix}-thermometer-4:before, +.@{fa-css-prefix}-thermometer:before, +.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; } +.@{fa-css-prefix}-thermometer-3:before, +.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; } +.@{fa-css-prefix}-thermometer-2:before, +.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; } +.@{fa-css-prefix}-thermometer-1:before, +.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; } +.@{fa-css-prefix}-thermometer-0:before, +.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; } +.@{fa-css-prefix}-shower:before { content: @fa-var-shower; } +.@{fa-css-prefix}-bathtub:before, +.@{fa-css-prefix}-s15:before, +.@{fa-css-prefix}-bath:before { content: @fa-var-bath; } +.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; } +.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; } +.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; } +.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; } +.@{fa-css-prefix}-times-rectangle:before, +.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; } +.@{fa-css-prefix}-times-rectangle-o:before, +.@{fa-css-prefix}-window-close-o:before { content: @fa-var-window-close-o; } +.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; } +.@{fa-css-prefix}-grav:before { content: @fa-var-grav; } +.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; } +.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; } +.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; } +.@{fa-css-prefix}-eercast:before { content: @fa-var-eercast; } +.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; } +.@{fa-css-prefix}-snowflake-o:before { content: @fa-var-snowflake-o; } +.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; } +.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; } +.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; } diff --git a/app/ZnoteAAC/layout/fontawesome/less/larger.less b/app/ZnoteAAC/layout/fontawesome/less/larger.less new file mode 100644 index 0000000..c9d6467 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/larger.less @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.@{fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.@{fa-css-prefix}-2x { font-size: 2em; } +.@{fa-css-prefix}-3x { font-size: 3em; } +.@{fa-css-prefix}-4x { font-size: 4em; } +.@{fa-css-prefix}-5x { font-size: 5em; } diff --git a/app/ZnoteAAC/layout/fontawesome/less/list.less b/app/ZnoteAAC/layout/fontawesome/less/list.less new file mode 100644 index 0000000..0b44038 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/list.less @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.@{fa-css-prefix}-ul { + padding-left: 0; + margin-left: @fa-li-width; + list-style-type: none; + > li { position: relative; } +} +.@{fa-css-prefix}-li { + position: absolute; + left: -@fa-li-width; + width: @fa-li-width; + top: (2em / 14); + text-align: center; + &.@{fa-css-prefix}-lg { + left: (-@fa-li-width + (4em / 14)); + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/mixins.less b/app/ZnoteAAC/layout/fontawesome/less/mixins.less new file mode 100644 index 0000000..beef231 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/mixins.less @@ -0,0 +1,60 @@ +// Mixins +// -------------------------- + +.fa-icon() { + display: inline-block; + font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} + +.fa-icon-rotate(@degrees, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + transform: rotate(@degrees); +} + +.fa-icon-flip(@horiz, @vert, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; + -webkit-transform: scale(@horiz, @vert); + -ms-transform: scale(@horiz, @vert); + transform: scale(@horiz, @vert); +} + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +.sr-only-focusable() { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/path.less b/app/ZnoteAAC/layout/fontawesome/less/path.less new file mode 100644 index 0000000..835be41 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/path.less @@ -0,0 +1,15 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'FontAwesome'; + src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); + src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), + url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), + url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), + url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), + url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); + // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + font-weight: normal; + font-style: normal; +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/rotated-flipped.less b/app/ZnoteAAC/layout/fontawesome/less/rotated-flipped.less new file mode 100644 index 0000000..f6ba814 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/rotated-flipped.less @@ -0,0 +1,20 @@ +// Rotated & Flipped Icons +// ------------------------- + +.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } +.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } +.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } + +.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } +.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root .@{fa-css-prefix}-rotate-90, +:root .@{fa-css-prefix}-rotate-180, +:root .@{fa-css-prefix}-rotate-270, +:root .@{fa-css-prefix}-flip-horizontal, +:root .@{fa-css-prefix}-flip-vertical { + filter: none; +} diff --git a/app/ZnoteAAC/layout/fontawesome/less/screen-reader.less b/app/ZnoteAAC/layout/fontawesome/less/screen-reader.less new file mode 100644 index 0000000..11c1881 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/screen-reader.less @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { .sr-only(); } +.sr-only-focusable { .sr-only-focusable(); } diff --git a/app/ZnoteAAC/layout/fontawesome/less/stacked.less b/app/ZnoteAAC/layout/fontawesome/less/stacked.less new file mode 100644 index 0000000..fc53fb0 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/stacked.less @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.@{fa-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.@{fa-css-prefix}-stack-1x { line-height: inherit; } +.@{fa-css-prefix}-stack-2x { font-size: 2em; } +.@{fa-css-prefix}-inverse { color: @fa-inverse; } diff --git a/app/ZnoteAAC/layout/fontawesome/less/variables.less b/app/ZnoteAAC/layout/fontawesome/less/variables.less new file mode 100644 index 0000000..7ddbbc0 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/less/variables.less @@ -0,0 +1,800 @@ +// Variables +// -------------------------- + +@fa-font-path: "../fonts"; +@fa-font-size-base: 14px; +@fa-line-height-base: 1; +//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts"; // for referencing Bootstrap CDN font files directly +@fa-css-prefix: fa; +@fa-version: "4.7.0"; +@fa-border-color: #eee; +@fa-inverse: #fff; +@fa-li-width: (30em / 14); + +@fa-var-500px: "\f26e"; +@fa-var-address-book: "\f2b9"; +@fa-var-address-book-o: "\f2ba"; +@fa-var-address-card: "\f2bb"; +@fa-var-address-card-o: "\f2bc"; +@fa-var-adjust: "\f042"; +@fa-var-adn: "\f170"; +@fa-var-align-center: "\f037"; +@fa-var-align-justify: "\f039"; +@fa-var-align-left: "\f036"; +@fa-var-align-right: "\f038"; +@fa-var-amazon: "\f270"; +@fa-var-ambulance: "\f0f9"; +@fa-var-american-sign-language-interpreting: "\f2a3"; +@fa-var-anchor: "\f13d"; +@fa-var-android: "\f17b"; +@fa-var-angellist: "\f209"; +@fa-var-angle-double-down: "\f103"; +@fa-var-angle-double-left: "\f100"; +@fa-var-angle-double-right: "\f101"; +@fa-var-angle-double-up: "\f102"; +@fa-var-angle-down: "\f107"; +@fa-var-angle-left: "\f104"; +@fa-var-angle-right: "\f105"; +@fa-var-angle-up: "\f106"; +@fa-var-apple: "\f179"; +@fa-var-archive: "\f187"; +@fa-var-area-chart: "\f1fe"; +@fa-var-arrow-circle-down: "\f0ab"; +@fa-var-arrow-circle-left: "\f0a8"; +@fa-var-arrow-circle-o-down: "\f01a"; +@fa-var-arrow-circle-o-left: "\f190"; +@fa-var-arrow-circle-o-right: "\f18e"; +@fa-var-arrow-circle-o-up: "\f01b"; +@fa-var-arrow-circle-right: "\f0a9"; +@fa-var-arrow-circle-up: "\f0aa"; +@fa-var-arrow-down: "\f063"; +@fa-var-arrow-left: "\f060"; +@fa-var-arrow-right: "\f061"; +@fa-var-arrow-up: "\f062"; +@fa-var-arrows: "\f047"; +@fa-var-arrows-alt: "\f0b2"; +@fa-var-arrows-h: "\f07e"; +@fa-var-arrows-v: "\f07d"; +@fa-var-asl-interpreting: "\f2a3"; +@fa-var-assistive-listening-systems: "\f2a2"; +@fa-var-asterisk: "\f069"; +@fa-var-at: "\f1fa"; +@fa-var-audio-description: "\f29e"; +@fa-var-automobile: "\f1b9"; +@fa-var-backward: "\f04a"; +@fa-var-balance-scale: "\f24e"; +@fa-var-ban: "\f05e"; +@fa-var-bandcamp: "\f2d5"; +@fa-var-bank: "\f19c"; +@fa-var-bar-chart: "\f080"; +@fa-var-bar-chart-o: "\f080"; +@fa-var-barcode: "\f02a"; +@fa-var-bars: "\f0c9"; +@fa-var-bath: "\f2cd"; +@fa-var-bathtub: "\f2cd"; +@fa-var-battery: "\f240"; +@fa-var-battery-0: "\f244"; +@fa-var-battery-1: "\f243"; +@fa-var-battery-2: "\f242"; +@fa-var-battery-3: "\f241"; +@fa-var-battery-4: "\f240"; +@fa-var-battery-empty: "\f244"; +@fa-var-battery-full: "\f240"; +@fa-var-battery-half: "\f242"; +@fa-var-battery-quarter: "\f243"; +@fa-var-battery-three-quarters: "\f241"; +@fa-var-bed: "\f236"; +@fa-var-beer: "\f0fc"; +@fa-var-behance: "\f1b4"; +@fa-var-behance-square: "\f1b5"; +@fa-var-bell: "\f0f3"; +@fa-var-bell-o: "\f0a2"; +@fa-var-bell-slash: "\f1f6"; +@fa-var-bell-slash-o: "\f1f7"; +@fa-var-bicycle: "\f206"; +@fa-var-binoculars: "\f1e5"; +@fa-var-birthday-cake: "\f1fd"; +@fa-var-bitbucket: "\f171"; +@fa-var-bitbucket-square: "\f172"; +@fa-var-bitcoin: "\f15a"; +@fa-var-black-tie: "\f27e"; +@fa-var-blind: "\f29d"; +@fa-var-bluetooth: "\f293"; +@fa-var-bluetooth-b: "\f294"; +@fa-var-bold: "\f032"; +@fa-var-bolt: "\f0e7"; +@fa-var-bomb: "\f1e2"; +@fa-var-book: "\f02d"; +@fa-var-bookmark: "\f02e"; +@fa-var-bookmark-o: "\f097"; +@fa-var-braille: "\f2a1"; +@fa-var-briefcase: "\f0b1"; +@fa-var-btc: "\f15a"; +@fa-var-bug: "\f188"; +@fa-var-building: "\f1ad"; +@fa-var-building-o: "\f0f7"; +@fa-var-bullhorn: "\f0a1"; +@fa-var-bullseye: "\f140"; +@fa-var-bus: "\f207"; +@fa-var-buysellads: "\f20d"; +@fa-var-cab: "\f1ba"; +@fa-var-calculator: "\f1ec"; +@fa-var-calendar: "\f073"; +@fa-var-calendar-check-o: "\f274"; +@fa-var-calendar-minus-o: "\f272"; +@fa-var-calendar-o: "\f133"; +@fa-var-calendar-plus-o: "\f271"; +@fa-var-calendar-times-o: "\f273"; +@fa-var-camera: "\f030"; +@fa-var-camera-retro: "\f083"; +@fa-var-car: "\f1b9"; +@fa-var-caret-down: "\f0d7"; +@fa-var-caret-left: "\f0d9"; +@fa-var-caret-right: "\f0da"; +@fa-var-caret-square-o-down: "\f150"; +@fa-var-caret-square-o-left: "\f191"; +@fa-var-caret-square-o-right: "\f152"; +@fa-var-caret-square-o-up: "\f151"; +@fa-var-caret-up: "\f0d8"; +@fa-var-cart-arrow-down: "\f218"; +@fa-var-cart-plus: "\f217"; +@fa-var-cc: "\f20a"; +@fa-var-cc-amex: "\f1f3"; +@fa-var-cc-diners-club: "\f24c"; +@fa-var-cc-discover: "\f1f2"; +@fa-var-cc-jcb: "\f24b"; +@fa-var-cc-mastercard: "\f1f1"; +@fa-var-cc-paypal: "\f1f4"; +@fa-var-cc-stripe: "\f1f5"; +@fa-var-cc-visa: "\f1f0"; +@fa-var-certificate: "\f0a3"; +@fa-var-chain: "\f0c1"; +@fa-var-chain-broken: "\f127"; +@fa-var-check: "\f00c"; +@fa-var-check-circle: "\f058"; +@fa-var-check-circle-o: "\f05d"; +@fa-var-check-square: "\f14a"; +@fa-var-check-square-o: "\f046"; +@fa-var-chevron-circle-down: "\f13a"; +@fa-var-chevron-circle-left: "\f137"; +@fa-var-chevron-circle-right: "\f138"; +@fa-var-chevron-circle-up: "\f139"; +@fa-var-chevron-down: "\f078"; +@fa-var-chevron-left: "\f053"; +@fa-var-chevron-right: "\f054"; +@fa-var-chevron-up: "\f077"; +@fa-var-child: "\f1ae"; +@fa-var-chrome: "\f268"; +@fa-var-circle: "\f111"; +@fa-var-circle-o: "\f10c"; +@fa-var-circle-o-notch: "\f1ce"; +@fa-var-circle-thin: "\f1db"; +@fa-var-clipboard: "\f0ea"; +@fa-var-clock-o: "\f017"; +@fa-var-clone: "\f24d"; +@fa-var-close: "\f00d"; +@fa-var-cloud: "\f0c2"; +@fa-var-cloud-download: "\f0ed"; +@fa-var-cloud-upload: "\f0ee"; +@fa-var-cny: "\f157"; +@fa-var-code: "\f121"; +@fa-var-code-fork: "\f126"; +@fa-var-codepen: "\f1cb"; +@fa-var-codiepie: "\f284"; +@fa-var-coffee: "\f0f4"; +@fa-var-cog: "\f013"; +@fa-var-cogs: "\f085"; +@fa-var-columns: "\f0db"; +@fa-var-comment: "\f075"; +@fa-var-comment-o: "\f0e5"; +@fa-var-commenting: "\f27a"; +@fa-var-commenting-o: "\f27b"; +@fa-var-comments: "\f086"; +@fa-var-comments-o: "\f0e6"; +@fa-var-compass: "\f14e"; +@fa-var-compress: "\f066"; +@fa-var-connectdevelop: "\f20e"; +@fa-var-contao: "\f26d"; +@fa-var-copy: "\f0c5"; +@fa-var-copyright: "\f1f9"; +@fa-var-creative-commons: "\f25e"; +@fa-var-credit-card: "\f09d"; +@fa-var-credit-card-alt: "\f283"; +@fa-var-crop: "\f125"; +@fa-var-crosshairs: "\f05b"; +@fa-var-css3: "\f13c"; +@fa-var-cube: "\f1b2"; +@fa-var-cubes: "\f1b3"; +@fa-var-cut: "\f0c4"; +@fa-var-cutlery: "\f0f5"; +@fa-var-dashboard: "\f0e4"; +@fa-var-dashcube: "\f210"; +@fa-var-database: "\f1c0"; +@fa-var-deaf: "\f2a4"; +@fa-var-deafness: "\f2a4"; +@fa-var-dedent: "\f03b"; +@fa-var-delicious: "\f1a5"; +@fa-var-desktop: "\f108"; +@fa-var-deviantart: "\f1bd"; +@fa-var-diamond: "\f219"; +@fa-var-digg: "\f1a6"; +@fa-var-dollar: "\f155"; +@fa-var-dot-circle-o: "\f192"; +@fa-var-download: "\f019"; +@fa-var-dribbble: "\f17d"; +@fa-var-drivers-license: "\f2c2"; +@fa-var-drivers-license-o: "\f2c3"; +@fa-var-dropbox: "\f16b"; +@fa-var-drupal: "\f1a9"; +@fa-var-edge: "\f282"; +@fa-var-edit: "\f044"; +@fa-var-eercast: "\f2da"; +@fa-var-eject: "\f052"; +@fa-var-ellipsis-h: "\f141"; +@fa-var-ellipsis-v: "\f142"; +@fa-var-empire: "\f1d1"; +@fa-var-envelope: "\f0e0"; +@fa-var-envelope-o: "\f003"; +@fa-var-envelope-open: "\f2b6"; +@fa-var-envelope-open-o: "\f2b7"; +@fa-var-envelope-square: "\f199"; +@fa-var-envira: "\f299"; +@fa-var-eraser: "\f12d"; +@fa-var-etsy: "\f2d7"; +@fa-var-eur: "\f153"; +@fa-var-euro: "\f153"; +@fa-var-exchange: "\f0ec"; +@fa-var-exclamation: "\f12a"; +@fa-var-exclamation-circle: "\f06a"; +@fa-var-exclamation-triangle: "\f071"; +@fa-var-expand: "\f065"; +@fa-var-expeditedssl: "\f23e"; +@fa-var-external-link: "\f08e"; +@fa-var-external-link-square: "\f14c"; +@fa-var-eye: "\f06e"; +@fa-var-eye-slash: "\f070"; +@fa-var-eyedropper: "\f1fb"; +@fa-var-fa: "\f2b4"; +@fa-var-facebook: "\f09a"; +@fa-var-facebook-f: "\f09a"; +@fa-var-facebook-official: "\f230"; +@fa-var-facebook-square: "\f082"; +@fa-var-fast-backward: "\f049"; +@fa-var-fast-forward: "\f050"; +@fa-var-fax: "\f1ac"; +@fa-var-feed: "\f09e"; +@fa-var-female: "\f182"; +@fa-var-fighter-jet: "\f0fb"; +@fa-var-file: "\f15b"; +@fa-var-file-archive-o: "\f1c6"; +@fa-var-file-audio-o: "\f1c7"; +@fa-var-file-code-o: "\f1c9"; +@fa-var-file-excel-o: "\f1c3"; +@fa-var-file-image-o: "\f1c5"; +@fa-var-file-movie-o: "\f1c8"; +@fa-var-file-o: "\f016"; +@fa-var-file-pdf-o: "\f1c1"; +@fa-var-file-photo-o: "\f1c5"; +@fa-var-file-picture-o: "\f1c5"; +@fa-var-file-powerpoint-o: "\f1c4"; +@fa-var-file-sound-o: "\f1c7"; +@fa-var-file-text: "\f15c"; +@fa-var-file-text-o: "\f0f6"; +@fa-var-file-video-o: "\f1c8"; +@fa-var-file-word-o: "\f1c2"; +@fa-var-file-zip-o: "\f1c6"; +@fa-var-files-o: "\f0c5"; +@fa-var-film: "\f008"; +@fa-var-filter: "\f0b0"; +@fa-var-fire: "\f06d"; +@fa-var-fire-extinguisher: "\f134"; +@fa-var-firefox: "\f269"; +@fa-var-first-order: "\f2b0"; +@fa-var-flag: "\f024"; +@fa-var-flag-checkered: "\f11e"; +@fa-var-flag-o: "\f11d"; +@fa-var-flash: "\f0e7"; +@fa-var-flask: "\f0c3"; +@fa-var-flickr: "\f16e"; +@fa-var-floppy-o: "\f0c7"; +@fa-var-folder: "\f07b"; +@fa-var-folder-o: "\f114"; +@fa-var-folder-open: "\f07c"; +@fa-var-folder-open-o: "\f115"; +@fa-var-font: "\f031"; +@fa-var-font-awesome: "\f2b4"; +@fa-var-fonticons: "\f280"; +@fa-var-fort-awesome: "\f286"; +@fa-var-forumbee: "\f211"; +@fa-var-forward: "\f04e"; +@fa-var-foursquare: "\f180"; +@fa-var-free-code-camp: "\f2c5"; +@fa-var-frown-o: "\f119"; +@fa-var-futbol-o: "\f1e3"; +@fa-var-gamepad: "\f11b"; +@fa-var-gavel: "\f0e3"; +@fa-var-gbp: "\f154"; +@fa-var-ge: "\f1d1"; +@fa-var-gear: "\f013"; +@fa-var-gears: "\f085"; +@fa-var-genderless: "\f22d"; +@fa-var-get-pocket: "\f265"; +@fa-var-gg: "\f260"; +@fa-var-gg-circle: "\f261"; +@fa-var-gift: "\f06b"; +@fa-var-git: "\f1d3"; +@fa-var-git-square: "\f1d2"; +@fa-var-github: "\f09b"; +@fa-var-github-alt: "\f113"; +@fa-var-github-square: "\f092"; +@fa-var-gitlab: "\f296"; +@fa-var-gittip: "\f184"; +@fa-var-glass: "\f000"; +@fa-var-glide: "\f2a5"; +@fa-var-glide-g: "\f2a6"; +@fa-var-globe: "\f0ac"; +@fa-var-google: "\f1a0"; +@fa-var-google-plus: "\f0d5"; +@fa-var-google-plus-circle: "\f2b3"; +@fa-var-google-plus-official: "\f2b3"; +@fa-var-google-plus-square: "\f0d4"; +@fa-var-google-wallet: "\f1ee"; +@fa-var-graduation-cap: "\f19d"; +@fa-var-gratipay: "\f184"; +@fa-var-grav: "\f2d6"; +@fa-var-group: "\f0c0"; +@fa-var-h-square: "\f0fd"; +@fa-var-hacker-news: "\f1d4"; +@fa-var-hand-grab-o: "\f255"; +@fa-var-hand-lizard-o: "\f258"; +@fa-var-hand-o-down: "\f0a7"; +@fa-var-hand-o-left: "\f0a5"; +@fa-var-hand-o-right: "\f0a4"; +@fa-var-hand-o-up: "\f0a6"; +@fa-var-hand-paper-o: "\f256"; +@fa-var-hand-peace-o: "\f25b"; +@fa-var-hand-pointer-o: "\f25a"; +@fa-var-hand-rock-o: "\f255"; +@fa-var-hand-scissors-o: "\f257"; +@fa-var-hand-spock-o: "\f259"; +@fa-var-hand-stop-o: "\f256"; +@fa-var-handshake-o: "\f2b5"; +@fa-var-hard-of-hearing: "\f2a4"; +@fa-var-hashtag: "\f292"; +@fa-var-hdd-o: "\f0a0"; +@fa-var-header: "\f1dc"; +@fa-var-headphones: "\f025"; +@fa-var-heart: "\f004"; +@fa-var-heart-o: "\f08a"; +@fa-var-heartbeat: "\f21e"; +@fa-var-history: "\f1da"; +@fa-var-home: "\f015"; +@fa-var-hospital-o: "\f0f8"; +@fa-var-hotel: "\f236"; +@fa-var-hourglass: "\f254"; +@fa-var-hourglass-1: "\f251"; +@fa-var-hourglass-2: "\f252"; +@fa-var-hourglass-3: "\f253"; +@fa-var-hourglass-end: "\f253"; +@fa-var-hourglass-half: "\f252"; +@fa-var-hourglass-o: "\f250"; +@fa-var-hourglass-start: "\f251"; +@fa-var-houzz: "\f27c"; +@fa-var-html5: "\f13b"; +@fa-var-i-cursor: "\f246"; +@fa-var-id-badge: "\f2c1"; +@fa-var-id-card: "\f2c2"; +@fa-var-id-card-o: "\f2c3"; +@fa-var-ils: "\f20b"; +@fa-var-image: "\f03e"; +@fa-var-imdb: "\f2d8"; +@fa-var-inbox: "\f01c"; +@fa-var-indent: "\f03c"; +@fa-var-industry: "\f275"; +@fa-var-info: "\f129"; +@fa-var-info-circle: "\f05a"; +@fa-var-inr: "\f156"; +@fa-var-instagram: "\f16d"; +@fa-var-institution: "\f19c"; +@fa-var-internet-explorer: "\f26b"; +@fa-var-intersex: "\f224"; +@fa-var-ioxhost: "\f208"; +@fa-var-italic: "\f033"; +@fa-var-joomla: "\f1aa"; +@fa-var-jpy: "\f157"; +@fa-var-jsfiddle: "\f1cc"; +@fa-var-key: "\f084"; +@fa-var-keyboard-o: "\f11c"; +@fa-var-krw: "\f159"; +@fa-var-language: "\f1ab"; +@fa-var-laptop: "\f109"; +@fa-var-lastfm: "\f202"; +@fa-var-lastfm-square: "\f203"; +@fa-var-leaf: "\f06c"; +@fa-var-leanpub: "\f212"; +@fa-var-legal: "\f0e3"; +@fa-var-lemon-o: "\f094"; +@fa-var-level-down: "\f149"; +@fa-var-level-up: "\f148"; +@fa-var-life-bouy: "\f1cd"; +@fa-var-life-buoy: "\f1cd"; +@fa-var-life-ring: "\f1cd"; +@fa-var-life-saver: "\f1cd"; +@fa-var-lightbulb-o: "\f0eb"; +@fa-var-line-chart: "\f201"; +@fa-var-link: "\f0c1"; +@fa-var-linkedin: "\f0e1"; +@fa-var-linkedin-square: "\f08c"; +@fa-var-linode: "\f2b8"; +@fa-var-linux: "\f17c"; +@fa-var-list: "\f03a"; +@fa-var-list-alt: "\f022"; +@fa-var-list-ol: "\f0cb"; +@fa-var-list-ul: "\f0ca"; +@fa-var-location-arrow: "\f124"; +@fa-var-lock: "\f023"; +@fa-var-long-arrow-down: "\f175"; +@fa-var-long-arrow-left: "\f177"; +@fa-var-long-arrow-right: "\f178"; +@fa-var-long-arrow-up: "\f176"; +@fa-var-low-vision: "\f2a8"; +@fa-var-magic: "\f0d0"; +@fa-var-magnet: "\f076"; +@fa-var-mail-forward: "\f064"; +@fa-var-mail-reply: "\f112"; +@fa-var-mail-reply-all: "\f122"; +@fa-var-male: "\f183"; +@fa-var-map: "\f279"; +@fa-var-map-marker: "\f041"; +@fa-var-map-o: "\f278"; +@fa-var-map-pin: "\f276"; +@fa-var-map-signs: "\f277"; +@fa-var-mars: "\f222"; +@fa-var-mars-double: "\f227"; +@fa-var-mars-stroke: "\f229"; +@fa-var-mars-stroke-h: "\f22b"; +@fa-var-mars-stroke-v: "\f22a"; +@fa-var-maxcdn: "\f136"; +@fa-var-meanpath: "\f20c"; +@fa-var-medium: "\f23a"; +@fa-var-medkit: "\f0fa"; +@fa-var-meetup: "\f2e0"; +@fa-var-meh-o: "\f11a"; +@fa-var-mercury: "\f223"; +@fa-var-microchip: "\f2db"; +@fa-var-microphone: "\f130"; +@fa-var-microphone-slash: "\f131"; +@fa-var-minus: "\f068"; +@fa-var-minus-circle: "\f056"; +@fa-var-minus-square: "\f146"; +@fa-var-minus-square-o: "\f147"; +@fa-var-mixcloud: "\f289"; +@fa-var-mobile: "\f10b"; +@fa-var-mobile-phone: "\f10b"; +@fa-var-modx: "\f285"; +@fa-var-money: "\f0d6"; +@fa-var-moon-o: "\f186"; +@fa-var-mortar-board: "\f19d"; +@fa-var-motorcycle: "\f21c"; +@fa-var-mouse-pointer: "\f245"; +@fa-var-music: "\f001"; +@fa-var-navicon: "\f0c9"; +@fa-var-neuter: "\f22c"; +@fa-var-newspaper-o: "\f1ea"; +@fa-var-object-group: "\f247"; +@fa-var-object-ungroup: "\f248"; +@fa-var-odnoklassniki: "\f263"; +@fa-var-odnoklassniki-square: "\f264"; +@fa-var-opencart: "\f23d"; +@fa-var-openid: "\f19b"; +@fa-var-opera: "\f26a"; +@fa-var-optin-monster: "\f23c"; +@fa-var-outdent: "\f03b"; +@fa-var-pagelines: "\f18c"; +@fa-var-paint-brush: "\f1fc"; +@fa-var-paper-plane: "\f1d8"; +@fa-var-paper-plane-o: "\f1d9"; +@fa-var-paperclip: "\f0c6"; +@fa-var-paragraph: "\f1dd"; +@fa-var-paste: "\f0ea"; +@fa-var-pause: "\f04c"; +@fa-var-pause-circle: "\f28b"; +@fa-var-pause-circle-o: "\f28c"; +@fa-var-paw: "\f1b0"; +@fa-var-paypal: "\f1ed"; +@fa-var-pencil: "\f040"; +@fa-var-pencil-square: "\f14b"; +@fa-var-pencil-square-o: "\f044"; +@fa-var-percent: "\f295"; +@fa-var-phone: "\f095"; +@fa-var-phone-square: "\f098"; +@fa-var-photo: "\f03e"; +@fa-var-picture-o: "\f03e"; +@fa-var-pie-chart: "\f200"; +@fa-var-pied-piper: "\f2ae"; +@fa-var-pied-piper-alt: "\f1a8"; +@fa-var-pied-piper-pp: "\f1a7"; +@fa-var-pinterest: "\f0d2"; +@fa-var-pinterest-p: "\f231"; +@fa-var-pinterest-square: "\f0d3"; +@fa-var-plane: "\f072"; +@fa-var-play: "\f04b"; +@fa-var-play-circle: "\f144"; +@fa-var-play-circle-o: "\f01d"; +@fa-var-plug: "\f1e6"; +@fa-var-plus: "\f067"; +@fa-var-plus-circle: "\f055"; +@fa-var-plus-square: "\f0fe"; +@fa-var-plus-square-o: "\f196"; +@fa-var-podcast: "\f2ce"; +@fa-var-power-off: "\f011"; +@fa-var-print: "\f02f"; +@fa-var-product-hunt: "\f288"; +@fa-var-puzzle-piece: "\f12e"; +@fa-var-qq: "\f1d6"; +@fa-var-qrcode: "\f029"; +@fa-var-question: "\f128"; +@fa-var-question-circle: "\f059"; +@fa-var-question-circle-o: "\f29c"; +@fa-var-quora: "\f2c4"; +@fa-var-quote-left: "\f10d"; +@fa-var-quote-right: "\f10e"; +@fa-var-ra: "\f1d0"; +@fa-var-random: "\f074"; +@fa-var-ravelry: "\f2d9"; +@fa-var-rebel: "\f1d0"; +@fa-var-recycle: "\f1b8"; +@fa-var-reddit: "\f1a1"; +@fa-var-reddit-alien: "\f281"; +@fa-var-reddit-square: "\f1a2"; +@fa-var-refresh: "\f021"; +@fa-var-registered: "\f25d"; +@fa-var-remove: "\f00d"; +@fa-var-renren: "\f18b"; +@fa-var-reorder: "\f0c9"; +@fa-var-repeat: "\f01e"; +@fa-var-reply: "\f112"; +@fa-var-reply-all: "\f122"; +@fa-var-resistance: "\f1d0"; +@fa-var-retweet: "\f079"; +@fa-var-rmb: "\f157"; +@fa-var-road: "\f018"; +@fa-var-rocket: "\f135"; +@fa-var-rotate-left: "\f0e2"; +@fa-var-rotate-right: "\f01e"; +@fa-var-rouble: "\f158"; +@fa-var-rss: "\f09e"; +@fa-var-rss-square: "\f143"; +@fa-var-rub: "\f158"; +@fa-var-ruble: "\f158"; +@fa-var-rupee: "\f156"; +@fa-var-s15: "\f2cd"; +@fa-var-safari: "\f267"; +@fa-var-save: "\f0c7"; +@fa-var-scissors: "\f0c4"; +@fa-var-scribd: "\f28a"; +@fa-var-search: "\f002"; +@fa-var-search-minus: "\f010"; +@fa-var-search-plus: "\f00e"; +@fa-var-sellsy: "\f213"; +@fa-var-send: "\f1d8"; +@fa-var-send-o: "\f1d9"; +@fa-var-server: "\f233"; +@fa-var-share: "\f064"; +@fa-var-share-alt: "\f1e0"; +@fa-var-share-alt-square: "\f1e1"; +@fa-var-share-square: "\f14d"; +@fa-var-share-square-o: "\f045"; +@fa-var-shekel: "\f20b"; +@fa-var-sheqel: "\f20b"; +@fa-var-shield: "\f132"; +@fa-var-ship: "\f21a"; +@fa-var-shirtsinbulk: "\f214"; +@fa-var-shopping-bag: "\f290"; +@fa-var-shopping-basket: "\f291"; +@fa-var-shopping-cart: "\f07a"; +@fa-var-shower: "\f2cc"; +@fa-var-sign-in: "\f090"; +@fa-var-sign-language: "\f2a7"; +@fa-var-sign-out: "\f08b"; +@fa-var-signal: "\f012"; +@fa-var-signing: "\f2a7"; +@fa-var-simplybuilt: "\f215"; +@fa-var-sitemap: "\f0e8"; +@fa-var-skyatlas: "\f216"; +@fa-var-skype: "\f17e"; +@fa-var-slack: "\f198"; +@fa-var-sliders: "\f1de"; +@fa-var-slideshare: "\f1e7"; +@fa-var-smile-o: "\f118"; +@fa-var-snapchat: "\f2ab"; +@fa-var-snapchat-ghost: "\f2ac"; +@fa-var-snapchat-square: "\f2ad"; +@fa-var-snowflake-o: "\f2dc"; +@fa-var-soccer-ball-o: "\f1e3"; +@fa-var-sort: "\f0dc"; +@fa-var-sort-alpha-asc: "\f15d"; +@fa-var-sort-alpha-desc: "\f15e"; +@fa-var-sort-amount-asc: "\f160"; +@fa-var-sort-amount-desc: "\f161"; +@fa-var-sort-asc: "\f0de"; +@fa-var-sort-desc: "\f0dd"; +@fa-var-sort-down: "\f0dd"; +@fa-var-sort-numeric-asc: "\f162"; +@fa-var-sort-numeric-desc: "\f163"; +@fa-var-sort-up: "\f0de"; +@fa-var-soundcloud: "\f1be"; +@fa-var-space-shuttle: "\f197"; +@fa-var-spinner: "\f110"; +@fa-var-spoon: "\f1b1"; +@fa-var-spotify: "\f1bc"; +@fa-var-square: "\f0c8"; +@fa-var-square-o: "\f096"; +@fa-var-stack-exchange: "\f18d"; +@fa-var-stack-overflow: "\f16c"; +@fa-var-star: "\f005"; +@fa-var-star-half: "\f089"; +@fa-var-star-half-empty: "\f123"; +@fa-var-star-half-full: "\f123"; +@fa-var-star-half-o: "\f123"; +@fa-var-star-o: "\f006"; +@fa-var-steam: "\f1b6"; +@fa-var-steam-square: "\f1b7"; +@fa-var-step-backward: "\f048"; +@fa-var-step-forward: "\f051"; +@fa-var-stethoscope: "\f0f1"; +@fa-var-sticky-note: "\f249"; +@fa-var-sticky-note-o: "\f24a"; +@fa-var-stop: "\f04d"; +@fa-var-stop-circle: "\f28d"; +@fa-var-stop-circle-o: "\f28e"; +@fa-var-street-view: "\f21d"; +@fa-var-strikethrough: "\f0cc"; +@fa-var-stumbleupon: "\f1a4"; +@fa-var-stumbleupon-circle: "\f1a3"; +@fa-var-subscript: "\f12c"; +@fa-var-subway: "\f239"; +@fa-var-suitcase: "\f0f2"; +@fa-var-sun-o: "\f185"; +@fa-var-superpowers: "\f2dd"; +@fa-var-superscript: "\f12b"; +@fa-var-support: "\f1cd"; +@fa-var-table: "\f0ce"; +@fa-var-tablet: "\f10a"; +@fa-var-tachometer: "\f0e4"; +@fa-var-tag: "\f02b"; +@fa-var-tags: "\f02c"; +@fa-var-tasks: "\f0ae"; +@fa-var-taxi: "\f1ba"; +@fa-var-telegram: "\f2c6"; +@fa-var-television: "\f26c"; +@fa-var-tencent-weibo: "\f1d5"; +@fa-var-terminal: "\f120"; +@fa-var-text-height: "\f034"; +@fa-var-text-width: "\f035"; +@fa-var-th: "\f00a"; +@fa-var-th-large: "\f009"; +@fa-var-th-list: "\f00b"; +@fa-var-themeisle: "\f2b2"; +@fa-var-thermometer: "\f2c7"; +@fa-var-thermometer-0: "\f2cb"; +@fa-var-thermometer-1: "\f2ca"; +@fa-var-thermometer-2: "\f2c9"; +@fa-var-thermometer-3: "\f2c8"; +@fa-var-thermometer-4: "\f2c7"; +@fa-var-thermometer-empty: "\f2cb"; +@fa-var-thermometer-full: "\f2c7"; +@fa-var-thermometer-half: "\f2c9"; +@fa-var-thermometer-quarter: "\f2ca"; +@fa-var-thermometer-three-quarters: "\f2c8"; +@fa-var-thumb-tack: "\f08d"; +@fa-var-thumbs-down: "\f165"; +@fa-var-thumbs-o-down: "\f088"; +@fa-var-thumbs-o-up: "\f087"; +@fa-var-thumbs-up: "\f164"; +@fa-var-ticket: "\f145"; +@fa-var-times: "\f00d"; +@fa-var-times-circle: "\f057"; +@fa-var-times-circle-o: "\f05c"; +@fa-var-times-rectangle: "\f2d3"; +@fa-var-times-rectangle-o: "\f2d4"; +@fa-var-tint: "\f043"; +@fa-var-toggle-down: "\f150"; +@fa-var-toggle-left: "\f191"; +@fa-var-toggle-off: "\f204"; +@fa-var-toggle-on: "\f205"; +@fa-var-toggle-right: "\f152"; +@fa-var-toggle-up: "\f151"; +@fa-var-trademark: "\f25c"; +@fa-var-train: "\f238"; +@fa-var-transgender: "\f224"; +@fa-var-transgender-alt: "\f225"; +@fa-var-trash: "\f1f8"; +@fa-var-trash-o: "\f014"; +@fa-var-tree: "\f1bb"; +@fa-var-trello: "\f181"; +@fa-var-tripadvisor: "\f262"; +@fa-var-trophy: "\f091"; +@fa-var-truck: "\f0d1"; +@fa-var-try: "\f195"; +@fa-var-tty: "\f1e4"; +@fa-var-tumblr: "\f173"; +@fa-var-tumblr-square: "\f174"; +@fa-var-turkish-lira: "\f195"; +@fa-var-tv: "\f26c"; +@fa-var-twitch: "\f1e8"; +@fa-var-twitter: "\f099"; +@fa-var-twitter-square: "\f081"; +@fa-var-umbrella: "\f0e9"; +@fa-var-underline: "\f0cd"; +@fa-var-undo: "\f0e2"; +@fa-var-universal-access: "\f29a"; +@fa-var-university: "\f19c"; +@fa-var-unlink: "\f127"; +@fa-var-unlock: "\f09c"; +@fa-var-unlock-alt: "\f13e"; +@fa-var-unsorted: "\f0dc"; +@fa-var-upload: "\f093"; +@fa-var-usb: "\f287"; +@fa-var-usd: "\f155"; +@fa-var-user: "\f007"; +@fa-var-user-circle: "\f2bd"; +@fa-var-user-circle-o: "\f2be"; +@fa-var-user-md: "\f0f0"; +@fa-var-user-o: "\f2c0"; +@fa-var-user-plus: "\f234"; +@fa-var-user-secret: "\f21b"; +@fa-var-user-times: "\f235"; +@fa-var-users: "\f0c0"; +@fa-var-vcard: "\f2bb"; +@fa-var-vcard-o: "\f2bc"; +@fa-var-venus: "\f221"; +@fa-var-venus-double: "\f226"; +@fa-var-venus-mars: "\f228"; +@fa-var-viacoin: "\f237"; +@fa-var-viadeo: "\f2a9"; +@fa-var-viadeo-square: "\f2aa"; +@fa-var-video-camera: "\f03d"; +@fa-var-vimeo: "\f27d"; +@fa-var-vimeo-square: "\f194"; +@fa-var-vine: "\f1ca"; +@fa-var-vk: "\f189"; +@fa-var-volume-control-phone: "\f2a0"; +@fa-var-volume-down: "\f027"; +@fa-var-volume-off: "\f026"; +@fa-var-volume-up: "\f028"; +@fa-var-warning: "\f071"; +@fa-var-wechat: "\f1d7"; +@fa-var-weibo: "\f18a"; +@fa-var-weixin: "\f1d7"; +@fa-var-whatsapp: "\f232"; +@fa-var-wheelchair: "\f193"; +@fa-var-wheelchair-alt: "\f29b"; +@fa-var-wifi: "\f1eb"; +@fa-var-wikipedia-w: "\f266"; +@fa-var-window-close: "\f2d3"; +@fa-var-window-close-o: "\f2d4"; +@fa-var-window-maximize: "\f2d0"; +@fa-var-window-minimize: "\f2d1"; +@fa-var-window-restore: "\f2d2"; +@fa-var-windows: "\f17a"; +@fa-var-won: "\f159"; +@fa-var-wordpress: "\f19a"; +@fa-var-wpbeginner: "\f297"; +@fa-var-wpexplorer: "\f2de"; +@fa-var-wpforms: "\f298"; +@fa-var-wrench: "\f0ad"; +@fa-var-xing: "\f168"; +@fa-var-xing-square: "\f169"; +@fa-var-y-combinator: "\f23b"; +@fa-var-y-combinator-square: "\f1d4"; +@fa-var-yahoo: "\f19e"; +@fa-var-yc: "\f23b"; +@fa-var-yc-square: "\f1d4"; +@fa-var-yelp: "\f1e9"; +@fa-var-yen: "\f157"; +@fa-var-yoast: "\f2b1"; +@fa-var-youtube: "\f167"; +@fa-var-youtube-play: "\f16a"; +@fa-var-youtube-square: "\f166"; + diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_animated.scss b/app/ZnoteAAC/layout/fontawesome/scss/_animated.scss new file mode 100644 index 0000000..8a020db --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_animated.scss @@ -0,0 +1,34 @@ +// Spinning Icons +// -------------------------- + +.#{$fa-css-prefix}-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} + +.#{$fa-css-prefix}-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_bordered-pulled.scss b/app/ZnoteAAC/layout/fontawesome/scss/_bordered-pulled.scss new file mode 100644 index 0000000..d4b85a0 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_bordered-pulled.scss @@ -0,0 +1,25 @@ +// Bordered & Pulled +// ------------------------- + +.#{$fa-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em $fa-border-color; + border-radius: .1em; +} + +.#{$fa-css-prefix}-pull-left { float: left; } +.#{$fa-css-prefix}-pull-right { float: right; } + +.#{$fa-css-prefix} { + &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } + &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } +} + +/* Deprecated as of 4.4.0 */ +.pull-right { float: right; } +.pull-left { float: left; } + +.#{$fa-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_core.scss b/app/ZnoteAAC/layout/fontawesome/scss/_core.scss new file mode 100644 index 0000000..7425ef8 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_core.scss @@ -0,0 +1,12 @@ +// Base Class Definition +// ------------------------- + +.#{$fa-css-prefix} { + display: inline-block; + font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_fixed-width.scss b/app/ZnoteAAC/layout/fontawesome/scss/_fixed-width.scss new file mode 100644 index 0000000..b221c98 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_fixed-width.scss @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.#{$fa-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_icons.scss b/app/ZnoteAAC/layout/fontawesome/scss/_icons.scss new file mode 100644 index 0000000..e63e702 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_icons.scss @@ -0,0 +1,789 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.#{$fa-css-prefix}-glass:before { content: $fa-var-glass; } +.#{$fa-css-prefix}-music:before { content: $fa-var-music; } +.#{$fa-css-prefix}-search:before { content: $fa-var-search; } +.#{$fa-css-prefix}-envelope-o:before { content: $fa-var-envelope-o; } +.#{$fa-css-prefix}-heart:before { content: $fa-var-heart; } +.#{$fa-css-prefix}-star:before { content: $fa-var-star; } +.#{$fa-css-prefix}-star-o:before { content: $fa-var-star-o; } +.#{$fa-css-prefix}-user:before { content: $fa-var-user; } +.#{$fa-css-prefix}-film:before { content: $fa-var-film; } +.#{$fa-css-prefix}-th-large:before { content: $fa-var-th-large; } +.#{$fa-css-prefix}-th:before { content: $fa-var-th; } +.#{$fa-css-prefix}-th-list:before { content: $fa-var-th-list; } +.#{$fa-css-prefix}-check:before { content: $fa-var-check; } +.#{$fa-css-prefix}-remove:before, +.#{$fa-css-prefix}-close:before, +.#{$fa-css-prefix}-times:before { content: $fa-var-times; } +.#{$fa-css-prefix}-search-plus:before { content: $fa-var-search-plus; } +.#{$fa-css-prefix}-search-minus:before { content: $fa-var-search-minus; } +.#{$fa-css-prefix}-power-off:before { content: $fa-var-power-off; } +.#{$fa-css-prefix}-signal:before { content: $fa-var-signal; } +.#{$fa-css-prefix}-gear:before, +.#{$fa-css-prefix}-cog:before { content: $fa-var-cog; } +.#{$fa-css-prefix}-trash-o:before { content: $fa-var-trash-o; } +.#{$fa-css-prefix}-home:before { content: $fa-var-home; } +.#{$fa-css-prefix}-file-o:before { content: $fa-var-file-o; } +.#{$fa-css-prefix}-clock-o:before { content: $fa-var-clock-o; } +.#{$fa-css-prefix}-road:before { content: $fa-var-road; } +.#{$fa-css-prefix}-download:before { content: $fa-var-download; } +.#{$fa-css-prefix}-arrow-circle-o-down:before { content: $fa-var-arrow-circle-o-down; } +.#{$fa-css-prefix}-arrow-circle-o-up:before { content: $fa-var-arrow-circle-o-up; } +.#{$fa-css-prefix}-inbox:before { content: $fa-var-inbox; } +.#{$fa-css-prefix}-play-circle-o:before { content: $fa-var-play-circle-o; } +.#{$fa-css-prefix}-rotate-right:before, +.#{$fa-css-prefix}-repeat:before { content: $fa-var-repeat; } +.#{$fa-css-prefix}-refresh:before { content: $fa-var-refresh; } +.#{$fa-css-prefix}-list-alt:before { content: $fa-var-list-alt; } +.#{$fa-css-prefix}-lock:before { content: $fa-var-lock; } +.#{$fa-css-prefix}-flag:before { content: $fa-var-flag; } +.#{$fa-css-prefix}-headphones:before { content: $fa-var-headphones; } +.#{$fa-css-prefix}-volume-off:before { content: $fa-var-volume-off; } +.#{$fa-css-prefix}-volume-down:before { content: $fa-var-volume-down; } +.#{$fa-css-prefix}-volume-up:before { content: $fa-var-volume-up; } +.#{$fa-css-prefix}-qrcode:before { content: $fa-var-qrcode; } +.#{$fa-css-prefix}-barcode:before { content: $fa-var-barcode; } +.#{$fa-css-prefix}-tag:before { content: $fa-var-tag; } +.#{$fa-css-prefix}-tags:before { content: $fa-var-tags; } +.#{$fa-css-prefix}-book:before { content: $fa-var-book; } +.#{$fa-css-prefix}-bookmark:before { content: $fa-var-bookmark; } +.#{$fa-css-prefix}-print:before { content: $fa-var-print; } +.#{$fa-css-prefix}-camera:before { content: $fa-var-camera; } +.#{$fa-css-prefix}-font:before { content: $fa-var-font; } +.#{$fa-css-prefix}-bold:before { content: $fa-var-bold; } +.#{$fa-css-prefix}-italic:before { content: $fa-var-italic; } +.#{$fa-css-prefix}-text-height:before { content: $fa-var-text-height; } +.#{$fa-css-prefix}-text-width:before { content: $fa-var-text-width; } +.#{$fa-css-prefix}-align-left:before { content: $fa-var-align-left; } +.#{$fa-css-prefix}-align-center:before { content: $fa-var-align-center; } +.#{$fa-css-prefix}-align-right:before { content: $fa-var-align-right; } +.#{$fa-css-prefix}-align-justify:before { content: $fa-var-align-justify; } +.#{$fa-css-prefix}-list:before { content: $fa-var-list; } +.#{$fa-css-prefix}-dedent:before, +.#{$fa-css-prefix}-outdent:before { content: $fa-var-outdent; } +.#{$fa-css-prefix}-indent:before { content: $fa-var-indent; } +.#{$fa-css-prefix}-video-camera:before { content: $fa-var-video-camera; } +.#{$fa-css-prefix}-photo:before, +.#{$fa-css-prefix}-image:before, +.#{$fa-css-prefix}-picture-o:before { content: $fa-var-picture-o; } +.#{$fa-css-prefix}-pencil:before { content: $fa-var-pencil; } +.#{$fa-css-prefix}-map-marker:before { content: $fa-var-map-marker; } +.#{$fa-css-prefix}-adjust:before { content: $fa-var-adjust; } +.#{$fa-css-prefix}-tint:before { content: $fa-var-tint; } +.#{$fa-css-prefix}-edit:before, +.#{$fa-css-prefix}-pencil-square-o:before { content: $fa-var-pencil-square-o; } +.#{$fa-css-prefix}-share-square-o:before { content: $fa-var-share-square-o; } +.#{$fa-css-prefix}-check-square-o:before { content: $fa-var-check-square-o; } +.#{$fa-css-prefix}-arrows:before { content: $fa-var-arrows; } +.#{$fa-css-prefix}-step-backward:before { content: $fa-var-step-backward; } +.#{$fa-css-prefix}-fast-backward:before { content: $fa-var-fast-backward; } +.#{$fa-css-prefix}-backward:before { content: $fa-var-backward; } +.#{$fa-css-prefix}-play:before { content: $fa-var-play; } +.#{$fa-css-prefix}-pause:before { content: $fa-var-pause; } +.#{$fa-css-prefix}-stop:before { content: $fa-var-stop; } +.#{$fa-css-prefix}-forward:before { content: $fa-var-forward; } +.#{$fa-css-prefix}-fast-forward:before { content: $fa-var-fast-forward; } +.#{$fa-css-prefix}-step-forward:before { content: $fa-var-step-forward; } +.#{$fa-css-prefix}-eject:before { content: $fa-var-eject; } +.#{$fa-css-prefix}-chevron-left:before { content: $fa-var-chevron-left; } +.#{$fa-css-prefix}-chevron-right:before { content: $fa-var-chevron-right; } +.#{$fa-css-prefix}-plus-circle:before { content: $fa-var-plus-circle; } +.#{$fa-css-prefix}-minus-circle:before { content: $fa-var-minus-circle; } +.#{$fa-css-prefix}-times-circle:before { content: $fa-var-times-circle; } +.#{$fa-css-prefix}-check-circle:before { content: $fa-var-check-circle; } +.#{$fa-css-prefix}-question-circle:before { content: $fa-var-question-circle; } +.#{$fa-css-prefix}-info-circle:before { content: $fa-var-info-circle; } +.#{$fa-css-prefix}-crosshairs:before { content: $fa-var-crosshairs; } +.#{$fa-css-prefix}-times-circle-o:before { content: $fa-var-times-circle-o; } +.#{$fa-css-prefix}-check-circle-o:before { content: $fa-var-check-circle-o; } +.#{$fa-css-prefix}-ban:before { content: $fa-var-ban; } +.#{$fa-css-prefix}-arrow-left:before { content: $fa-var-arrow-left; } +.#{$fa-css-prefix}-arrow-right:before { content: $fa-var-arrow-right; } +.#{$fa-css-prefix}-arrow-up:before { content: $fa-var-arrow-up; } +.#{$fa-css-prefix}-arrow-down:before { content: $fa-var-arrow-down; } +.#{$fa-css-prefix}-mail-forward:before, +.#{$fa-css-prefix}-share:before { content: $fa-var-share; } +.#{$fa-css-prefix}-expand:before { content: $fa-var-expand; } +.#{$fa-css-prefix}-compress:before { content: $fa-var-compress; } +.#{$fa-css-prefix}-plus:before { content: $fa-var-plus; } +.#{$fa-css-prefix}-minus:before { content: $fa-var-minus; } +.#{$fa-css-prefix}-asterisk:before { content: $fa-var-asterisk; } +.#{$fa-css-prefix}-exclamation-circle:before { content: $fa-var-exclamation-circle; } +.#{$fa-css-prefix}-gift:before { content: $fa-var-gift; } +.#{$fa-css-prefix}-leaf:before { content: $fa-var-leaf; } +.#{$fa-css-prefix}-fire:before { content: $fa-var-fire; } +.#{$fa-css-prefix}-eye:before { content: $fa-var-eye; } +.#{$fa-css-prefix}-eye-slash:before { content: $fa-var-eye-slash; } +.#{$fa-css-prefix}-warning:before, +.#{$fa-css-prefix}-exclamation-triangle:before { content: $fa-var-exclamation-triangle; } +.#{$fa-css-prefix}-plane:before { content: $fa-var-plane; } +.#{$fa-css-prefix}-calendar:before { content: $fa-var-calendar; } +.#{$fa-css-prefix}-random:before { content: $fa-var-random; } +.#{$fa-css-prefix}-comment:before { content: $fa-var-comment; } +.#{$fa-css-prefix}-magnet:before { content: $fa-var-magnet; } +.#{$fa-css-prefix}-chevron-up:before { content: $fa-var-chevron-up; } +.#{$fa-css-prefix}-chevron-down:before { content: $fa-var-chevron-down; } +.#{$fa-css-prefix}-retweet:before { content: $fa-var-retweet; } +.#{$fa-css-prefix}-shopping-cart:before { content: $fa-var-shopping-cart; } +.#{$fa-css-prefix}-folder:before { content: $fa-var-folder; } +.#{$fa-css-prefix}-folder-open:before { content: $fa-var-folder-open; } +.#{$fa-css-prefix}-arrows-v:before { content: $fa-var-arrows-v; } +.#{$fa-css-prefix}-arrows-h:before { content: $fa-var-arrows-h; } +.#{$fa-css-prefix}-bar-chart-o:before, +.#{$fa-css-prefix}-bar-chart:before { content: $fa-var-bar-chart; } +.#{$fa-css-prefix}-twitter-square:before { content: $fa-var-twitter-square; } +.#{$fa-css-prefix}-facebook-square:before { content: $fa-var-facebook-square; } +.#{$fa-css-prefix}-camera-retro:before { content: $fa-var-camera-retro; } +.#{$fa-css-prefix}-key:before { content: $fa-var-key; } +.#{$fa-css-prefix}-gears:before, +.#{$fa-css-prefix}-cogs:before { content: $fa-var-cogs; } +.#{$fa-css-prefix}-comments:before { content: $fa-var-comments; } +.#{$fa-css-prefix}-thumbs-o-up:before { content: $fa-var-thumbs-o-up; } +.#{$fa-css-prefix}-thumbs-o-down:before { content: $fa-var-thumbs-o-down; } +.#{$fa-css-prefix}-star-half:before { content: $fa-var-star-half; } +.#{$fa-css-prefix}-heart-o:before { content: $fa-var-heart-o; } +.#{$fa-css-prefix}-sign-out:before { content: $fa-var-sign-out; } +.#{$fa-css-prefix}-linkedin-square:before { content: $fa-var-linkedin-square; } +.#{$fa-css-prefix}-thumb-tack:before { content: $fa-var-thumb-tack; } +.#{$fa-css-prefix}-external-link:before { content: $fa-var-external-link; } +.#{$fa-css-prefix}-sign-in:before { content: $fa-var-sign-in; } +.#{$fa-css-prefix}-trophy:before { content: $fa-var-trophy; } +.#{$fa-css-prefix}-github-square:before { content: $fa-var-github-square; } +.#{$fa-css-prefix}-upload:before { content: $fa-var-upload; } +.#{$fa-css-prefix}-lemon-o:before { content: $fa-var-lemon-o; } +.#{$fa-css-prefix}-phone:before { content: $fa-var-phone; } +.#{$fa-css-prefix}-square-o:before { content: $fa-var-square-o; } +.#{$fa-css-prefix}-bookmark-o:before { content: $fa-var-bookmark-o; } +.#{$fa-css-prefix}-phone-square:before { content: $fa-var-phone-square; } +.#{$fa-css-prefix}-twitter:before { content: $fa-var-twitter; } +.#{$fa-css-prefix}-facebook-f:before, +.#{$fa-css-prefix}-facebook:before { content: $fa-var-facebook; } +.#{$fa-css-prefix}-github:before { content: $fa-var-github; } +.#{$fa-css-prefix}-unlock:before { content: $fa-var-unlock; } +.#{$fa-css-prefix}-credit-card:before { content: $fa-var-credit-card; } +.#{$fa-css-prefix}-feed:before, +.#{$fa-css-prefix}-rss:before { content: $fa-var-rss; } +.#{$fa-css-prefix}-hdd-o:before { content: $fa-var-hdd-o; } +.#{$fa-css-prefix}-bullhorn:before { content: $fa-var-bullhorn; } +.#{$fa-css-prefix}-bell:before { content: $fa-var-bell; } +.#{$fa-css-prefix}-certificate:before { content: $fa-var-certificate; } +.#{$fa-css-prefix}-hand-o-right:before { content: $fa-var-hand-o-right; } +.#{$fa-css-prefix}-hand-o-left:before { content: $fa-var-hand-o-left; } +.#{$fa-css-prefix}-hand-o-up:before { content: $fa-var-hand-o-up; } +.#{$fa-css-prefix}-hand-o-down:before { content: $fa-var-hand-o-down; } +.#{$fa-css-prefix}-arrow-circle-left:before { content: $fa-var-arrow-circle-left; } +.#{$fa-css-prefix}-arrow-circle-right:before { content: $fa-var-arrow-circle-right; } +.#{$fa-css-prefix}-arrow-circle-up:before { content: $fa-var-arrow-circle-up; } +.#{$fa-css-prefix}-arrow-circle-down:before { content: $fa-var-arrow-circle-down; } +.#{$fa-css-prefix}-globe:before { content: $fa-var-globe; } +.#{$fa-css-prefix}-wrench:before { content: $fa-var-wrench; } +.#{$fa-css-prefix}-tasks:before { content: $fa-var-tasks; } +.#{$fa-css-prefix}-filter:before { content: $fa-var-filter; } +.#{$fa-css-prefix}-briefcase:before { content: $fa-var-briefcase; } +.#{$fa-css-prefix}-arrows-alt:before { content: $fa-var-arrows-alt; } +.#{$fa-css-prefix}-group:before, +.#{$fa-css-prefix}-users:before { content: $fa-var-users; } +.#{$fa-css-prefix}-chain:before, +.#{$fa-css-prefix}-link:before { content: $fa-var-link; } +.#{$fa-css-prefix}-cloud:before { content: $fa-var-cloud; } +.#{$fa-css-prefix}-flask:before { content: $fa-var-flask; } +.#{$fa-css-prefix}-cut:before, +.#{$fa-css-prefix}-scissors:before { content: $fa-var-scissors; } +.#{$fa-css-prefix}-copy:before, +.#{$fa-css-prefix}-files-o:before { content: $fa-var-files-o; } +.#{$fa-css-prefix}-paperclip:before { content: $fa-var-paperclip; } +.#{$fa-css-prefix}-save:before, +.#{$fa-css-prefix}-floppy-o:before { content: $fa-var-floppy-o; } +.#{$fa-css-prefix}-square:before { content: $fa-var-square; } +.#{$fa-css-prefix}-navicon:before, +.#{$fa-css-prefix}-reorder:before, +.#{$fa-css-prefix}-bars:before { content: $fa-var-bars; } +.#{$fa-css-prefix}-list-ul:before { content: $fa-var-list-ul; } +.#{$fa-css-prefix}-list-ol:before { content: $fa-var-list-ol; } +.#{$fa-css-prefix}-strikethrough:before { content: $fa-var-strikethrough; } +.#{$fa-css-prefix}-underline:before { content: $fa-var-underline; } +.#{$fa-css-prefix}-table:before { content: $fa-var-table; } +.#{$fa-css-prefix}-magic:before { content: $fa-var-magic; } +.#{$fa-css-prefix}-truck:before { content: $fa-var-truck; } +.#{$fa-css-prefix}-pinterest:before { content: $fa-var-pinterest; } +.#{$fa-css-prefix}-pinterest-square:before { content: $fa-var-pinterest-square; } +.#{$fa-css-prefix}-google-plus-square:before { content: $fa-var-google-plus-square; } +.#{$fa-css-prefix}-google-plus:before { content: $fa-var-google-plus; } +.#{$fa-css-prefix}-money:before { content: $fa-var-money; } +.#{$fa-css-prefix}-caret-down:before { content: $fa-var-caret-down; } +.#{$fa-css-prefix}-caret-up:before { content: $fa-var-caret-up; } +.#{$fa-css-prefix}-caret-left:before { content: $fa-var-caret-left; } +.#{$fa-css-prefix}-caret-right:before { content: $fa-var-caret-right; } +.#{$fa-css-prefix}-columns:before { content: $fa-var-columns; } +.#{$fa-css-prefix}-unsorted:before, +.#{$fa-css-prefix}-sort:before { content: $fa-var-sort; } +.#{$fa-css-prefix}-sort-down:before, +.#{$fa-css-prefix}-sort-desc:before { content: $fa-var-sort-desc; } +.#{$fa-css-prefix}-sort-up:before, +.#{$fa-css-prefix}-sort-asc:before { content: $fa-var-sort-asc; } +.#{$fa-css-prefix}-envelope:before { content: $fa-var-envelope; } +.#{$fa-css-prefix}-linkedin:before { content: $fa-var-linkedin; } +.#{$fa-css-prefix}-rotate-left:before, +.#{$fa-css-prefix}-undo:before { content: $fa-var-undo; } +.#{$fa-css-prefix}-legal:before, +.#{$fa-css-prefix}-gavel:before { content: $fa-var-gavel; } +.#{$fa-css-prefix}-dashboard:before, +.#{$fa-css-prefix}-tachometer:before { content: $fa-var-tachometer; } +.#{$fa-css-prefix}-comment-o:before { content: $fa-var-comment-o; } +.#{$fa-css-prefix}-comments-o:before { content: $fa-var-comments-o; } +.#{$fa-css-prefix}-flash:before, +.#{$fa-css-prefix}-bolt:before { content: $fa-var-bolt; } +.#{$fa-css-prefix}-sitemap:before { content: $fa-var-sitemap; } +.#{$fa-css-prefix}-umbrella:before { content: $fa-var-umbrella; } +.#{$fa-css-prefix}-paste:before, +.#{$fa-css-prefix}-clipboard:before { content: $fa-var-clipboard; } +.#{$fa-css-prefix}-lightbulb-o:before { content: $fa-var-lightbulb-o; } +.#{$fa-css-prefix}-exchange:before { content: $fa-var-exchange; } +.#{$fa-css-prefix}-cloud-download:before { content: $fa-var-cloud-download; } +.#{$fa-css-prefix}-cloud-upload:before { content: $fa-var-cloud-upload; } +.#{$fa-css-prefix}-user-md:before { content: $fa-var-user-md; } +.#{$fa-css-prefix}-stethoscope:before { content: $fa-var-stethoscope; } +.#{$fa-css-prefix}-suitcase:before { content: $fa-var-suitcase; } +.#{$fa-css-prefix}-bell-o:before { content: $fa-var-bell-o; } +.#{$fa-css-prefix}-coffee:before { content: $fa-var-coffee; } +.#{$fa-css-prefix}-cutlery:before { content: $fa-var-cutlery; } +.#{$fa-css-prefix}-file-text-o:before { content: $fa-var-file-text-o; } +.#{$fa-css-prefix}-building-o:before { content: $fa-var-building-o; } +.#{$fa-css-prefix}-hospital-o:before { content: $fa-var-hospital-o; } +.#{$fa-css-prefix}-ambulance:before { content: $fa-var-ambulance; } +.#{$fa-css-prefix}-medkit:before { content: $fa-var-medkit; } +.#{$fa-css-prefix}-fighter-jet:before { content: $fa-var-fighter-jet; } +.#{$fa-css-prefix}-beer:before { content: $fa-var-beer; } +.#{$fa-css-prefix}-h-square:before { content: $fa-var-h-square; } +.#{$fa-css-prefix}-plus-square:before { content: $fa-var-plus-square; } +.#{$fa-css-prefix}-angle-double-left:before { content: $fa-var-angle-double-left; } +.#{$fa-css-prefix}-angle-double-right:before { content: $fa-var-angle-double-right; } +.#{$fa-css-prefix}-angle-double-up:before { content: $fa-var-angle-double-up; } +.#{$fa-css-prefix}-angle-double-down:before { content: $fa-var-angle-double-down; } +.#{$fa-css-prefix}-angle-left:before { content: $fa-var-angle-left; } +.#{$fa-css-prefix}-angle-right:before { content: $fa-var-angle-right; } +.#{$fa-css-prefix}-angle-up:before { content: $fa-var-angle-up; } +.#{$fa-css-prefix}-angle-down:before { content: $fa-var-angle-down; } +.#{$fa-css-prefix}-desktop:before { content: $fa-var-desktop; } +.#{$fa-css-prefix}-laptop:before { content: $fa-var-laptop; } +.#{$fa-css-prefix}-tablet:before { content: $fa-var-tablet; } +.#{$fa-css-prefix}-mobile-phone:before, +.#{$fa-css-prefix}-mobile:before { content: $fa-var-mobile; } +.#{$fa-css-prefix}-circle-o:before { content: $fa-var-circle-o; } +.#{$fa-css-prefix}-quote-left:before { content: $fa-var-quote-left; } +.#{$fa-css-prefix}-quote-right:before { content: $fa-var-quote-right; } +.#{$fa-css-prefix}-spinner:before { content: $fa-var-spinner; } +.#{$fa-css-prefix}-circle:before { content: $fa-var-circle; } +.#{$fa-css-prefix}-mail-reply:before, +.#{$fa-css-prefix}-reply:before { content: $fa-var-reply; } +.#{$fa-css-prefix}-github-alt:before { content: $fa-var-github-alt; } +.#{$fa-css-prefix}-folder-o:before { content: $fa-var-folder-o; } +.#{$fa-css-prefix}-folder-open-o:before { content: $fa-var-folder-open-o; } +.#{$fa-css-prefix}-smile-o:before { content: $fa-var-smile-o; } +.#{$fa-css-prefix}-frown-o:before { content: $fa-var-frown-o; } +.#{$fa-css-prefix}-meh-o:before { content: $fa-var-meh-o; } +.#{$fa-css-prefix}-gamepad:before { content: $fa-var-gamepad; } +.#{$fa-css-prefix}-keyboard-o:before { content: $fa-var-keyboard-o; } +.#{$fa-css-prefix}-flag-o:before { content: $fa-var-flag-o; } +.#{$fa-css-prefix}-flag-checkered:before { content: $fa-var-flag-checkered; } +.#{$fa-css-prefix}-terminal:before { content: $fa-var-terminal; } +.#{$fa-css-prefix}-code:before { content: $fa-var-code; } +.#{$fa-css-prefix}-mail-reply-all:before, +.#{$fa-css-prefix}-reply-all:before { content: $fa-var-reply-all; } +.#{$fa-css-prefix}-star-half-empty:before, +.#{$fa-css-prefix}-star-half-full:before, +.#{$fa-css-prefix}-star-half-o:before { content: $fa-var-star-half-o; } +.#{$fa-css-prefix}-location-arrow:before { content: $fa-var-location-arrow; } +.#{$fa-css-prefix}-crop:before { content: $fa-var-crop; } +.#{$fa-css-prefix}-code-fork:before { content: $fa-var-code-fork; } +.#{$fa-css-prefix}-unlink:before, +.#{$fa-css-prefix}-chain-broken:before { content: $fa-var-chain-broken; } +.#{$fa-css-prefix}-question:before { content: $fa-var-question; } +.#{$fa-css-prefix}-info:before { content: $fa-var-info; } +.#{$fa-css-prefix}-exclamation:before { content: $fa-var-exclamation; } +.#{$fa-css-prefix}-superscript:before { content: $fa-var-superscript; } +.#{$fa-css-prefix}-subscript:before { content: $fa-var-subscript; } +.#{$fa-css-prefix}-eraser:before { content: $fa-var-eraser; } +.#{$fa-css-prefix}-puzzle-piece:before { content: $fa-var-puzzle-piece; } +.#{$fa-css-prefix}-microphone:before { content: $fa-var-microphone; } +.#{$fa-css-prefix}-microphone-slash:before { content: $fa-var-microphone-slash; } +.#{$fa-css-prefix}-shield:before { content: $fa-var-shield; } +.#{$fa-css-prefix}-calendar-o:before { content: $fa-var-calendar-o; } +.#{$fa-css-prefix}-fire-extinguisher:before { content: $fa-var-fire-extinguisher; } +.#{$fa-css-prefix}-rocket:before { content: $fa-var-rocket; } +.#{$fa-css-prefix}-maxcdn:before { content: $fa-var-maxcdn; } +.#{$fa-css-prefix}-chevron-circle-left:before { content: $fa-var-chevron-circle-left; } +.#{$fa-css-prefix}-chevron-circle-right:before { content: $fa-var-chevron-circle-right; } +.#{$fa-css-prefix}-chevron-circle-up:before { content: $fa-var-chevron-circle-up; } +.#{$fa-css-prefix}-chevron-circle-down:before { content: $fa-var-chevron-circle-down; } +.#{$fa-css-prefix}-html5:before { content: $fa-var-html5; } +.#{$fa-css-prefix}-css3:before { content: $fa-var-css3; } +.#{$fa-css-prefix}-anchor:before { content: $fa-var-anchor; } +.#{$fa-css-prefix}-unlock-alt:before { content: $fa-var-unlock-alt; } +.#{$fa-css-prefix}-bullseye:before { content: $fa-var-bullseye; } +.#{$fa-css-prefix}-ellipsis-h:before { content: $fa-var-ellipsis-h; } +.#{$fa-css-prefix}-ellipsis-v:before { content: $fa-var-ellipsis-v; } +.#{$fa-css-prefix}-rss-square:before { content: $fa-var-rss-square; } +.#{$fa-css-prefix}-play-circle:before { content: $fa-var-play-circle; } +.#{$fa-css-prefix}-ticket:before { content: $fa-var-ticket; } +.#{$fa-css-prefix}-minus-square:before { content: $fa-var-minus-square; } +.#{$fa-css-prefix}-minus-square-o:before { content: $fa-var-minus-square-o; } +.#{$fa-css-prefix}-level-up:before { content: $fa-var-level-up; } +.#{$fa-css-prefix}-level-down:before { content: $fa-var-level-down; } +.#{$fa-css-prefix}-check-square:before { content: $fa-var-check-square; } +.#{$fa-css-prefix}-pencil-square:before { content: $fa-var-pencil-square; } +.#{$fa-css-prefix}-external-link-square:before { content: $fa-var-external-link-square; } +.#{$fa-css-prefix}-share-square:before { content: $fa-var-share-square; } +.#{$fa-css-prefix}-compass:before { content: $fa-var-compass; } +.#{$fa-css-prefix}-toggle-down:before, +.#{$fa-css-prefix}-caret-square-o-down:before { content: $fa-var-caret-square-o-down; } +.#{$fa-css-prefix}-toggle-up:before, +.#{$fa-css-prefix}-caret-square-o-up:before { content: $fa-var-caret-square-o-up; } +.#{$fa-css-prefix}-toggle-right:before, +.#{$fa-css-prefix}-caret-square-o-right:before { content: $fa-var-caret-square-o-right; } +.#{$fa-css-prefix}-euro:before, +.#{$fa-css-prefix}-eur:before { content: $fa-var-eur; } +.#{$fa-css-prefix}-gbp:before { content: $fa-var-gbp; } +.#{$fa-css-prefix}-dollar:before, +.#{$fa-css-prefix}-usd:before { content: $fa-var-usd; } +.#{$fa-css-prefix}-rupee:before, +.#{$fa-css-prefix}-inr:before { content: $fa-var-inr; } +.#{$fa-css-prefix}-cny:before, +.#{$fa-css-prefix}-rmb:before, +.#{$fa-css-prefix}-yen:before, +.#{$fa-css-prefix}-jpy:before { content: $fa-var-jpy; } +.#{$fa-css-prefix}-ruble:before, +.#{$fa-css-prefix}-rouble:before, +.#{$fa-css-prefix}-rub:before { content: $fa-var-rub; } +.#{$fa-css-prefix}-won:before, +.#{$fa-css-prefix}-krw:before { content: $fa-var-krw; } +.#{$fa-css-prefix}-bitcoin:before, +.#{$fa-css-prefix}-btc:before { content: $fa-var-btc; } +.#{$fa-css-prefix}-file:before { content: $fa-var-file; } +.#{$fa-css-prefix}-file-text:before { content: $fa-var-file-text; } +.#{$fa-css-prefix}-sort-alpha-asc:before { content: $fa-var-sort-alpha-asc; } +.#{$fa-css-prefix}-sort-alpha-desc:before { content: $fa-var-sort-alpha-desc; } +.#{$fa-css-prefix}-sort-amount-asc:before { content: $fa-var-sort-amount-asc; } +.#{$fa-css-prefix}-sort-amount-desc:before { content: $fa-var-sort-amount-desc; } +.#{$fa-css-prefix}-sort-numeric-asc:before { content: $fa-var-sort-numeric-asc; } +.#{$fa-css-prefix}-sort-numeric-desc:before { content: $fa-var-sort-numeric-desc; } +.#{$fa-css-prefix}-thumbs-up:before { content: $fa-var-thumbs-up; } +.#{$fa-css-prefix}-thumbs-down:before { content: $fa-var-thumbs-down; } +.#{$fa-css-prefix}-youtube-square:before { content: $fa-var-youtube-square; } +.#{$fa-css-prefix}-youtube:before { content: $fa-var-youtube; } +.#{$fa-css-prefix}-xing:before { content: $fa-var-xing; } +.#{$fa-css-prefix}-xing-square:before { content: $fa-var-xing-square; } +.#{$fa-css-prefix}-youtube-play:before { content: $fa-var-youtube-play; } +.#{$fa-css-prefix}-dropbox:before { content: $fa-var-dropbox; } +.#{$fa-css-prefix}-stack-overflow:before { content: $fa-var-stack-overflow; } +.#{$fa-css-prefix}-instagram:before { content: $fa-var-instagram; } +.#{$fa-css-prefix}-flickr:before { content: $fa-var-flickr; } +.#{$fa-css-prefix}-adn:before { content: $fa-var-adn; } +.#{$fa-css-prefix}-bitbucket:before { content: $fa-var-bitbucket; } +.#{$fa-css-prefix}-bitbucket-square:before { content: $fa-var-bitbucket-square; } +.#{$fa-css-prefix}-tumblr:before { content: $fa-var-tumblr; } +.#{$fa-css-prefix}-tumblr-square:before { content: $fa-var-tumblr-square; } +.#{$fa-css-prefix}-long-arrow-down:before { content: $fa-var-long-arrow-down; } +.#{$fa-css-prefix}-long-arrow-up:before { content: $fa-var-long-arrow-up; } +.#{$fa-css-prefix}-long-arrow-left:before { content: $fa-var-long-arrow-left; } +.#{$fa-css-prefix}-long-arrow-right:before { content: $fa-var-long-arrow-right; } +.#{$fa-css-prefix}-apple:before { content: $fa-var-apple; } +.#{$fa-css-prefix}-windows:before { content: $fa-var-windows; } +.#{$fa-css-prefix}-android:before { content: $fa-var-android; } +.#{$fa-css-prefix}-linux:before { content: $fa-var-linux; } +.#{$fa-css-prefix}-dribbble:before { content: $fa-var-dribbble; } +.#{$fa-css-prefix}-skype:before { content: $fa-var-skype; } +.#{$fa-css-prefix}-foursquare:before { content: $fa-var-foursquare; } +.#{$fa-css-prefix}-trello:before { content: $fa-var-trello; } +.#{$fa-css-prefix}-female:before { content: $fa-var-female; } +.#{$fa-css-prefix}-male:before { content: $fa-var-male; } +.#{$fa-css-prefix}-gittip:before, +.#{$fa-css-prefix}-gratipay:before { content: $fa-var-gratipay; } +.#{$fa-css-prefix}-sun-o:before { content: $fa-var-sun-o; } +.#{$fa-css-prefix}-moon-o:before { content: $fa-var-moon-o; } +.#{$fa-css-prefix}-archive:before { content: $fa-var-archive; } +.#{$fa-css-prefix}-bug:before { content: $fa-var-bug; } +.#{$fa-css-prefix}-vk:before { content: $fa-var-vk; } +.#{$fa-css-prefix}-weibo:before { content: $fa-var-weibo; } +.#{$fa-css-prefix}-renren:before { content: $fa-var-renren; } +.#{$fa-css-prefix}-pagelines:before { content: $fa-var-pagelines; } +.#{$fa-css-prefix}-stack-exchange:before { content: $fa-var-stack-exchange; } +.#{$fa-css-prefix}-arrow-circle-o-right:before { content: $fa-var-arrow-circle-o-right; } +.#{$fa-css-prefix}-arrow-circle-o-left:before { content: $fa-var-arrow-circle-o-left; } +.#{$fa-css-prefix}-toggle-left:before, +.#{$fa-css-prefix}-caret-square-o-left:before { content: $fa-var-caret-square-o-left; } +.#{$fa-css-prefix}-dot-circle-o:before { content: $fa-var-dot-circle-o; } +.#{$fa-css-prefix}-wheelchair:before { content: $fa-var-wheelchair; } +.#{$fa-css-prefix}-vimeo-square:before { content: $fa-var-vimeo-square; } +.#{$fa-css-prefix}-turkish-lira:before, +.#{$fa-css-prefix}-try:before { content: $fa-var-try; } +.#{$fa-css-prefix}-plus-square-o:before { content: $fa-var-plus-square-o; } +.#{$fa-css-prefix}-space-shuttle:before { content: $fa-var-space-shuttle; } +.#{$fa-css-prefix}-slack:before { content: $fa-var-slack; } +.#{$fa-css-prefix}-envelope-square:before { content: $fa-var-envelope-square; } +.#{$fa-css-prefix}-wordpress:before { content: $fa-var-wordpress; } +.#{$fa-css-prefix}-openid:before { content: $fa-var-openid; } +.#{$fa-css-prefix}-institution:before, +.#{$fa-css-prefix}-bank:before, +.#{$fa-css-prefix}-university:before { content: $fa-var-university; } +.#{$fa-css-prefix}-mortar-board:before, +.#{$fa-css-prefix}-graduation-cap:before { content: $fa-var-graduation-cap; } +.#{$fa-css-prefix}-yahoo:before { content: $fa-var-yahoo; } +.#{$fa-css-prefix}-google:before { content: $fa-var-google; } +.#{$fa-css-prefix}-reddit:before { content: $fa-var-reddit; } +.#{$fa-css-prefix}-reddit-square:before { content: $fa-var-reddit-square; } +.#{$fa-css-prefix}-stumbleupon-circle:before { content: $fa-var-stumbleupon-circle; } +.#{$fa-css-prefix}-stumbleupon:before { content: $fa-var-stumbleupon; } +.#{$fa-css-prefix}-delicious:before { content: $fa-var-delicious; } +.#{$fa-css-prefix}-digg:before { content: $fa-var-digg; } +.#{$fa-css-prefix}-pied-piper-pp:before { content: $fa-var-pied-piper-pp; } +.#{$fa-css-prefix}-pied-piper-alt:before { content: $fa-var-pied-piper-alt; } +.#{$fa-css-prefix}-drupal:before { content: $fa-var-drupal; } +.#{$fa-css-prefix}-joomla:before { content: $fa-var-joomla; } +.#{$fa-css-prefix}-language:before { content: $fa-var-language; } +.#{$fa-css-prefix}-fax:before { content: $fa-var-fax; } +.#{$fa-css-prefix}-building:before { content: $fa-var-building; } +.#{$fa-css-prefix}-child:before { content: $fa-var-child; } +.#{$fa-css-prefix}-paw:before { content: $fa-var-paw; } +.#{$fa-css-prefix}-spoon:before { content: $fa-var-spoon; } +.#{$fa-css-prefix}-cube:before { content: $fa-var-cube; } +.#{$fa-css-prefix}-cubes:before { content: $fa-var-cubes; } +.#{$fa-css-prefix}-behance:before { content: $fa-var-behance; } +.#{$fa-css-prefix}-behance-square:before { content: $fa-var-behance-square; } +.#{$fa-css-prefix}-steam:before { content: $fa-var-steam; } +.#{$fa-css-prefix}-steam-square:before { content: $fa-var-steam-square; } +.#{$fa-css-prefix}-recycle:before { content: $fa-var-recycle; } +.#{$fa-css-prefix}-automobile:before, +.#{$fa-css-prefix}-car:before { content: $fa-var-car; } +.#{$fa-css-prefix}-cab:before, +.#{$fa-css-prefix}-taxi:before { content: $fa-var-taxi; } +.#{$fa-css-prefix}-tree:before { content: $fa-var-tree; } +.#{$fa-css-prefix}-spotify:before { content: $fa-var-spotify; } +.#{$fa-css-prefix}-deviantart:before { content: $fa-var-deviantart; } +.#{$fa-css-prefix}-soundcloud:before { content: $fa-var-soundcloud; } +.#{$fa-css-prefix}-database:before { content: $fa-var-database; } +.#{$fa-css-prefix}-file-pdf-o:before { content: $fa-var-file-pdf-o; } +.#{$fa-css-prefix}-file-word-o:before { content: $fa-var-file-word-o; } +.#{$fa-css-prefix}-file-excel-o:before { content: $fa-var-file-excel-o; } +.#{$fa-css-prefix}-file-powerpoint-o:before { content: $fa-var-file-powerpoint-o; } +.#{$fa-css-prefix}-file-photo-o:before, +.#{$fa-css-prefix}-file-picture-o:before, +.#{$fa-css-prefix}-file-image-o:before { content: $fa-var-file-image-o; } +.#{$fa-css-prefix}-file-zip-o:before, +.#{$fa-css-prefix}-file-archive-o:before { content: $fa-var-file-archive-o; } +.#{$fa-css-prefix}-file-sound-o:before, +.#{$fa-css-prefix}-file-audio-o:before { content: $fa-var-file-audio-o; } +.#{$fa-css-prefix}-file-movie-o:before, +.#{$fa-css-prefix}-file-video-o:before { content: $fa-var-file-video-o; } +.#{$fa-css-prefix}-file-code-o:before { content: $fa-var-file-code-o; } +.#{$fa-css-prefix}-vine:before { content: $fa-var-vine; } +.#{$fa-css-prefix}-codepen:before { content: $fa-var-codepen; } +.#{$fa-css-prefix}-jsfiddle:before { content: $fa-var-jsfiddle; } +.#{$fa-css-prefix}-life-bouy:before, +.#{$fa-css-prefix}-life-buoy:before, +.#{$fa-css-prefix}-life-saver:before, +.#{$fa-css-prefix}-support:before, +.#{$fa-css-prefix}-life-ring:before { content: $fa-var-life-ring; } +.#{$fa-css-prefix}-circle-o-notch:before { content: $fa-var-circle-o-notch; } +.#{$fa-css-prefix}-ra:before, +.#{$fa-css-prefix}-resistance:before, +.#{$fa-css-prefix}-rebel:before { content: $fa-var-rebel; } +.#{$fa-css-prefix}-ge:before, +.#{$fa-css-prefix}-empire:before { content: $fa-var-empire; } +.#{$fa-css-prefix}-git-square:before { content: $fa-var-git-square; } +.#{$fa-css-prefix}-git:before { content: $fa-var-git; } +.#{$fa-css-prefix}-y-combinator-square:before, +.#{$fa-css-prefix}-yc-square:before, +.#{$fa-css-prefix}-hacker-news:before { content: $fa-var-hacker-news; } +.#{$fa-css-prefix}-tencent-weibo:before { content: $fa-var-tencent-weibo; } +.#{$fa-css-prefix}-qq:before { content: $fa-var-qq; } +.#{$fa-css-prefix}-wechat:before, +.#{$fa-css-prefix}-weixin:before { content: $fa-var-weixin; } +.#{$fa-css-prefix}-send:before, +.#{$fa-css-prefix}-paper-plane:before { content: $fa-var-paper-plane; } +.#{$fa-css-prefix}-send-o:before, +.#{$fa-css-prefix}-paper-plane-o:before { content: $fa-var-paper-plane-o; } +.#{$fa-css-prefix}-history:before { content: $fa-var-history; } +.#{$fa-css-prefix}-circle-thin:before { content: $fa-var-circle-thin; } +.#{$fa-css-prefix}-header:before { content: $fa-var-header; } +.#{$fa-css-prefix}-paragraph:before { content: $fa-var-paragraph; } +.#{$fa-css-prefix}-sliders:before { content: $fa-var-sliders; } +.#{$fa-css-prefix}-share-alt:before { content: $fa-var-share-alt; } +.#{$fa-css-prefix}-share-alt-square:before { content: $fa-var-share-alt-square; } +.#{$fa-css-prefix}-bomb:before { content: $fa-var-bomb; } +.#{$fa-css-prefix}-soccer-ball-o:before, +.#{$fa-css-prefix}-futbol-o:before { content: $fa-var-futbol-o; } +.#{$fa-css-prefix}-tty:before { content: $fa-var-tty; } +.#{$fa-css-prefix}-binoculars:before { content: $fa-var-binoculars; } +.#{$fa-css-prefix}-plug:before { content: $fa-var-plug; } +.#{$fa-css-prefix}-slideshare:before { content: $fa-var-slideshare; } +.#{$fa-css-prefix}-twitch:before { content: $fa-var-twitch; } +.#{$fa-css-prefix}-yelp:before { content: $fa-var-yelp; } +.#{$fa-css-prefix}-newspaper-o:before { content: $fa-var-newspaper-o; } +.#{$fa-css-prefix}-wifi:before { content: $fa-var-wifi; } +.#{$fa-css-prefix}-calculator:before { content: $fa-var-calculator; } +.#{$fa-css-prefix}-paypal:before { content: $fa-var-paypal; } +.#{$fa-css-prefix}-google-wallet:before { content: $fa-var-google-wallet; } +.#{$fa-css-prefix}-cc-visa:before { content: $fa-var-cc-visa; } +.#{$fa-css-prefix}-cc-mastercard:before { content: $fa-var-cc-mastercard; } +.#{$fa-css-prefix}-cc-discover:before { content: $fa-var-cc-discover; } +.#{$fa-css-prefix}-cc-amex:before { content: $fa-var-cc-amex; } +.#{$fa-css-prefix}-cc-paypal:before { content: $fa-var-cc-paypal; } +.#{$fa-css-prefix}-cc-stripe:before { content: $fa-var-cc-stripe; } +.#{$fa-css-prefix}-bell-slash:before { content: $fa-var-bell-slash; } +.#{$fa-css-prefix}-bell-slash-o:before { content: $fa-var-bell-slash-o; } +.#{$fa-css-prefix}-trash:before { content: $fa-var-trash; } +.#{$fa-css-prefix}-copyright:before { content: $fa-var-copyright; } +.#{$fa-css-prefix}-at:before { content: $fa-var-at; } +.#{$fa-css-prefix}-eyedropper:before { content: $fa-var-eyedropper; } +.#{$fa-css-prefix}-paint-brush:before { content: $fa-var-paint-brush; } +.#{$fa-css-prefix}-birthday-cake:before { content: $fa-var-birthday-cake; } +.#{$fa-css-prefix}-area-chart:before { content: $fa-var-area-chart; } +.#{$fa-css-prefix}-pie-chart:before { content: $fa-var-pie-chart; } +.#{$fa-css-prefix}-line-chart:before { content: $fa-var-line-chart; } +.#{$fa-css-prefix}-lastfm:before { content: $fa-var-lastfm; } +.#{$fa-css-prefix}-lastfm-square:before { content: $fa-var-lastfm-square; } +.#{$fa-css-prefix}-toggle-off:before { content: $fa-var-toggle-off; } +.#{$fa-css-prefix}-toggle-on:before { content: $fa-var-toggle-on; } +.#{$fa-css-prefix}-bicycle:before { content: $fa-var-bicycle; } +.#{$fa-css-prefix}-bus:before { content: $fa-var-bus; } +.#{$fa-css-prefix}-ioxhost:before { content: $fa-var-ioxhost; } +.#{$fa-css-prefix}-angellist:before { content: $fa-var-angellist; } +.#{$fa-css-prefix}-cc:before { content: $fa-var-cc; } +.#{$fa-css-prefix}-shekel:before, +.#{$fa-css-prefix}-sheqel:before, +.#{$fa-css-prefix}-ils:before { content: $fa-var-ils; } +.#{$fa-css-prefix}-meanpath:before { content: $fa-var-meanpath; } +.#{$fa-css-prefix}-buysellads:before { content: $fa-var-buysellads; } +.#{$fa-css-prefix}-connectdevelop:before { content: $fa-var-connectdevelop; } +.#{$fa-css-prefix}-dashcube:before { content: $fa-var-dashcube; } +.#{$fa-css-prefix}-forumbee:before { content: $fa-var-forumbee; } +.#{$fa-css-prefix}-leanpub:before { content: $fa-var-leanpub; } +.#{$fa-css-prefix}-sellsy:before { content: $fa-var-sellsy; } +.#{$fa-css-prefix}-shirtsinbulk:before { content: $fa-var-shirtsinbulk; } +.#{$fa-css-prefix}-simplybuilt:before { content: $fa-var-simplybuilt; } +.#{$fa-css-prefix}-skyatlas:before { content: $fa-var-skyatlas; } +.#{$fa-css-prefix}-cart-plus:before { content: $fa-var-cart-plus; } +.#{$fa-css-prefix}-cart-arrow-down:before { content: $fa-var-cart-arrow-down; } +.#{$fa-css-prefix}-diamond:before { content: $fa-var-diamond; } +.#{$fa-css-prefix}-ship:before { content: $fa-var-ship; } +.#{$fa-css-prefix}-user-secret:before { content: $fa-var-user-secret; } +.#{$fa-css-prefix}-motorcycle:before { content: $fa-var-motorcycle; } +.#{$fa-css-prefix}-street-view:before { content: $fa-var-street-view; } +.#{$fa-css-prefix}-heartbeat:before { content: $fa-var-heartbeat; } +.#{$fa-css-prefix}-venus:before { content: $fa-var-venus; } +.#{$fa-css-prefix}-mars:before { content: $fa-var-mars; } +.#{$fa-css-prefix}-mercury:before { content: $fa-var-mercury; } +.#{$fa-css-prefix}-intersex:before, +.#{$fa-css-prefix}-transgender:before { content: $fa-var-transgender; } +.#{$fa-css-prefix}-transgender-alt:before { content: $fa-var-transgender-alt; } +.#{$fa-css-prefix}-venus-double:before { content: $fa-var-venus-double; } +.#{$fa-css-prefix}-mars-double:before { content: $fa-var-mars-double; } +.#{$fa-css-prefix}-venus-mars:before { content: $fa-var-venus-mars; } +.#{$fa-css-prefix}-mars-stroke:before { content: $fa-var-mars-stroke; } +.#{$fa-css-prefix}-mars-stroke-v:before { content: $fa-var-mars-stroke-v; } +.#{$fa-css-prefix}-mars-stroke-h:before { content: $fa-var-mars-stroke-h; } +.#{$fa-css-prefix}-neuter:before { content: $fa-var-neuter; } +.#{$fa-css-prefix}-genderless:before { content: $fa-var-genderless; } +.#{$fa-css-prefix}-facebook-official:before { content: $fa-var-facebook-official; } +.#{$fa-css-prefix}-pinterest-p:before { content: $fa-var-pinterest-p; } +.#{$fa-css-prefix}-whatsapp:before { content: $fa-var-whatsapp; } +.#{$fa-css-prefix}-server:before { content: $fa-var-server; } +.#{$fa-css-prefix}-user-plus:before { content: $fa-var-user-plus; } +.#{$fa-css-prefix}-user-times:before { content: $fa-var-user-times; } +.#{$fa-css-prefix}-hotel:before, +.#{$fa-css-prefix}-bed:before { content: $fa-var-bed; } +.#{$fa-css-prefix}-viacoin:before { content: $fa-var-viacoin; } +.#{$fa-css-prefix}-train:before { content: $fa-var-train; } +.#{$fa-css-prefix}-subway:before { content: $fa-var-subway; } +.#{$fa-css-prefix}-medium:before { content: $fa-var-medium; } +.#{$fa-css-prefix}-yc:before, +.#{$fa-css-prefix}-y-combinator:before { content: $fa-var-y-combinator; } +.#{$fa-css-prefix}-optin-monster:before { content: $fa-var-optin-monster; } +.#{$fa-css-prefix}-opencart:before { content: $fa-var-opencart; } +.#{$fa-css-prefix}-expeditedssl:before { content: $fa-var-expeditedssl; } +.#{$fa-css-prefix}-battery-4:before, +.#{$fa-css-prefix}-battery:before, +.#{$fa-css-prefix}-battery-full:before { content: $fa-var-battery-full; } +.#{$fa-css-prefix}-battery-3:before, +.#{$fa-css-prefix}-battery-three-quarters:before { content: $fa-var-battery-three-quarters; } +.#{$fa-css-prefix}-battery-2:before, +.#{$fa-css-prefix}-battery-half:before { content: $fa-var-battery-half; } +.#{$fa-css-prefix}-battery-1:before, +.#{$fa-css-prefix}-battery-quarter:before { content: $fa-var-battery-quarter; } +.#{$fa-css-prefix}-battery-0:before, +.#{$fa-css-prefix}-battery-empty:before { content: $fa-var-battery-empty; } +.#{$fa-css-prefix}-mouse-pointer:before { content: $fa-var-mouse-pointer; } +.#{$fa-css-prefix}-i-cursor:before { content: $fa-var-i-cursor; } +.#{$fa-css-prefix}-object-group:before { content: $fa-var-object-group; } +.#{$fa-css-prefix}-object-ungroup:before { content: $fa-var-object-ungroup; } +.#{$fa-css-prefix}-sticky-note:before { content: $fa-var-sticky-note; } +.#{$fa-css-prefix}-sticky-note-o:before { content: $fa-var-sticky-note-o; } +.#{$fa-css-prefix}-cc-jcb:before { content: $fa-var-cc-jcb; } +.#{$fa-css-prefix}-cc-diners-club:before { content: $fa-var-cc-diners-club; } +.#{$fa-css-prefix}-clone:before { content: $fa-var-clone; } +.#{$fa-css-prefix}-balance-scale:before { content: $fa-var-balance-scale; } +.#{$fa-css-prefix}-hourglass-o:before { content: $fa-var-hourglass-o; } +.#{$fa-css-prefix}-hourglass-1:before, +.#{$fa-css-prefix}-hourglass-start:before { content: $fa-var-hourglass-start; } +.#{$fa-css-prefix}-hourglass-2:before, +.#{$fa-css-prefix}-hourglass-half:before { content: $fa-var-hourglass-half; } +.#{$fa-css-prefix}-hourglass-3:before, +.#{$fa-css-prefix}-hourglass-end:before { content: $fa-var-hourglass-end; } +.#{$fa-css-prefix}-hourglass:before { content: $fa-var-hourglass; } +.#{$fa-css-prefix}-hand-grab-o:before, +.#{$fa-css-prefix}-hand-rock-o:before { content: $fa-var-hand-rock-o; } +.#{$fa-css-prefix}-hand-stop-o:before, +.#{$fa-css-prefix}-hand-paper-o:before { content: $fa-var-hand-paper-o; } +.#{$fa-css-prefix}-hand-scissors-o:before { content: $fa-var-hand-scissors-o; } +.#{$fa-css-prefix}-hand-lizard-o:before { content: $fa-var-hand-lizard-o; } +.#{$fa-css-prefix}-hand-spock-o:before { content: $fa-var-hand-spock-o; } +.#{$fa-css-prefix}-hand-pointer-o:before { content: $fa-var-hand-pointer-o; } +.#{$fa-css-prefix}-hand-peace-o:before { content: $fa-var-hand-peace-o; } +.#{$fa-css-prefix}-trademark:before { content: $fa-var-trademark; } +.#{$fa-css-prefix}-registered:before { content: $fa-var-registered; } +.#{$fa-css-prefix}-creative-commons:before { content: $fa-var-creative-commons; } +.#{$fa-css-prefix}-gg:before { content: $fa-var-gg; } +.#{$fa-css-prefix}-gg-circle:before { content: $fa-var-gg-circle; } +.#{$fa-css-prefix}-tripadvisor:before { content: $fa-var-tripadvisor; } +.#{$fa-css-prefix}-odnoklassniki:before { content: $fa-var-odnoklassniki; } +.#{$fa-css-prefix}-odnoklassniki-square:before { content: $fa-var-odnoklassniki-square; } +.#{$fa-css-prefix}-get-pocket:before { content: $fa-var-get-pocket; } +.#{$fa-css-prefix}-wikipedia-w:before { content: $fa-var-wikipedia-w; } +.#{$fa-css-prefix}-safari:before { content: $fa-var-safari; } +.#{$fa-css-prefix}-chrome:before { content: $fa-var-chrome; } +.#{$fa-css-prefix}-firefox:before { content: $fa-var-firefox; } +.#{$fa-css-prefix}-opera:before { content: $fa-var-opera; } +.#{$fa-css-prefix}-internet-explorer:before { content: $fa-var-internet-explorer; } +.#{$fa-css-prefix}-tv:before, +.#{$fa-css-prefix}-television:before { content: $fa-var-television; } +.#{$fa-css-prefix}-contao:before { content: $fa-var-contao; } +.#{$fa-css-prefix}-500px:before { content: $fa-var-500px; } +.#{$fa-css-prefix}-amazon:before { content: $fa-var-amazon; } +.#{$fa-css-prefix}-calendar-plus-o:before { content: $fa-var-calendar-plus-o; } +.#{$fa-css-prefix}-calendar-minus-o:before { content: $fa-var-calendar-minus-o; } +.#{$fa-css-prefix}-calendar-times-o:before { content: $fa-var-calendar-times-o; } +.#{$fa-css-prefix}-calendar-check-o:before { content: $fa-var-calendar-check-o; } +.#{$fa-css-prefix}-industry:before { content: $fa-var-industry; } +.#{$fa-css-prefix}-map-pin:before { content: $fa-var-map-pin; } +.#{$fa-css-prefix}-map-signs:before { content: $fa-var-map-signs; } +.#{$fa-css-prefix}-map-o:before { content: $fa-var-map-o; } +.#{$fa-css-prefix}-map:before { content: $fa-var-map; } +.#{$fa-css-prefix}-commenting:before { content: $fa-var-commenting; } +.#{$fa-css-prefix}-commenting-o:before { content: $fa-var-commenting-o; } +.#{$fa-css-prefix}-houzz:before { content: $fa-var-houzz; } +.#{$fa-css-prefix}-vimeo:before { content: $fa-var-vimeo; } +.#{$fa-css-prefix}-black-tie:before { content: $fa-var-black-tie; } +.#{$fa-css-prefix}-fonticons:before { content: $fa-var-fonticons; } +.#{$fa-css-prefix}-reddit-alien:before { content: $fa-var-reddit-alien; } +.#{$fa-css-prefix}-edge:before { content: $fa-var-edge; } +.#{$fa-css-prefix}-credit-card-alt:before { content: $fa-var-credit-card-alt; } +.#{$fa-css-prefix}-codiepie:before { content: $fa-var-codiepie; } +.#{$fa-css-prefix}-modx:before { content: $fa-var-modx; } +.#{$fa-css-prefix}-fort-awesome:before { content: $fa-var-fort-awesome; } +.#{$fa-css-prefix}-usb:before { content: $fa-var-usb; } +.#{$fa-css-prefix}-product-hunt:before { content: $fa-var-product-hunt; } +.#{$fa-css-prefix}-mixcloud:before { content: $fa-var-mixcloud; } +.#{$fa-css-prefix}-scribd:before { content: $fa-var-scribd; } +.#{$fa-css-prefix}-pause-circle:before { content: $fa-var-pause-circle; } +.#{$fa-css-prefix}-pause-circle-o:before { content: $fa-var-pause-circle-o; } +.#{$fa-css-prefix}-stop-circle:before { content: $fa-var-stop-circle; } +.#{$fa-css-prefix}-stop-circle-o:before { content: $fa-var-stop-circle-o; } +.#{$fa-css-prefix}-shopping-bag:before { content: $fa-var-shopping-bag; } +.#{$fa-css-prefix}-shopping-basket:before { content: $fa-var-shopping-basket; } +.#{$fa-css-prefix}-hashtag:before { content: $fa-var-hashtag; } +.#{$fa-css-prefix}-bluetooth:before { content: $fa-var-bluetooth; } +.#{$fa-css-prefix}-bluetooth-b:before { content: $fa-var-bluetooth-b; } +.#{$fa-css-prefix}-percent:before { content: $fa-var-percent; } +.#{$fa-css-prefix}-gitlab:before { content: $fa-var-gitlab; } +.#{$fa-css-prefix}-wpbeginner:before { content: $fa-var-wpbeginner; } +.#{$fa-css-prefix}-wpforms:before { content: $fa-var-wpforms; } +.#{$fa-css-prefix}-envira:before { content: $fa-var-envira; } +.#{$fa-css-prefix}-universal-access:before { content: $fa-var-universal-access; } +.#{$fa-css-prefix}-wheelchair-alt:before { content: $fa-var-wheelchair-alt; } +.#{$fa-css-prefix}-question-circle-o:before { content: $fa-var-question-circle-o; } +.#{$fa-css-prefix}-blind:before { content: $fa-var-blind; } +.#{$fa-css-prefix}-audio-description:before { content: $fa-var-audio-description; } +.#{$fa-css-prefix}-volume-control-phone:before { content: $fa-var-volume-control-phone; } +.#{$fa-css-prefix}-braille:before { content: $fa-var-braille; } +.#{$fa-css-prefix}-assistive-listening-systems:before { content: $fa-var-assistive-listening-systems; } +.#{$fa-css-prefix}-asl-interpreting:before, +.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: $fa-var-american-sign-language-interpreting; } +.#{$fa-css-prefix}-deafness:before, +.#{$fa-css-prefix}-hard-of-hearing:before, +.#{$fa-css-prefix}-deaf:before { content: $fa-var-deaf; } +.#{$fa-css-prefix}-glide:before { content: $fa-var-glide; } +.#{$fa-css-prefix}-glide-g:before { content: $fa-var-glide-g; } +.#{$fa-css-prefix}-signing:before, +.#{$fa-css-prefix}-sign-language:before { content: $fa-var-sign-language; } +.#{$fa-css-prefix}-low-vision:before { content: $fa-var-low-vision; } +.#{$fa-css-prefix}-viadeo:before { content: $fa-var-viadeo; } +.#{$fa-css-prefix}-viadeo-square:before { content: $fa-var-viadeo-square; } +.#{$fa-css-prefix}-snapchat:before { content: $fa-var-snapchat; } +.#{$fa-css-prefix}-snapchat-ghost:before { content: $fa-var-snapchat-ghost; } +.#{$fa-css-prefix}-snapchat-square:before { content: $fa-var-snapchat-square; } +.#{$fa-css-prefix}-pied-piper:before { content: $fa-var-pied-piper; } +.#{$fa-css-prefix}-first-order:before { content: $fa-var-first-order; } +.#{$fa-css-prefix}-yoast:before { content: $fa-var-yoast; } +.#{$fa-css-prefix}-themeisle:before { content: $fa-var-themeisle; } +.#{$fa-css-prefix}-google-plus-circle:before, +.#{$fa-css-prefix}-google-plus-official:before { content: $fa-var-google-plus-official; } +.#{$fa-css-prefix}-fa:before, +.#{$fa-css-prefix}-font-awesome:before { content: $fa-var-font-awesome; } +.#{$fa-css-prefix}-handshake-o:before { content: $fa-var-handshake-o; } +.#{$fa-css-prefix}-envelope-open:before { content: $fa-var-envelope-open; } +.#{$fa-css-prefix}-envelope-open-o:before { content: $fa-var-envelope-open-o; } +.#{$fa-css-prefix}-linode:before { content: $fa-var-linode; } +.#{$fa-css-prefix}-address-book:before { content: $fa-var-address-book; } +.#{$fa-css-prefix}-address-book-o:before { content: $fa-var-address-book-o; } +.#{$fa-css-prefix}-vcard:before, +.#{$fa-css-prefix}-address-card:before { content: $fa-var-address-card; } +.#{$fa-css-prefix}-vcard-o:before, +.#{$fa-css-prefix}-address-card-o:before { content: $fa-var-address-card-o; } +.#{$fa-css-prefix}-user-circle:before { content: $fa-var-user-circle; } +.#{$fa-css-prefix}-user-circle-o:before { content: $fa-var-user-circle-o; } +.#{$fa-css-prefix}-user-o:before { content: $fa-var-user-o; } +.#{$fa-css-prefix}-id-badge:before { content: $fa-var-id-badge; } +.#{$fa-css-prefix}-drivers-license:before, +.#{$fa-css-prefix}-id-card:before { content: $fa-var-id-card; } +.#{$fa-css-prefix}-drivers-license-o:before, +.#{$fa-css-prefix}-id-card-o:before { content: $fa-var-id-card-o; } +.#{$fa-css-prefix}-quora:before { content: $fa-var-quora; } +.#{$fa-css-prefix}-free-code-camp:before { content: $fa-var-free-code-camp; } +.#{$fa-css-prefix}-telegram:before { content: $fa-var-telegram; } +.#{$fa-css-prefix}-thermometer-4:before, +.#{$fa-css-prefix}-thermometer:before, +.#{$fa-css-prefix}-thermometer-full:before { content: $fa-var-thermometer-full; } +.#{$fa-css-prefix}-thermometer-3:before, +.#{$fa-css-prefix}-thermometer-three-quarters:before { content: $fa-var-thermometer-three-quarters; } +.#{$fa-css-prefix}-thermometer-2:before, +.#{$fa-css-prefix}-thermometer-half:before { content: $fa-var-thermometer-half; } +.#{$fa-css-prefix}-thermometer-1:before, +.#{$fa-css-prefix}-thermometer-quarter:before { content: $fa-var-thermometer-quarter; } +.#{$fa-css-prefix}-thermometer-0:before, +.#{$fa-css-prefix}-thermometer-empty:before { content: $fa-var-thermometer-empty; } +.#{$fa-css-prefix}-shower:before { content: $fa-var-shower; } +.#{$fa-css-prefix}-bathtub:before, +.#{$fa-css-prefix}-s15:before, +.#{$fa-css-prefix}-bath:before { content: $fa-var-bath; } +.#{$fa-css-prefix}-podcast:before { content: $fa-var-podcast; } +.#{$fa-css-prefix}-window-maximize:before { content: $fa-var-window-maximize; } +.#{$fa-css-prefix}-window-minimize:before { content: $fa-var-window-minimize; } +.#{$fa-css-prefix}-window-restore:before { content: $fa-var-window-restore; } +.#{$fa-css-prefix}-times-rectangle:before, +.#{$fa-css-prefix}-window-close:before { content: $fa-var-window-close; } +.#{$fa-css-prefix}-times-rectangle-o:before, +.#{$fa-css-prefix}-window-close-o:before { content: $fa-var-window-close-o; } +.#{$fa-css-prefix}-bandcamp:before { content: $fa-var-bandcamp; } +.#{$fa-css-prefix}-grav:before { content: $fa-var-grav; } +.#{$fa-css-prefix}-etsy:before { content: $fa-var-etsy; } +.#{$fa-css-prefix}-imdb:before { content: $fa-var-imdb; } +.#{$fa-css-prefix}-ravelry:before { content: $fa-var-ravelry; } +.#{$fa-css-prefix}-eercast:before { content: $fa-var-eercast; } +.#{$fa-css-prefix}-microchip:before { content: $fa-var-microchip; } +.#{$fa-css-prefix}-snowflake-o:before { content: $fa-var-snowflake-o; } +.#{$fa-css-prefix}-superpowers:before { content: $fa-var-superpowers; } +.#{$fa-css-prefix}-wpexplorer:before { content: $fa-var-wpexplorer; } +.#{$fa-css-prefix}-meetup:before { content: $fa-var-meetup; } diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_larger.scss b/app/ZnoteAAC/layout/fontawesome/scss/_larger.scss new file mode 100644 index 0000000..41e9a81 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_larger.scss @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.#{$fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.#{$fa-css-prefix}-2x { font-size: 2em; } +.#{$fa-css-prefix}-3x { font-size: 3em; } +.#{$fa-css-prefix}-4x { font-size: 4em; } +.#{$fa-css-prefix}-5x { font-size: 5em; } diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_list.scss b/app/ZnoteAAC/layout/fontawesome/scss/_list.scss new file mode 100644 index 0000000..7d1e4d5 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_list.scss @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.#{$fa-css-prefix}-ul { + padding-left: 0; + margin-left: $fa-li-width; + list-style-type: none; + > li { position: relative; } +} +.#{$fa-css-prefix}-li { + position: absolute; + left: -$fa-li-width; + width: $fa-li-width; + top: (2em / 14); + text-align: center; + &.#{$fa-css-prefix}-lg { + left: -$fa-li-width + (4em / 14); + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_mixins.scss b/app/ZnoteAAC/layout/fontawesome/scss/_mixins.scss new file mode 100644 index 0000000..c3bbd57 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_mixins.scss @@ -0,0 +1,60 @@ +// Mixins +// -------------------------- + +@mixin fa-icon() { + display: inline-block; + font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} + +@mixin fa-icon-rotate($degrees, $rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; + -webkit-transform: rotate($degrees); + -ms-transform: rotate($degrees); + transform: rotate($degrees); +} + +@mixin fa-icon-flip($horiz, $vert, $rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; + -webkit-transform: scale($horiz, $vert); + -ms-transform: scale($horiz, $vert); + transform: scale($horiz, $vert); +} + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +@mixin sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_path.scss b/app/ZnoteAAC/layout/fontawesome/scss/_path.scss new file mode 100644 index 0000000..bb457c2 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_path.scss @@ -0,0 +1,15 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'FontAwesome'; + src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); + src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), + url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), + url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), + url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), + url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); +// src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + font-weight: normal; + font-style: normal; +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_rotated-flipped.scss b/app/ZnoteAAC/layout/fontawesome/scss/_rotated-flipped.scss new file mode 100644 index 0000000..a3558fd --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_rotated-flipped.scss @@ -0,0 +1,20 @@ +// Rotated & Flipped Icons +// ------------------------- + +.#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } +.#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } +.#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } + +.#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } +.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root .#{$fa-css-prefix}-rotate-90, +:root .#{$fa-css-prefix}-rotate-180, +:root .#{$fa-css-prefix}-rotate-270, +:root .#{$fa-css-prefix}-flip-horizontal, +:root .#{$fa-css-prefix}-flip-vertical { + filter: none; +} diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_screen-reader.scss b/app/ZnoteAAC/layout/fontawesome/scss/_screen-reader.scss new file mode 100644 index 0000000..637426f --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_screen-reader.scss @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { @include sr-only(); } +.sr-only-focusable { @include sr-only-focusable(); } diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_stacked.scss b/app/ZnoteAAC/layout/fontawesome/scss/_stacked.scss new file mode 100644 index 0000000..aef7403 --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_stacked.scss @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.#{$fa-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.#{$fa-css-prefix}-stack-1x { line-height: inherit; } +.#{$fa-css-prefix}-stack-2x { font-size: 2em; } +.#{$fa-css-prefix}-inverse { color: $fa-inverse; } diff --git a/app/ZnoteAAC/layout/fontawesome/scss/_variables.scss b/app/ZnoteAAC/layout/fontawesome/scss/_variables.scss new file mode 100644 index 0000000..498fc4a --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/_variables.scss @@ -0,0 +1,800 @@ +// Variables +// -------------------------- + +$fa-font-path: "../fonts" !default; +$fa-font-size-base: 14px !default; +$fa-line-height-base: 1 !default; +//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default; // for referencing Bootstrap CDN font files directly +$fa-css-prefix: fa !default; +$fa-version: "4.7.0" !default; +$fa-border-color: #eee !default; +$fa-inverse: #fff !default; +$fa-li-width: (30em / 14) !default; + +$fa-var-500px: "\f26e"; +$fa-var-address-book: "\f2b9"; +$fa-var-address-book-o: "\f2ba"; +$fa-var-address-card: "\f2bb"; +$fa-var-address-card-o: "\f2bc"; +$fa-var-adjust: "\f042"; +$fa-var-adn: "\f170"; +$fa-var-align-center: "\f037"; +$fa-var-align-justify: "\f039"; +$fa-var-align-left: "\f036"; +$fa-var-align-right: "\f038"; +$fa-var-amazon: "\f270"; +$fa-var-ambulance: "\f0f9"; +$fa-var-american-sign-language-interpreting: "\f2a3"; +$fa-var-anchor: "\f13d"; +$fa-var-android: "\f17b"; +$fa-var-angellist: "\f209"; +$fa-var-angle-double-down: "\f103"; +$fa-var-angle-double-left: "\f100"; +$fa-var-angle-double-right: "\f101"; +$fa-var-angle-double-up: "\f102"; +$fa-var-angle-down: "\f107"; +$fa-var-angle-left: "\f104"; +$fa-var-angle-right: "\f105"; +$fa-var-angle-up: "\f106"; +$fa-var-apple: "\f179"; +$fa-var-archive: "\f187"; +$fa-var-area-chart: "\f1fe"; +$fa-var-arrow-circle-down: "\f0ab"; +$fa-var-arrow-circle-left: "\f0a8"; +$fa-var-arrow-circle-o-down: "\f01a"; +$fa-var-arrow-circle-o-left: "\f190"; +$fa-var-arrow-circle-o-right: "\f18e"; +$fa-var-arrow-circle-o-up: "\f01b"; +$fa-var-arrow-circle-right: "\f0a9"; +$fa-var-arrow-circle-up: "\f0aa"; +$fa-var-arrow-down: "\f063"; +$fa-var-arrow-left: "\f060"; +$fa-var-arrow-right: "\f061"; +$fa-var-arrow-up: "\f062"; +$fa-var-arrows: "\f047"; +$fa-var-arrows-alt: "\f0b2"; +$fa-var-arrows-h: "\f07e"; +$fa-var-arrows-v: "\f07d"; +$fa-var-asl-interpreting: "\f2a3"; +$fa-var-assistive-listening-systems: "\f2a2"; +$fa-var-asterisk: "\f069"; +$fa-var-at: "\f1fa"; +$fa-var-audio-description: "\f29e"; +$fa-var-automobile: "\f1b9"; +$fa-var-backward: "\f04a"; +$fa-var-balance-scale: "\f24e"; +$fa-var-ban: "\f05e"; +$fa-var-bandcamp: "\f2d5"; +$fa-var-bank: "\f19c"; +$fa-var-bar-chart: "\f080"; +$fa-var-bar-chart-o: "\f080"; +$fa-var-barcode: "\f02a"; +$fa-var-bars: "\f0c9"; +$fa-var-bath: "\f2cd"; +$fa-var-bathtub: "\f2cd"; +$fa-var-battery: "\f240"; +$fa-var-battery-0: "\f244"; +$fa-var-battery-1: "\f243"; +$fa-var-battery-2: "\f242"; +$fa-var-battery-3: "\f241"; +$fa-var-battery-4: "\f240"; +$fa-var-battery-empty: "\f244"; +$fa-var-battery-full: "\f240"; +$fa-var-battery-half: "\f242"; +$fa-var-battery-quarter: "\f243"; +$fa-var-battery-three-quarters: "\f241"; +$fa-var-bed: "\f236"; +$fa-var-beer: "\f0fc"; +$fa-var-behance: "\f1b4"; +$fa-var-behance-square: "\f1b5"; +$fa-var-bell: "\f0f3"; +$fa-var-bell-o: "\f0a2"; +$fa-var-bell-slash: "\f1f6"; +$fa-var-bell-slash-o: "\f1f7"; +$fa-var-bicycle: "\f206"; +$fa-var-binoculars: "\f1e5"; +$fa-var-birthday-cake: "\f1fd"; +$fa-var-bitbucket: "\f171"; +$fa-var-bitbucket-square: "\f172"; +$fa-var-bitcoin: "\f15a"; +$fa-var-black-tie: "\f27e"; +$fa-var-blind: "\f29d"; +$fa-var-bluetooth: "\f293"; +$fa-var-bluetooth-b: "\f294"; +$fa-var-bold: "\f032"; +$fa-var-bolt: "\f0e7"; +$fa-var-bomb: "\f1e2"; +$fa-var-book: "\f02d"; +$fa-var-bookmark: "\f02e"; +$fa-var-bookmark-o: "\f097"; +$fa-var-braille: "\f2a1"; +$fa-var-briefcase: "\f0b1"; +$fa-var-btc: "\f15a"; +$fa-var-bug: "\f188"; +$fa-var-building: "\f1ad"; +$fa-var-building-o: "\f0f7"; +$fa-var-bullhorn: "\f0a1"; +$fa-var-bullseye: "\f140"; +$fa-var-bus: "\f207"; +$fa-var-buysellads: "\f20d"; +$fa-var-cab: "\f1ba"; +$fa-var-calculator: "\f1ec"; +$fa-var-calendar: "\f073"; +$fa-var-calendar-check-o: "\f274"; +$fa-var-calendar-minus-o: "\f272"; +$fa-var-calendar-o: "\f133"; +$fa-var-calendar-plus-o: "\f271"; +$fa-var-calendar-times-o: "\f273"; +$fa-var-camera: "\f030"; +$fa-var-camera-retro: "\f083"; +$fa-var-car: "\f1b9"; +$fa-var-caret-down: "\f0d7"; +$fa-var-caret-left: "\f0d9"; +$fa-var-caret-right: "\f0da"; +$fa-var-caret-square-o-down: "\f150"; +$fa-var-caret-square-o-left: "\f191"; +$fa-var-caret-square-o-right: "\f152"; +$fa-var-caret-square-o-up: "\f151"; +$fa-var-caret-up: "\f0d8"; +$fa-var-cart-arrow-down: "\f218"; +$fa-var-cart-plus: "\f217"; +$fa-var-cc: "\f20a"; +$fa-var-cc-amex: "\f1f3"; +$fa-var-cc-diners-club: "\f24c"; +$fa-var-cc-discover: "\f1f2"; +$fa-var-cc-jcb: "\f24b"; +$fa-var-cc-mastercard: "\f1f1"; +$fa-var-cc-paypal: "\f1f4"; +$fa-var-cc-stripe: "\f1f5"; +$fa-var-cc-visa: "\f1f0"; +$fa-var-certificate: "\f0a3"; +$fa-var-chain: "\f0c1"; +$fa-var-chain-broken: "\f127"; +$fa-var-check: "\f00c"; +$fa-var-check-circle: "\f058"; +$fa-var-check-circle-o: "\f05d"; +$fa-var-check-square: "\f14a"; +$fa-var-check-square-o: "\f046"; +$fa-var-chevron-circle-down: "\f13a"; +$fa-var-chevron-circle-left: "\f137"; +$fa-var-chevron-circle-right: "\f138"; +$fa-var-chevron-circle-up: "\f139"; +$fa-var-chevron-down: "\f078"; +$fa-var-chevron-left: "\f053"; +$fa-var-chevron-right: "\f054"; +$fa-var-chevron-up: "\f077"; +$fa-var-child: "\f1ae"; +$fa-var-chrome: "\f268"; +$fa-var-circle: "\f111"; +$fa-var-circle-o: "\f10c"; +$fa-var-circle-o-notch: "\f1ce"; +$fa-var-circle-thin: "\f1db"; +$fa-var-clipboard: "\f0ea"; +$fa-var-clock-o: "\f017"; +$fa-var-clone: "\f24d"; +$fa-var-close: "\f00d"; +$fa-var-cloud: "\f0c2"; +$fa-var-cloud-download: "\f0ed"; +$fa-var-cloud-upload: "\f0ee"; +$fa-var-cny: "\f157"; +$fa-var-code: "\f121"; +$fa-var-code-fork: "\f126"; +$fa-var-codepen: "\f1cb"; +$fa-var-codiepie: "\f284"; +$fa-var-coffee: "\f0f4"; +$fa-var-cog: "\f013"; +$fa-var-cogs: "\f085"; +$fa-var-columns: "\f0db"; +$fa-var-comment: "\f075"; +$fa-var-comment-o: "\f0e5"; +$fa-var-commenting: "\f27a"; +$fa-var-commenting-o: "\f27b"; +$fa-var-comments: "\f086"; +$fa-var-comments-o: "\f0e6"; +$fa-var-compass: "\f14e"; +$fa-var-compress: "\f066"; +$fa-var-connectdevelop: "\f20e"; +$fa-var-contao: "\f26d"; +$fa-var-copy: "\f0c5"; +$fa-var-copyright: "\f1f9"; +$fa-var-creative-commons: "\f25e"; +$fa-var-credit-card: "\f09d"; +$fa-var-credit-card-alt: "\f283"; +$fa-var-crop: "\f125"; +$fa-var-crosshairs: "\f05b"; +$fa-var-css3: "\f13c"; +$fa-var-cube: "\f1b2"; +$fa-var-cubes: "\f1b3"; +$fa-var-cut: "\f0c4"; +$fa-var-cutlery: "\f0f5"; +$fa-var-dashboard: "\f0e4"; +$fa-var-dashcube: "\f210"; +$fa-var-database: "\f1c0"; +$fa-var-deaf: "\f2a4"; +$fa-var-deafness: "\f2a4"; +$fa-var-dedent: "\f03b"; +$fa-var-delicious: "\f1a5"; +$fa-var-desktop: "\f108"; +$fa-var-deviantart: "\f1bd"; +$fa-var-diamond: "\f219"; +$fa-var-digg: "\f1a6"; +$fa-var-dollar: "\f155"; +$fa-var-dot-circle-o: "\f192"; +$fa-var-download: "\f019"; +$fa-var-dribbble: "\f17d"; +$fa-var-drivers-license: "\f2c2"; +$fa-var-drivers-license-o: "\f2c3"; +$fa-var-dropbox: "\f16b"; +$fa-var-drupal: "\f1a9"; +$fa-var-edge: "\f282"; +$fa-var-edit: "\f044"; +$fa-var-eercast: "\f2da"; +$fa-var-eject: "\f052"; +$fa-var-ellipsis-h: "\f141"; +$fa-var-ellipsis-v: "\f142"; +$fa-var-empire: "\f1d1"; +$fa-var-envelope: "\f0e0"; +$fa-var-envelope-o: "\f003"; +$fa-var-envelope-open: "\f2b6"; +$fa-var-envelope-open-o: "\f2b7"; +$fa-var-envelope-square: "\f199"; +$fa-var-envira: "\f299"; +$fa-var-eraser: "\f12d"; +$fa-var-etsy: "\f2d7"; +$fa-var-eur: "\f153"; +$fa-var-euro: "\f153"; +$fa-var-exchange: "\f0ec"; +$fa-var-exclamation: "\f12a"; +$fa-var-exclamation-circle: "\f06a"; +$fa-var-exclamation-triangle: "\f071"; +$fa-var-expand: "\f065"; +$fa-var-expeditedssl: "\f23e"; +$fa-var-external-link: "\f08e"; +$fa-var-external-link-square: "\f14c"; +$fa-var-eye: "\f06e"; +$fa-var-eye-slash: "\f070"; +$fa-var-eyedropper: "\f1fb"; +$fa-var-fa: "\f2b4"; +$fa-var-facebook: "\f09a"; +$fa-var-facebook-f: "\f09a"; +$fa-var-facebook-official: "\f230"; +$fa-var-facebook-square: "\f082"; +$fa-var-fast-backward: "\f049"; +$fa-var-fast-forward: "\f050"; +$fa-var-fax: "\f1ac"; +$fa-var-feed: "\f09e"; +$fa-var-female: "\f182"; +$fa-var-fighter-jet: "\f0fb"; +$fa-var-file: "\f15b"; +$fa-var-file-archive-o: "\f1c6"; +$fa-var-file-audio-o: "\f1c7"; +$fa-var-file-code-o: "\f1c9"; +$fa-var-file-excel-o: "\f1c3"; +$fa-var-file-image-o: "\f1c5"; +$fa-var-file-movie-o: "\f1c8"; +$fa-var-file-o: "\f016"; +$fa-var-file-pdf-o: "\f1c1"; +$fa-var-file-photo-o: "\f1c5"; +$fa-var-file-picture-o: "\f1c5"; +$fa-var-file-powerpoint-o: "\f1c4"; +$fa-var-file-sound-o: "\f1c7"; +$fa-var-file-text: "\f15c"; +$fa-var-file-text-o: "\f0f6"; +$fa-var-file-video-o: "\f1c8"; +$fa-var-file-word-o: "\f1c2"; +$fa-var-file-zip-o: "\f1c6"; +$fa-var-files-o: "\f0c5"; +$fa-var-film: "\f008"; +$fa-var-filter: "\f0b0"; +$fa-var-fire: "\f06d"; +$fa-var-fire-extinguisher: "\f134"; +$fa-var-firefox: "\f269"; +$fa-var-first-order: "\f2b0"; +$fa-var-flag: "\f024"; +$fa-var-flag-checkered: "\f11e"; +$fa-var-flag-o: "\f11d"; +$fa-var-flash: "\f0e7"; +$fa-var-flask: "\f0c3"; +$fa-var-flickr: "\f16e"; +$fa-var-floppy-o: "\f0c7"; +$fa-var-folder: "\f07b"; +$fa-var-folder-o: "\f114"; +$fa-var-folder-open: "\f07c"; +$fa-var-folder-open-o: "\f115"; +$fa-var-font: "\f031"; +$fa-var-font-awesome: "\f2b4"; +$fa-var-fonticons: "\f280"; +$fa-var-fort-awesome: "\f286"; +$fa-var-forumbee: "\f211"; +$fa-var-forward: "\f04e"; +$fa-var-foursquare: "\f180"; +$fa-var-free-code-camp: "\f2c5"; +$fa-var-frown-o: "\f119"; +$fa-var-futbol-o: "\f1e3"; +$fa-var-gamepad: "\f11b"; +$fa-var-gavel: "\f0e3"; +$fa-var-gbp: "\f154"; +$fa-var-ge: "\f1d1"; +$fa-var-gear: "\f013"; +$fa-var-gears: "\f085"; +$fa-var-genderless: "\f22d"; +$fa-var-get-pocket: "\f265"; +$fa-var-gg: "\f260"; +$fa-var-gg-circle: "\f261"; +$fa-var-gift: "\f06b"; +$fa-var-git: "\f1d3"; +$fa-var-git-square: "\f1d2"; +$fa-var-github: "\f09b"; +$fa-var-github-alt: "\f113"; +$fa-var-github-square: "\f092"; +$fa-var-gitlab: "\f296"; +$fa-var-gittip: "\f184"; +$fa-var-glass: "\f000"; +$fa-var-glide: "\f2a5"; +$fa-var-glide-g: "\f2a6"; +$fa-var-globe: "\f0ac"; +$fa-var-google: "\f1a0"; +$fa-var-google-plus: "\f0d5"; +$fa-var-google-plus-circle: "\f2b3"; +$fa-var-google-plus-official: "\f2b3"; +$fa-var-google-plus-square: "\f0d4"; +$fa-var-google-wallet: "\f1ee"; +$fa-var-graduation-cap: "\f19d"; +$fa-var-gratipay: "\f184"; +$fa-var-grav: "\f2d6"; +$fa-var-group: "\f0c0"; +$fa-var-h-square: "\f0fd"; +$fa-var-hacker-news: "\f1d4"; +$fa-var-hand-grab-o: "\f255"; +$fa-var-hand-lizard-o: "\f258"; +$fa-var-hand-o-down: "\f0a7"; +$fa-var-hand-o-left: "\f0a5"; +$fa-var-hand-o-right: "\f0a4"; +$fa-var-hand-o-up: "\f0a6"; +$fa-var-hand-paper-o: "\f256"; +$fa-var-hand-peace-o: "\f25b"; +$fa-var-hand-pointer-o: "\f25a"; +$fa-var-hand-rock-o: "\f255"; +$fa-var-hand-scissors-o: "\f257"; +$fa-var-hand-spock-o: "\f259"; +$fa-var-hand-stop-o: "\f256"; +$fa-var-handshake-o: "\f2b5"; +$fa-var-hard-of-hearing: "\f2a4"; +$fa-var-hashtag: "\f292"; +$fa-var-hdd-o: "\f0a0"; +$fa-var-header: "\f1dc"; +$fa-var-headphones: "\f025"; +$fa-var-heart: "\f004"; +$fa-var-heart-o: "\f08a"; +$fa-var-heartbeat: "\f21e"; +$fa-var-history: "\f1da"; +$fa-var-home: "\f015"; +$fa-var-hospital-o: "\f0f8"; +$fa-var-hotel: "\f236"; +$fa-var-hourglass: "\f254"; +$fa-var-hourglass-1: "\f251"; +$fa-var-hourglass-2: "\f252"; +$fa-var-hourglass-3: "\f253"; +$fa-var-hourglass-end: "\f253"; +$fa-var-hourglass-half: "\f252"; +$fa-var-hourglass-o: "\f250"; +$fa-var-hourglass-start: "\f251"; +$fa-var-houzz: "\f27c"; +$fa-var-html5: "\f13b"; +$fa-var-i-cursor: "\f246"; +$fa-var-id-badge: "\f2c1"; +$fa-var-id-card: "\f2c2"; +$fa-var-id-card-o: "\f2c3"; +$fa-var-ils: "\f20b"; +$fa-var-image: "\f03e"; +$fa-var-imdb: "\f2d8"; +$fa-var-inbox: "\f01c"; +$fa-var-indent: "\f03c"; +$fa-var-industry: "\f275"; +$fa-var-info: "\f129"; +$fa-var-info-circle: "\f05a"; +$fa-var-inr: "\f156"; +$fa-var-instagram: "\f16d"; +$fa-var-institution: "\f19c"; +$fa-var-internet-explorer: "\f26b"; +$fa-var-intersex: "\f224"; +$fa-var-ioxhost: "\f208"; +$fa-var-italic: "\f033"; +$fa-var-joomla: "\f1aa"; +$fa-var-jpy: "\f157"; +$fa-var-jsfiddle: "\f1cc"; +$fa-var-key: "\f084"; +$fa-var-keyboard-o: "\f11c"; +$fa-var-krw: "\f159"; +$fa-var-language: "\f1ab"; +$fa-var-laptop: "\f109"; +$fa-var-lastfm: "\f202"; +$fa-var-lastfm-square: "\f203"; +$fa-var-leaf: "\f06c"; +$fa-var-leanpub: "\f212"; +$fa-var-legal: "\f0e3"; +$fa-var-lemon-o: "\f094"; +$fa-var-level-down: "\f149"; +$fa-var-level-up: "\f148"; +$fa-var-life-bouy: "\f1cd"; +$fa-var-life-buoy: "\f1cd"; +$fa-var-life-ring: "\f1cd"; +$fa-var-life-saver: "\f1cd"; +$fa-var-lightbulb-o: "\f0eb"; +$fa-var-line-chart: "\f201"; +$fa-var-link: "\f0c1"; +$fa-var-linkedin: "\f0e1"; +$fa-var-linkedin-square: "\f08c"; +$fa-var-linode: "\f2b8"; +$fa-var-linux: "\f17c"; +$fa-var-list: "\f03a"; +$fa-var-list-alt: "\f022"; +$fa-var-list-ol: "\f0cb"; +$fa-var-list-ul: "\f0ca"; +$fa-var-location-arrow: "\f124"; +$fa-var-lock: "\f023"; +$fa-var-long-arrow-down: "\f175"; +$fa-var-long-arrow-left: "\f177"; +$fa-var-long-arrow-right: "\f178"; +$fa-var-long-arrow-up: "\f176"; +$fa-var-low-vision: "\f2a8"; +$fa-var-magic: "\f0d0"; +$fa-var-magnet: "\f076"; +$fa-var-mail-forward: "\f064"; +$fa-var-mail-reply: "\f112"; +$fa-var-mail-reply-all: "\f122"; +$fa-var-male: "\f183"; +$fa-var-map: "\f279"; +$fa-var-map-marker: "\f041"; +$fa-var-map-o: "\f278"; +$fa-var-map-pin: "\f276"; +$fa-var-map-signs: "\f277"; +$fa-var-mars: "\f222"; +$fa-var-mars-double: "\f227"; +$fa-var-mars-stroke: "\f229"; +$fa-var-mars-stroke-h: "\f22b"; +$fa-var-mars-stroke-v: "\f22a"; +$fa-var-maxcdn: "\f136"; +$fa-var-meanpath: "\f20c"; +$fa-var-medium: "\f23a"; +$fa-var-medkit: "\f0fa"; +$fa-var-meetup: "\f2e0"; +$fa-var-meh-o: "\f11a"; +$fa-var-mercury: "\f223"; +$fa-var-microchip: "\f2db"; +$fa-var-microphone: "\f130"; +$fa-var-microphone-slash: "\f131"; +$fa-var-minus: "\f068"; +$fa-var-minus-circle: "\f056"; +$fa-var-minus-square: "\f146"; +$fa-var-minus-square-o: "\f147"; +$fa-var-mixcloud: "\f289"; +$fa-var-mobile: "\f10b"; +$fa-var-mobile-phone: "\f10b"; +$fa-var-modx: "\f285"; +$fa-var-money: "\f0d6"; +$fa-var-moon-o: "\f186"; +$fa-var-mortar-board: "\f19d"; +$fa-var-motorcycle: "\f21c"; +$fa-var-mouse-pointer: "\f245"; +$fa-var-music: "\f001"; +$fa-var-navicon: "\f0c9"; +$fa-var-neuter: "\f22c"; +$fa-var-newspaper-o: "\f1ea"; +$fa-var-object-group: "\f247"; +$fa-var-object-ungroup: "\f248"; +$fa-var-odnoklassniki: "\f263"; +$fa-var-odnoklassniki-square: "\f264"; +$fa-var-opencart: "\f23d"; +$fa-var-openid: "\f19b"; +$fa-var-opera: "\f26a"; +$fa-var-optin-monster: "\f23c"; +$fa-var-outdent: "\f03b"; +$fa-var-pagelines: "\f18c"; +$fa-var-paint-brush: "\f1fc"; +$fa-var-paper-plane: "\f1d8"; +$fa-var-paper-plane-o: "\f1d9"; +$fa-var-paperclip: "\f0c6"; +$fa-var-paragraph: "\f1dd"; +$fa-var-paste: "\f0ea"; +$fa-var-pause: "\f04c"; +$fa-var-pause-circle: "\f28b"; +$fa-var-pause-circle-o: "\f28c"; +$fa-var-paw: "\f1b0"; +$fa-var-paypal: "\f1ed"; +$fa-var-pencil: "\f040"; +$fa-var-pencil-square: "\f14b"; +$fa-var-pencil-square-o: "\f044"; +$fa-var-percent: "\f295"; +$fa-var-phone: "\f095"; +$fa-var-phone-square: "\f098"; +$fa-var-photo: "\f03e"; +$fa-var-picture-o: "\f03e"; +$fa-var-pie-chart: "\f200"; +$fa-var-pied-piper: "\f2ae"; +$fa-var-pied-piper-alt: "\f1a8"; +$fa-var-pied-piper-pp: "\f1a7"; +$fa-var-pinterest: "\f0d2"; +$fa-var-pinterest-p: "\f231"; +$fa-var-pinterest-square: "\f0d3"; +$fa-var-plane: "\f072"; +$fa-var-play: "\f04b"; +$fa-var-play-circle: "\f144"; +$fa-var-play-circle-o: "\f01d"; +$fa-var-plug: "\f1e6"; +$fa-var-plus: "\f067"; +$fa-var-plus-circle: "\f055"; +$fa-var-plus-square: "\f0fe"; +$fa-var-plus-square-o: "\f196"; +$fa-var-podcast: "\f2ce"; +$fa-var-power-off: "\f011"; +$fa-var-print: "\f02f"; +$fa-var-product-hunt: "\f288"; +$fa-var-puzzle-piece: "\f12e"; +$fa-var-qq: "\f1d6"; +$fa-var-qrcode: "\f029"; +$fa-var-question: "\f128"; +$fa-var-question-circle: "\f059"; +$fa-var-question-circle-o: "\f29c"; +$fa-var-quora: "\f2c4"; +$fa-var-quote-left: "\f10d"; +$fa-var-quote-right: "\f10e"; +$fa-var-ra: "\f1d0"; +$fa-var-random: "\f074"; +$fa-var-ravelry: "\f2d9"; +$fa-var-rebel: "\f1d0"; +$fa-var-recycle: "\f1b8"; +$fa-var-reddit: "\f1a1"; +$fa-var-reddit-alien: "\f281"; +$fa-var-reddit-square: "\f1a2"; +$fa-var-refresh: "\f021"; +$fa-var-registered: "\f25d"; +$fa-var-remove: "\f00d"; +$fa-var-renren: "\f18b"; +$fa-var-reorder: "\f0c9"; +$fa-var-repeat: "\f01e"; +$fa-var-reply: "\f112"; +$fa-var-reply-all: "\f122"; +$fa-var-resistance: "\f1d0"; +$fa-var-retweet: "\f079"; +$fa-var-rmb: "\f157"; +$fa-var-road: "\f018"; +$fa-var-rocket: "\f135"; +$fa-var-rotate-left: "\f0e2"; +$fa-var-rotate-right: "\f01e"; +$fa-var-rouble: "\f158"; +$fa-var-rss: "\f09e"; +$fa-var-rss-square: "\f143"; +$fa-var-rub: "\f158"; +$fa-var-ruble: "\f158"; +$fa-var-rupee: "\f156"; +$fa-var-s15: "\f2cd"; +$fa-var-safari: "\f267"; +$fa-var-save: "\f0c7"; +$fa-var-scissors: "\f0c4"; +$fa-var-scribd: "\f28a"; +$fa-var-search: "\f002"; +$fa-var-search-minus: "\f010"; +$fa-var-search-plus: "\f00e"; +$fa-var-sellsy: "\f213"; +$fa-var-send: "\f1d8"; +$fa-var-send-o: "\f1d9"; +$fa-var-server: "\f233"; +$fa-var-share: "\f064"; +$fa-var-share-alt: "\f1e0"; +$fa-var-share-alt-square: "\f1e1"; +$fa-var-share-square: "\f14d"; +$fa-var-share-square-o: "\f045"; +$fa-var-shekel: "\f20b"; +$fa-var-sheqel: "\f20b"; +$fa-var-shield: "\f132"; +$fa-var-ship: "\f21a"; +$fa-var-shirtsinbulk: "\f214"; +$fa-var-shopping-bag: "\f290"; +$fa-var-shopping-basket: "\f291"; +$fa-var-shopping-cart: "\f07a"; +$fa-var-shower: "\f2cc"; +$fa-var-sign-in: "\f090"; +$fa-var-sign-language: "\f2a7"; +$fa-var-sign-out: "\f08b"; +$fa-var-signal: "\f012"; +$fa-var-signing: "\f2a7"; +$fa-var-simplybuilt: "\f215"; +$fa-var-sitemap: "\f0e8"; +$fa-var-skyatlas: "\f216"; +$fa-var-skype: "\f17e"; +$fa-var-slack: "\f198"; +$fa-var-sliders: "\f1de"; +$fa-var-slideshare: "\f1e7"; +$fa-var-smile-o: "\f118"; +$fa-var-snapchat: "\f2ab"; +$fa-var-snapchat-ghost: "\f2ac"; +$fa-var-snapchat-square: "\f2ad"; +$fa-var-snowflake-o: "\f2dc"; +$fa-var-soccer-ball-o: "\f1e3"; +$fa-var-sort: "\f0dc"; +$fa-var-sort-alpha-asc: "\f15d"; +$fa-var-sort-alpha-desc: "\f15e"; +$fa-var-sort-amount-asc: "\f160"; +$fa-var-sort-amount-desc: "\f161"; +$fa-var-sort-asc: "\f0de"; +$fa-var-sort-desc: "\f0dd"; +$fa-var-sort-down: "\f0dd"; +$fa-var-sort-numeric-asc: "\f162"; +$fa-var-sort-numeric-desc: "\f163"; +$fa-var-sort-up: "\f0de"; +$fa-var-soundcloud: "\f1be"; +$fa-var-space-shuttle: "\f197"; +$fa-var-spinner: "\f110"; +$fa-var-spoon: "\f1b1"; +$fa-var-spotify: "\f1bc"; +$fa-var-square: "\f0c8"; +$fa-var-square-o: "\f096"; +$fa-var-stack-exchange: "\f18d"; +$fa-var-stack-overflow: "\f16c"; +$fa-var-star: "\f005"; +$fa-var-star-half: "\f089"; +$fa-var-star-half-empty: "\f123"; +$fa-var-star-half-full: "\f123"; +$fa-var-star-half-o: "\f123"; +$fa-var-star-o: "\f006"; +$fa-var-steam: "\f1b6"; +$fa-var-steam-square: "\f1b7"; +$fa-var-step-backward: "\f048"; +$fa-var-step-forward: "\f051"; +$fa-var-stethoscope: "\f0f1"; +$fa-var-sticky-note: "\f249"; +$fa-var-sticky-note-o: "\f24a"; +$fa-var-stop: "\f04d"; +$fa-var-stop-circle: "\f28d"; +$fa-var-stop-circle-o: "\f28e"; +$fa-var-street-view: "\f21d"; +$fa-var-strikethrough: "\f0cc"; +$fa-var-stumbleupon: "\f1a4"; +$fa-var-stumbleupon-circle: "\f1a3"; +$fa-var-subscript: "\f12c"; +$fa-var-subway: "\f239"; +$fa-var-suitcase: "\f0f2"; +$fa-var-sun-o: "\f185"; +$fa-var-superpowers: "\f2dd"; +$fa-var-superscript: "\f12b"; +$fa-var-support: "\f1cd"; +$fa-var-table: "\f0ce"; +$fa-var-tablet: "\f10a"; +$fa-var-tachometer: "\f0e4"; +$fa-var-tag: "\f02b"; +$fa-var-tags: "\f02c"; +$fa-var-tasks: "\f0ae"; +$fa-var-taxi: "\f1ba"; +$fa-var-telegram: "\f2c6"; +$fa-var-television: "\f26c"; +$fa-var-tencent-weibo: "\f1d5"; +$fa-var-terminal: "\f120"; +$fa-var-text-height: "\f034"; +$fa-var-text-width: "\f035"; +$fa-var-th: "\f00a"; +$fa-var-th-large: "\f009"; +$fa-var-th-list: "\f00b"; +$fa-var-themeisle: "\f2b2"; +$fa-var-thermometer: "\f2c7"; +$fa-var-thermometer-0: "\f2cb"; +$fa-var-thermometer-1: "\f2ca"; +$fa-var-thermometer-2: "\f2c9"; +$fa-var-thermometer-3: "\f2c8"; +$fa-var-thermometer-4: "\f2c7"; +$fa-var-thermometer-empty: "\f2cb"; +$fa-var-thermometer-full: "\f2c7"; +$fa-var-thermometer-half: "\f2c9"; +$fa-var-thermometer-quarter: "\f2ca"; +$fa-var-thermometer-three-quarters: "\f2c8"; +$fa-var-thumb-tack: "\f08d"; +$fa-var-thumbs-down: "\f165"; +$fa-var-thumbs-o-down: "\f088"; +$fa-var-thumbs-o-up: "\f087"; +$fa-var-thumbs-up: "\f164"; +$fa-var-ticket: "\f145"; +$fa-var-times: "\f00d"; +$fa-var-times-circle: "\f057"; +$fa-var-times-circle-o: "\f05c"; +$fa-var-times-rectangle: "\f2d3"; +$fa-var-times-rectangle-o: "\f2d4"; +$fa-var-tint: "\f043"; +$fa-var-toggle-down: "\f150"; +$fa-var-toggle-left: "\f191"; +$fa-var-toggle-off: "\f204"; +$fa-var-toggle-on: "\f205"; +$fa-var-toggle-right: "\f152"; +$fa-var-toggle-up: "\f151"; +$fa-var-trademark: "\f25c"; +$fa-var-train: "\f238"; +$fa-var-transgender: "\f224"; +$fa-var-transgender-alt: "\f225"; +$fa-var-trash: "\f1f8"; +$fa-var-trash-o: "\f014"; +$fa-var-tree: "\f1bb"; +$fa-var-trello: "\f181"; +$fa-var-tripadvisor: "\f262"; +$fa-var-trophy: "\f091"; +$fa-var-truck: "\f0d1"; +$fa-var-try: "\f195"; +$fa-var-tty: "\f1e4"; +$fa-var-tumblr: "\f173"; +$fa-var-tumblr-square: "\f174"; +$fa-var-turkish-lira: "\f195"; +$fa-var-tv: "\f26c"; +$fa-var-twitch: "\f1e8"; +$fa-var-twitter: "\f099"; +$fa-var-twitter-square: "\f081"; +$fa-var-umbrella: "\f0e9"; +$fa-var-underline: "\f0cd"; +$fa-var-undo: "\f0e2"; +$fa-var-universal-access: "\f29a"; +$fa-var-university: "\f19c"; +$fa-var-unlink: "\f127"; +$fa-var-unlock: "\f09c"; +$fa-var-unlock-alt: "\f13e"; +$fa-var-unsorted: "\f0dc"; +$fa-var-upload: "\f093"; +$fa-var-usb: "\f287"; +$fa-var-usd: "\f155"; +$fa-var-user: "\f007"; +$fa-var-user-circle: "\f2bd"; +$fa-var-user-circle-o: "\f2be"; +$fa-var-user-md: "\f0f0"; +$fa-var-user-o: "\f2c0"; +$fa-var-user-plus: "\f234"; +$fa-var-user-secret: "\f21b"; +$fa-var-user-times: "\f235"; +$fa-var-users: "\f0c0"; +$fa-var-vcard: "\f2bb"; +$fa-var-vcard-o: "\f2bc"; +$fa-var-venus: "\f221"; +$fa-var-venus-double: "\f226"; +$fa-var-venus-mars: "\f228"; +$fa-var-viacoin: "\f237"; +$fa-var-viadeo: "\f2a9"; +$fa-var-viadeo-square: "\f2aa"; +$fa-var-video-camera: "\f03d"; +$fa-var-vimeo: "\f27d"; +$fa-var-vimeo-square: "\f194"; +$fa-var-vine: "\f1ca"; +$fa-var-vk: "\f189"; +$fa-var-volume-control-phone: "\f2a0"; +$fa-var-volume-down: "\f027"; +$fa-var-volume-off: "\f026"; +$fa-var-volume-up: "\f028"; +$fa-var-warning: "\f071"; +$fa-var-wechat: "\f1d7"; +$fa-var-weibo: "\f18a"; +$fa-var-weixin: "\f1d7"; +$fa-var-whatsapp: "\f232"; +$fa-var-wheelchair: "\f193"; +$fa-var-wheelchair-alt: "\f29b"; +$fa-var-wifi: "\f1eb"; +$fa-var-wikipedia-w: "\f266"; +$fa-var-window-close: "\f2d3"; +$fa-var-window-close-o: "\f2d4"; +$fa-var-window-maximize: "\f2d0"; +$fa-var-window-minimize: "\f2d1"; +$fa-var-window-restore: "\f2d2"; +$fa-var-windows: "\f17a"; +$fa-var-won: "\f159"; +$fa-var-wordpress: "\f19a"; +$fa-var-wpbeginner: "\f297"; +$fa-var-wpexplorer: "\f2de"; +$fa-var-wpforms: "\f298"; +$fa-var-wrench: "\f0ad"; +$fa-var-xing: "\f168"; +$fa-var-xing-square: "\f169"; +$fa-var-y-combinator: "\f23b"; +$fa-var-y-combinator-square: "\f1d4"; +$fa-var-yahoo: "\f19e"; +$fa-var-yc: "\f23b"; +$fa-var-yc-square: "\f1d4"; +$fa-var-yelp: "\f1e9"; +$fa-var-yen: "\f157"; +$fa-var-yoast: "\f2b1"; +$fa-var-youtube: "\f167"; +$fa-var-youtube-play: "\f16a"; +$fa-var-youtube-square: "\f166"; + diff --git a/app/ZnoteAAC/layout/fontawesome/scss/font-awesome.scss b/app/ZnoteAAC/layout/fontawesome/scss/font-awesome.scss new file mode 100644 index 0000000..f1c83aa --- /dev/null +++ b/app/ZnoteAAC/layout/fontawesome/scss/font-awesome.scss @@ -0,0 +1,18 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables"; +@import "mixins"; +@import "path"; +@import "core"; +@import "larger"; +@import "fixed-width"; +@import "list"; +@import "bordered-pulled"; +@import "animated"; +@import "rotated-flipped"; +@import "stacked"; +@import "icons"; +@import "screen-reader"; diff --git a/app/ZnoteAAC/layout/img/atomio_front.jpg b/app/ZnoteAAC/layout/img/atomio_front.jpg new file mode 100644 index 0000000..c834537 Binary files /dev/null and b/app/ZnoteAAC/layout/img/atomio_front.jpg differ diff --git a/app/ZnoteAAC/layout/img/atomio_profile.jpg b/app/ZnoteAAC/layout/img/atomio_profile.jpg new file mode 100644 index 0000000..e697437 Binary files /dev/null and b/app/ZnoteAAC/layout/img/atomio_profile.jpg differ diff --git a/app/ZnoteAAC/layout/img/bg.png b/app/ZnoteAAC/layout/img/bg.png new file mode 100644 index 0000000..5c283a8 Binary files /dev/null and b/app/ZnoteAAC/layout/img/bg.png differ diff --git a/app/ZnoteAAC/layout/img/guild_default.jpg b/app/ZnoteAAC/layout/img/guild_default.jpg new file mode 100644 index 0000000..078247b Binary files /dev/null and b/app/ZnoteAAC/layout/img/guild_default.jpg differ diff --git a/app/ZnoteAAC/layout/img/header.png b/app/ZnoteAAC/layout/img/header.png new file mode 100644 index 0000000..a2e852e Binary files /dev/null and b/app/ZnoteAAC/layout/img/header.png differ diff --git a/app/ZnoteAAC/layout/img/mainbg.jpg b/app/ZnoteAAC/layout/img/mainbg.jpg new file mode 100644 index 0000000..e5aa4aa Binary files /dev/null and b/app/ZnoteAAC/layout/img/mainbg.jpg differ diff --git a/app/ZnoteAAC/layout/img/modern.png b/app/ZnoteAAC/layout/img/modern.png new file mode 100644 index 0000000..337f92f Binary files /dev/null and b/app/ZnoteAAC/layout/img/modern.png differ diff --git a/app/ZnoteAAC/layout/img/prev1.png b/app/ZnoteAAC/layout/img/prev1.png new file mode 100644 index 0000000..ba6e22f Binary files /dev/null and b/app/ZnoteAAC/layout/img/prev1.png differ diff --git a/app/ZnoteAAC/layout/img/prev2.png b/app/ZnoteAAC/layout/img/prev2.png new file mode 100644 index 0000000..6a3b0af Binary files /dev/null and b/app/ZnoteAAC/layout/img/prev2.png differ diff --git a/app/ZnoteAAC/layout/img/preview.png b/app/ZnoteAAC/layout/img/preview.png new file mode 100644 index 0000000..2b8f23c Binary files /dev/null and b/app/ZnoteAAC/layout/img/preview.png differ diff --git a/app/ZnoteAAC/layout/img/sm_icons.png b/app/ZnoteAAC/layout/img/sm_icons.png new file mode 100644 index 0000000..05537a9 Binary files /dev/null and b/app/ZnoteAAC/layout/img/sm_icons.png differ diff --git a/app/ZnoteAAC/layout/index.php b/app/ZnoteAAC/layout/index.php new file mode 100644 index 0000000..aece377 --- /dev/null +++ b/app/ZnoteAAC/layout/index.php @@ -0,0 +1,253 @@ + "https://www.twitter.com/", + "facebook" => "https://www.facebook.com/", + "youtube" => "https://www.youtube.com/", + "twitch" => "https://www.twitch.tv/" + ); + $url_param = "/?subtopic="; + + /* + isNew -- determines wether the page is new or not, set to _true_ if it is a new page + + isPage -- if the menu item is not a page but a link to a file to be downloaded, set to _false_ + + */ + $menu_items = array( + "Main" => array( + "fontIcon" => "home", + "Latest News" => array("latestnews", "isNew" => false, "isPage" => true), + "News Archive" => array("newsarchive", "isNew" => false, "isPage" => true), + "Report Bug(s)" => array("reportbug", "isNew" => true, "isPage" => true) + ), + "Account" => array( + "fontIcon" => "user-circle", + "My Account" => array("accountmanagement", "isNew" => false, "isPage" => true), + "Create Account" => array("createaccount", "isNew" => false, "isPage" => true), + "Downloads" => array("downloads", "isNew" => false, "isPage" => true), + "Recover Password" => array("loastaccount", "isNew" => true, "isPage" => true) + ), + "Community" => array( + "fontIcon" => "users", + "Characters" => array("characters", "isNew" => false, "isPage" => true), + "Who is online" => array("whoisonline", "isNew" => false, "isPage" => true), + "Highscores" => array("highscores", "isNew" => false, "isPage" => true), + "Houses" => array("houses", "isNew" => false, "isPage" => true), + "Latest Kills" => array("killstatistics", "isNew" => false, "isPage" => true), + "Guilds" => array("guilds", "isNew" => false, "isPage" => true) + ), + "Library" => array( + "fontIcon" => "book", + "Server Rules" => array("tibiarules", "isNew" => false, "isPage" => true), + "Server Info" => array("serverinfo", "isNew" => false, "isPage" => true), + "Exp Table" => array("experiencetable", "isNew" => true, "isPage" => true) + ), + "Support" => array( + "fontIcon" => "info-circle", + "Team" => array("team", "isNew" => false, "isPage" => true), + "testDLFILE" => array("file.txt", "isNew" => true, "isPage" => false) + ), + "Shop" => array( + "fontIcon" => "shopping-cart", + "Donate" => array("donate", "isNew" => true, "isPage" => true), + "Buy Points" => array("buypoints", "isNew" => false, "isPage" => true), + "Items" => array("shopoffer", "isNew" => false, "isPage" => true) + ) + ); + + $countDown = "Apr 5, 2019 15:37:25"; +?> + + + + + OTS Title | Layout + + + + + + + + + + + + + + +
    + + + + +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +
    +
    +
    + test +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    +
    + +
    +
    +
    + test +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    +
    + + +
    + + + +
    + +
    +
    + Login +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + Events +
    +
    + + + + + + +
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    Event Name 2h 5m 10s
    +
    +
    + +
    +
    + Top 10 Players +
    +
    + + + + + + + + + + + + +
    #Name
    1Name
    2Name
    3Name
    4Name
    5Name
    6Name
    7Name
    8Name
    9Name
    10Name
    +
    +
    +
    + +
    + + +
    + + + \ No newline at end of file diff --git a/app/ZnoteAAC/layout/js/countdown.js b/app/ZnoteAAC/layout/js/countdown.js new file mode 100644 index 0000000..ef0540c --- /dev/null +++ b/app/ZnoteAAC/layout/js/countdown.js @@ -0,0 +1,50 @@ +function countDown(elid, seconds, msg){ + // Set the date we're counting down to + var countDownDate = new Date(); + countDownDate.setSeconds(countDownDate.getSeconds() + seconds); + var countDownDate = countDownDate.getTime(); + + // Update the count down every 1 second + window.countDownInterval = setInterval(function() { + + // Get todays date and time + var now = new Date().getTime(); + + // Find the distance between now and the count down date + var distance = countDownDate - now; + + // Time calculations for days, hours, minutes and seconds + var days = Math.floor(distance / (1000 * 60 * 60 * 24)); + var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((distance % (1000 * 60)) / 1000); + + // Display the result in the element with id="demo" + document.getElementById(elid).innerHTML = "Server starts in: "+ days + "d " + hours + "h " + minutes + "m " + seconds + "s "; + + // If the count down is finished, write some text + if (distance < 0) { + clearInterval(window.countDownInterval); + document.getElementById(elid).innerHTML = msg; + } + }, 1000); + + // Get todays date and time + var now = new Date().getTime(); + + // Find the distance between now and the count down date + var distance = countDownDate - now; + + // Time calculations for days, hours, minutes and seconds + var days = Math.floor(distance / (1000 * 60 * 60 * 24)); + var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((distance % (1000 * 60)) / 1000); + + // Display the result in the element with id="demo" + document.getElementById(elid).innerHTML = "Server starts in: "+ days + "d " + hours + "h " + minutes + "m " + seconds + "s "; + + if (distance < 0) { + document.getElementById(elid).innerHTML = msg; + } +} \ No newline at end of file diff --git a/app/ZnoteAAC/layout/js/jquery.js b/app/ZnoteAAC/layout/js/jquery.js new file mode 100644 index 0000000..b061403 --- /dev/null +++ b/app/ZnoteAAC/layout/js/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 false, + "twitter" => "https://www.twitter.com/", + "facebook" => "https://www.facebook.com/", + "youtube" => "https://www.youtube.com/", + "twitch" => "https://www.twitch.tv/" + ); + + // Use same date format when changing: yyyy-mm-dd hh:mm + $countDown = "2020-06-10 01:00"; + + // Hide countdown after 1 day (24 hours) after countDown + $countDown_hide = 1 * 24 * 60 * 60; + + // Say this after countdown, and before the row is hidden + $countDown_complete = "ONLINE"; +?> diff --git a/app/ZnoteAAC/layout/menu.php b/app/ZnoteAAC/layout/menu.php new file mode 100644 index 0000000..07c864a --- /dev/null +++ b/app/ZnoteAAC/layout/menu.php @@ -0,0 +1,65 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/layout/overall/footer.php b/app/ZnoteAAC/layout/overall/footer.php new file mode 100644 index 0000000..1dc3a81 --- /dev/null +++ b/app/ZnoteAAC/layout/overall/footer.php @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/app/ZnoteAAC/layout/overall/header.php b/app/ZnoteAAC/layout/overall/header.php new file mode 100644 index 0000000..5ffd878 --- /dev/null +++ b/app/ZnoteAAC/layout/overall/header.php @@ -0,0 +1,65 @@ + + + + + + <?php echo $config['site_title']; ?> + + + + + + + + + + 0): ?> + + + + + + 0) echo " class='page_{$page_filename}'"; ?>> + + +
    + + + + +
    + 0): ?> +
    +
    +
    +
    +
    + + +
    diff --git a/app/ZnoteAAC/layout/sub.php b/app/ZnoteAAC/layout/sub.php new file mode 100644 index 0000000..74a6c9f --- /dev/null +++ b/app/ZnoteAAC/layout/sub.php @@ -0,0 +1,49 @@ + array( + 'file' => 'fileName.php', + 'override' => false + ), + ................ + There are 2 ways to view your page, by using sub.php file, or by overriding an existing default page. + 1: yourwebiste.com/sub.php?page=PAGENAME + 2: By having override => true, then it will load your sub file instead of the default znote aac file. + +*/ + +$subpages = array( + // website.com/sub.php?page=blank + 'blank' => array( + // layout/sub/blank.php + 'file' => 'blank.php', + // false means don't run this file instead of the regular file at website.com/blank.php + 'override' => false + ), + 'recover' => array( + 'file' => 'recover.php', + 'override' => false + ), + 'search' => array( + 'file' => 'search.php', + 'override' => false + ), + 'houses' => array( + 'file' => 'houses.php', + 'override' => false + ), + 'downloads' => array( + 'file' => 'downloads.php', + 'override' => false + ), + 'loginhelp' => array( + 'file' => 'loginhelp.php', + 'override' => false + ) +); +?> diff --git a/app/ZnoteAAC/layout/sub/index.php b/app/ZnoteAAC/layout/sub/index.php new file mode 100644 index 0000000..da31421 --- /dev/null +++ b/app/ZnoteAAC/layout/sub/index.php @@ -0,0 +1,146 @@ +useMemory(false); + $changelogs = $changelogCache->load(); + + if (isset($changelogs) && !empty($changelogs) && $changelogs !== false) { + ?> +
    + + + + + + + + + + +
    Latest Changelog Updates (Click here to see full changelog)
    +
    + hasExpired()) { + $news = fetchAllNews(); + $cache->setContent($news); + $cache->save(); +} else { + $news = $cache->load(); +} + +// Design and present the list +if ($news) { + + $total_news = count($news); + $row_news = $total_news / $config['news_per_page']; + $page_amount = ceil($total_news / $config['news_per_page']); + $current = $config['news_per_page'] * $page; + + function TransformToBBCode($string) { + $tags = array( + '[center]{$1}[/center]' => '
    $1
    ', + '[b]{$1}[/b]' => '$1', + '[size={$1}]{$2}[/size]' => '$2', + '[img]{$1}[/img]' => 'image', + '[link]{$1}[/link]' => '$1', + '[link={$1}]{$2}[/link]' => '$2', + '[color={$1}]{$2}[/color]' => '$2', + '[*]{$1}[/*]' => '
  • $1
  • ', + '[youtube]{$1}[/youtube]' => '
    ', + ); + foreach ($tags as $tag => $value) { + $code = preg_replace('/placeholder([0-9]+)/', '(.*?)', preg_quote(preg_replace('/\{\$([0-9]+)\}/', 'placeholder$1', $tag), '/')); + $string = preg_replace('/'.$code.'/i', $value, $string); + } + return $string; + } + + if ($view !== "") { // We want to view a specific news post + $si = false; + if (ctype_digit($view) === false) { + for ($i = 0; $i < count($news); $i++) if ($view === urlencode($news[$i]['title'])) $si = $i; + } else { + for ($i = 0; $i < count($news); $i++) if ((int)$view === (int)$news[$i]['id']) $si = $i; + } + + if ($si !== false) { + echo "hello world!"; + ?> +
    +
    +
    + [#'.$news[$si]['id'].'] '. getClock($news[$si]['date'], true) .' by '. $news[$si]['name'] .' - '. TransformToBBCode($news[$si]['title']) .''; ?> +
    +
    +

    +
    +
    +
    + + + + + + + + + +
    News post not found.
    +

    We failed to find the post you where looking for.

    +
    + +
    +
    +
    + '.getClock($news[$i]['date'], true).' by '. $news[$i]['name'] .' - '. TransformToBBCode($news[$i]['title']) .''; ?> +
    +
    +

    +
    +
    +
    + '; + + for ($i = 0; $i < $page_amount; $i++) { + + if ($i == $page) { + + echo ''; + + } else { + + echo ''; + } + } + + echo ''; + + } + +} else { + echo '

    No news exist.

    '; +} +?> diff --git a/app/ZnoteAAC/layout/sub/loginhelp.php b/app/ZnoteAAC/layout/sub/loginhelp.php new file mode 100644 index 0000000..86e11d2 --- /dev/null +++ b/app/ZnoteAAC/layout/sub/loginhelp.php @@ -0,0 +1,19 @@ + +

    Login

    +

    Please fill the login form in this aside bar to login. →

    + + \ No newline at end of file diff --git a/app/ZnoteAAC/layout/sub/recover.php b/app/ZnoteAAC/layout/sub/recover.php new file mode 100644 index 0000000..a259862 --- /dev/null +++ b/app/ZnoteAAC/layout/sub/recover.php @@ -0,0 +1,10 @@ +
    +
    +
    + Lost Account +
    +
    + Have you lost your username, or your password? +
    +
    +
    \ No newline at end of file diff --git a/app/ZnoteAAC/layout/sub/search.php b/app/ZnoteAAC/layout/sub/search.php new file mode 100644 index 0000000..f71b617 --- /dev/null +++ b/app/ZnoteAAC/layout/sub/search.php @@ -0,0 +1,13 @@ +
    +
    +
    + Character search +
    +
    +
    + + +
    +
    +
    +
    \ No newline at end of file diff --git a/app/ZnoteAAC/layout/widgets/admin.php b/app/ZnoteAAC/layout/widgets/admin.php new file mode 100644 index 0000000..6da4b93 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/admin.php @@ -0,0 +1,58 @@ +
    +
    + Administration +
    +
    +
      +
    • + Admin Page +
    • +
    • + Admin News +
    • +
    • + Admin Gallery +
    • +
    • + Admin Skills +
    • +
    • + Admin Reports +
    • +
    • + Admin Helpdesk +
    • +
    • + Admin Shop +
    • +
    • + Admin Auction +
    • + '1';"); + + foreach($threads as $thread) { + $response = false; + $posts = mysql_select_multi("SELECT `id`, `player_id` FROM `znote_forum_posts` WHERE `thread_id`='". $thread['id'] ."';"); + if ($posts !== false) { + foreach($posts as $post) { + foreach ($staffs as $staff) { + if ($post['player_id'] == $staff['id']) $response = true; + } + } + } + + if (!$response) $new++; + } + } + ?> +
    • + Feedback: [] new +
    • +
    +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/charactersearch.php b/app/ZnoteAAC/layout/widgets/charactersearch.php new file mode 100644 index 0000000..312d899 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/charactersearch.php @@ -0,0 +1,62 @@ +
    +
    + Character search +
    +
    +
    +
    + +
    + Luxitur +
    +
    +
    +
    + +
    + hasExpired()) { + $names_sql = mysql_select_multi('SELECT `name` FROM `players` ORDER BY `name` ASC;'); + $names = array(); + if ($names_sql !== false): foreach ($names_sql as $name) { + $names[] = $name['name']; + } endif; + $cache->setContent($names); + $cache->save(); + } else { + $names = $cache->load(); + } + ?> + +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/highscore.php b/app/ZnoteAAC/layout/widgets/highscore.php new file mode 100644 index 0000000..d330a85 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/highscore.php @@ -0,0 +1,21 @@ +
    +
    + View Highscores +
    +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/app/ZnoteAAC/layout/widgets/houses.php b/app/ZnoteAAC/layout/widgets/houses.php new file mode 100644 index 0000000..20e02c0 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/houses.php @@ -0,0 +1,20 @@ +
    +
    + Town list / houses +
    +
    +
    "> + + + +
    +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/login.php b/app/ZnoteAAC/layout/widgets/login.php new file mode 100644 index 0000000..08b60e2 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/login.php @@ -0,0 +1,31 @@ +
    +
    + Login / Register +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    New account

    +

    Lost username or password?

    +
    +
    +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/myaccount.php b/app/ZnoteAAC/layout/widgets/myaccount.php new file mode 100644 index 0000000..e90afe5 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/myaccount.php @@ -0,0 +1,24 @@ +
    +
    + Welcome, . +
    +
    + +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/serverinfo.php b/app/ZnoteAAC/layout/widgets/serverinfo.php new file mode 100644 index 0000000..324c5d8 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/serverinfo.php @@ -0,0 +1,34 @@ +
    +
    + Server Information +
    +
    +
      +
      Server Offline!

      "; + $status = false; + } + else { + $info = chr(6).chr(0).chr(255).chr(255).'info'; + fwrite($sock, $info); + $data=''; + while (!feof($sock))$data .= fgets($sock, 1024); + fclose($sock); + echo "
      Server Online!

      "; + } + } + if ($status) { + ?> +
    • Players online: +
    • + +
    • Registered accounts:
    • +
    +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/topplayers.php b/app/ZnoteAAC/layout/widgets/topplayers.php new file mode 100644 index 0000000..3ce32cf --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/topplayers.php @@ -0,0 +1,27 @@ +
    +
    + Top 5 players +
    +
    + + hasExpired()) { + $players = mysql_select_multi('SELECT `name`, `level`, `experience` FROM `players` WHERE `group_id` < ' . $config['highscore']['ignoreGroupId'] . ' ORDER BY `level` DESC, `experience` DESC LIMIT 5;'); + + $cache->setContent($players); + $cache->save(); + } else { + $players = $cache->load(); + } + + if ($players) { + foreach($players as $count => $player) { + $nr = $count+1; + echo ""; + } + } + ?> +
    {$nr}{$player['name']} ({$player['level']}).
    +
    +
    diff --git a/app/ZnoteAAC/layout/widgets/vote.php b/app/ZnoteAAC/layout/widgets/vote.php new file mode 100644 index 0000000..38dd033 --- /dev/null +++ b/app/ZnoteAAC/layout/widgets/vote.php @@ -0,0 +1,11 @@ +
    +
    + Vote for us! +
    +
    +
    + Get points by voting at OTServers.eu + +
    +
    +
    \ No newline at end of file diff --git a/app/ZnoteAAC/login.php b/app/ZnoteAAC/login.php new file mode 100644 index 0000000..954b7f1 --- /dev/null +++ b/app/ZnoteAAC/login.php @@ -0,0 +1,414 @@ + $code, 'errorMessage' => $message)); + //error_log("\nServer = " . $response . "\n-"); + die($response); + } + + function sendMessage($message) { + $response = json_encode($message); + //error_log("\nServer = " . $response . "\n\n-"); + die($response); + } + + + header("Content-Type: application/json"); + $input = file_get_contents("php://input"); + //error_log("\n\n\nClient = " . $input . "\n"); + + $client = json_decode($input); + + if (!isset($client->type)) { + sendError("Type missing."); + } + + switch($client->type) { + // {"count":0,"isreturner":true,"offset":0,"showrewardnews":false,"type":"news"} + case "cacheinfo": + // {"type":"cacheinfo"} + sendMessage(array( + 'playersonline' => (int)user_count_online(), + 'twitchstreams' => 0, + 'twitchviewer' => 0, + 'gamingyoutubestreams' => 0, + 'gamingyoutubeviewer' => 0 + )); + break; + + case 'eventschedule': + // {"type":"eventschedule"} + $eventlist = []; + $file_path = $config['server_path'] . 'data/XML/events.xml'; + /* + + + + + +
    + + + + + +
    + + + */ + if (!file_exists($file_path)) { + sendMessage(array( + 'eventlist' => array() + )); + } + $xml = new DOMDocument; + $xml->load($file_path); + $tableevent = $xml->getElementsByTagName('event'); + + if (!function_exists("parseEvent")) { + function parseEvent($table1, $date, $table2) { + if ($table1) { + if ($date) { + if ($table2) { + $date = $table1->getAttribute('startdate'); + return date_create("{$date}")->format('U'); + } else { + $date = $table1->getAttribute('enddate'); + return date_create("{$date}")->format('U'); + } + } else { + foreach($table1 as $attr) { + if ($attr) { + return $attr->getAttribute($table2); + } + } + } + } + return; + } + } + + foreach ($tableevent as $event) { + if ($event) { + $eventlist[] = array( + 'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'), + 'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'), + 'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'), + 'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')), + 'enddate' => intval(parseEvent($event, true, false)), + 'isseasonal' => (intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal')) == 1) ? true : false, + 'name' => $event->getAttribute('name'), + 'startdate' => intval(parseEvent($event, true, true)), + 'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent')) + ); + } + } + + sendMessage(array( + 'eventlist' => $eventlist, + 'lastupdatetimestamp' => time() + )); + break; + + case 'boostedcreature': + // {"type":"boostedcreature"} + sendMessage(array( + //'boostedcreature' => false, + 'raceid' => 219 + )); + break; + + case 'news': + // {"count":0,"isreturner":true,"offset":0,"showrewardnews":false,"type":"news"} + sendMessage(array( + 'gamenews' => array(), // element structure? + 'categorycounts' => array( + 'support' => 1, + 'game contents' => 2, + 'useful info' => 3, + 'major updates' => 4, + 'client features' => 5 + ), + 'maxeditdate' => 1590979202 + )); + break; + + case "login": + /* { + 'accountname' => 'username', + "email":"my@email.com", + 'password' => 'superpass', + 'stayloggedin' => true, + 'token' => '123123', (or not set) + 'type' => 'login', + } */ + + $email = (isset($client->email)) ? sanitize($client->email) : false; + $username = (isset($client->accountname)) ? sanitize($client->accountname) : false; + $password = SHA1($client->password); + $token = (isset($client->token)) ? sanitize($client->token) : false; + + $fields = '`id`, `premium_ends_at`'; + if ($config['twoFactorAuthenticator']) $fields .= ', `secret`'; + + $account = false; + + if ($email !== false) { + $fields .= ', `name`'; + $account = mysql_select_single("SELECT {$fields} FROM `accounts` WHERE `email`='{$email}' AND `password`='{$password}' LIMIT 1;"); + if ($account !== false) { + $username = $account['name']; + } + } elseif ($username !== false) { + $account = mysql_select_single("SELECT {$fields} FROM `accounts` WHERE `name`='{$username}' AND `password`='{$password}' LIMIT 1;"); + } + + if ($account === false) { + sendError('Wrong username and/or password.'); + } + + if ($config['twoFactorAuthenticator'] === true && $account['secret'] !== null) { + if ($token === false) { + sendError('Submit a valid two-factor authentication token.', 6); + } else { + require_once("engine/function/rfc6238.php"); + if (TokenAuth6238::verify($account['secret'], $token) !== true) { + sendError('Two-factor authentication failed, token is wrong.', 6); + } + } + } + + $players = mysql_select_multi("SELECT `name`, `sex`, `level`, `vocation`, `lookbody`, `looktype`, `lookhead`, `looklegs`, `lookfeet`, `lookaddons`, `deletion` FROM `players` WHERE `account_id`='".$account['id']."';"); + if ($players !== false) { + + $gameserver = $config['gameserver']; + // Override $config['gameserver'] if server has installed Lua script for loginWebService + $sql_elements = mysql_select_multi(" + SELECT + `key`, + `value` + FROM `znote_global_storage` + WHERE `key` IN('SERVER_NAME', 'IP', 'GAME_PORT') + "); + if ($sql_elements !== false) { + foreach ($sql_elements AS $element) { + switch ($element['key']) { + case 'SERVER_NAME': + $gameserver['name'] = $element['value']; + break; + case 'IP': + $gameserver['ip'] = $element['value']; + break; + case 'GAME_PORT': + $gameserver['port'] = (int)$element['value']; + break; + } + } + } + + $sessionKey = ($email !== false) ? $email."\n".$client->password : $username."\n".$client->password; + $sessionKey .= (isset($account['secret']) && strlen($account['secret']) > 5) ? "\n".$token : "\n"; + $sessionKey .= "\n".floor(time() / 30); + + $freePremium = (isset($config['freePremium'])) ? $config['freePremium'] : true; + $response = array( + 'session' => array( + 'fpstracking' => false, + 'optiontracking' => false, + 'isreturner' => true, + 'returnernotification' => false, + 'showrewardnews' => false, + 'tournamentticketpurchasestate' => 0, + 'emailcoderequest' => false, + 'sessionkey' => $sessionKey, + 'lastlogintime' => 0, + 'ispremium' => ($account['premium_ends_at'] > time() || $freePremium) ? true : false, + 'premiumuntil' => $account['premium_ends_at'], + 'status' => 'active' + ), + 'playdata' => array( + 'worlds' => array( + array( + 'id' => 0, + 'name' => $gameserver['name'], + 'externaladdress' => $gameserver['ip'], + 'externalport' => $gameserver['port'], + 'previewstate' => 0, + 'location' => 'ALL', + // 0 - open pvp + // 1 - optional + // 2 - hardcore + // 3 - retro open pvp + // 4 - retro hardcore pvp + // 5 and higher - (unknown) + 'pvptype' => 0, + 'externaladdressunprotected' => $gameserver['ip'], + 'externaladdressprotected' => $gameserver['ip'], + 'externalportunprotected' => $gameserver['port'], + 'externalportprotected' => $gameserver['port'], + 'istournamentworld' => false, + 'restrictedstore' => false, + 'currenttournamentphase' => 2, + 'anticheatprotection' => false + ) + ), + 'characters' => array( + //array( 'worldid' => ASD, 'name' => asd, 'ismale' => true, 'tutorial' => false ), + ) + ) + ); + + foreach ($players as $player) { + $response['playdata']['characters'][] = array( + 'worldid' => 0, + 'name' => $player['name'], + 'ismale' => ($player['sex'] === 1) ? true : false, + 'tutorial' => false, + 'level' => intval($player['level']), + 'vocation' => vocation_id_to_name($player['vocation']), + 'outfitid' => intval($player['looktype']), + 'headcolor' => intval($player['lookhead']), + 'torsocolor' => intval($player['lookbody']), + 'legscolor' => intval($player['looklegs']), + 'detailcolor' => intval($player['lookfeet']), + 'addonsflags' => intval($player['lookaddons']), + 'ishidden' => intval($player['deletion']) === 1, + 'istournamentparticipant' => false, + 'remainingdailytournamentplaytime' => 0 + ); + } + + sendMessage($response); + } else { + sendError("Character list is empty."); + } + break; + + default: + sendError("Unsupported type: " . sanitize($client->type)); + } + +} // End client 11 loginWebService + +logged_in_redirect(); +include 'layout/overall/header.php'; + +if (empty($_POST) === false) { + + if ($config['log_ip']) { + znote_visitor_insert_detailed_data(5); + } + + $username = $_POST['username']; + $password = $_POST['password']; + + if (empty($username) || empty($password)) { + $errors[] = 'You need to enter a username and password.'; + } else if (strlen($username) > 32 || strlen($password) > 64) { + $errors[] = 'Username or password is too long.'; + } else if (user_exist($username) === false) { + $errors[] = 'Failed to authorize your account, are the details correct, have you registered?'; + } /*else if (user_activated($username) === false) { + $errors[] = 'You havent activated your account! Please check your email.
    Note it may appear in your junk/spam box.'; + } */else if ($config['use_token'] && !Token::isValid($_POST['token'])) { + Token::debug($_POST['token']); + $errors[] = 'Token is invalid.'; + } else { + + // Starting loging + if ($config['ServerEngine'] == 'TFS_02' || $config['ServerEngine'] == 'OTHIRE' || $config['ServerEngine'] == 'TFS_10') $login = user_login($username, $password); + else if ($config['ServerEngine'] == 'TFS_03') $login = user_login_03($username, $password); + else $login = false; + if ($login === false) { + $errors[] = 'Username and password combination is wrong.'; + } else { + // Check if user have access to login + $status = false; + if ($config['mailserver']['register']) { + $authenticate = mysql_select_single("SELECT `id` FROM `znote_accounts` WHERE `account_id`='$login' AND `active`='1' LIMIT 1;"); + if ($authenticate !== false) { + $status = true; + } else { + $errors[] = "Your account is not activated. An email should have been sent to you when you registered. Please find it and click the activation link to activate your account."; + } + } else $status = true; + + if ($status) { + // Regular login success, now lets check authentication token code + if ($config['ServerEngine'] == 'TFS_10' && $config['twoFactorAuthenticator']) { + require_once("engine/function/rfc6238.php"); + + // Two factor authentication code / token + $authcode = (isset($_POST['authcode'])) ? getValue($_POST['authcode']) : false; + + // Load secret values from db + $query = mysql_select_single("SELECT `a`.`secret` AS `secret`, `za`.`secret` AS `znote_secret` FROM `accounts` AS `a` INNER JOIN `znote_accounts` AS `za` ON `a`.`id` = `za`.`account_id` WHERE `a`.`id`='".(int)$login."' LIMIT 1;"); + + // If account table HAS a secret, we need to validate it + if ($query['secret'] !== NULL) { + + // Validate the secret first to make sure all is good. + if (TokenAuth6238::verify($query['secret'], $authcode) !== true) { + $errors[] = "Submitted Two-Factor Authentication token is wrong."; + $errors[] = "Make sure to type the correct token from your mobile authenticator."; + $status = false; + } + + } else { + + // secret from accounts table is null/not set. Perhaps we can activate it: + if ($query['znote_secret'] !== NULL && $authcode !== false && !empty($authcode)) { + + // Validate the secret first to make sure all is good. + if (TokenAuth6238::verify($query['znote_secret'], $authcode)) { + // Success, enable the 2FA system + mysql_update("UPDATE `accounts` SET `secret`= '".$query['znote_secret']."' WHERE `id`='$login';"); + } else { + $errors[] = "Activating Two-Factor authentication failed."; + $errors[] = "Try to login without token and configure your app properly."; + $errors[] = "Submitted Two-Factor Authentication token is wrong."; + $errors[] = "Make sure to type the correct token from your mobile authenticator."; + $status = false; + } + } + } + } // End tfs 1.0+ with 2FA auth + + if ($status) { + setSession('user_id', $login); + + // if IP is not set (etc acc created before Znote AAC was in use) + $znote_data = user_znote_account_data($login, 'ip'); + if ($znote_data['ip'] == 0) { + $update_data = array( + 'ip' => getIPLong(), + ); + user_update_znote_account($update_data); + } + + // Send them to myaccount.php + header('Location: myaccount.php'); + exit(); + } + } + } + } +} else { + header('Location: index.php'); +} + +if (empty($errors) === false) { + ?> +

    We tried to log you in, but...

    + diff --git a/app/ZnoteAAC/logout.php b/app/ZnoteAAC/logout.php new file mode 100644 index 0000000..8312f34 --- /dev/null +++ b/app/ZnoteAAC/logout.php @@ -0,0 +1,8 @@ + diff --git a/app/ZnoteAAC/mailtest.php b/app/ZnoteAAC/mailtest.php new file mode 100644 index 0000000..6eb98b4 --- /dev/null +++ b/app/ZnoteAAC/mailtest.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/app/ZnoteAAC/market.php b/app/ZnoteAAC/market.php new file mode 100644 index 0000000..5ec17a8 --- /dev/null +++ b/app/ZnoteAAC/market.php @@ -0,0 +1,220 @@ + +

    Marketplace

    +

    Failed to load item list.

    +

    Attempted to load this file:

    +

    Configure correct 'server_path' in config.php.

    +

    If the path is correct, make sure your web user has access to read it.

    + setExpiration(60); + if ($cache->hasExpired()) { + $offers = array( + 'wts' => mysql_select_multi("SELECT `mo`.`id`, `mo`.`itemtype` AS `item_id`, `mo`.`amount`, `mo`.`price`, `mo`.`created`, `mo`.`anonymous`, `p`.`name` AS `player_name` FROM `market_offers` AS `mo` INNER JOIN `players` AS `p` ON `mo`.`player_id`=`p`.`id` WHERE `mo`.`sale` = '1' ORDER BY `mo`.`created` DESC;"), + 'wtb' => mysql_select_multi("SELECT `mo`.`id`, `mo`.`itemtype` AS `item_id`, `mo`.`amount`, `mo`.`price`, `mo`.`created`, `mo`.`anonymous`, `p`.`name` AS `player_name` FROM `market_offers` AS `mo` INNER JOIN `players` AS `p` ON `mo`.`player_id`=`p`.`id` WHERE `mo`.`sale` = '0' ORDER BY `mo`.`created` DESC;") + ); + $cache->setContent($offers); + $cache->save(); + } else { + $offers = $cache->load(); + } + ?> +

    Marketplace

    +

    You can buy and sell items by clicking on the market in depot.
    To sell an item: Place item inside your depot, click on market, search for your item and sell it.

    + +

    WTS: Want to sell

    + + + + + + + + + + + + + + + + + + + + + +
    Item nameItemCountPrice for 1AddedByCompare
    " alt="Item Image">".$o['player_name'].""; ?>
    +

    WTB: Want to buy

    + + + + + + + + + + + + + + + + + + + + + +
    Item nameItemCountPrice for 1AddedByCompare
    " alt="Item Image">".$o['player_name'].""; ?>
    + 0) ? (int)$compare : getValue($compare); + + $condition = "`itemtype`='$compare'"; + + if (is_string($compare)) { + $query = array(); + foreach ($items as $id => $name) { + if (strpos(strtolower($name), stripslashes(strtolower($compare))) !== false) { + $query[] = $id; + } + } + $condition = (!empty($query)) ? "`itemtype` IN (". implode(',', $query) .")" : false; + } + + // First list active bids + if ($condition === false) { + $offers = array(); + $historyOffers = array(); + } else { + $offers = mysql_select_multi("SELECT `mo`.`id`, `mo`.`sale`, `mo`.`itemtype` AS `item_id`, `mo`.`amount`, `mo`.`price`, `mo`.`created`, `mo`.`anonymous`, `p`.`name` AS `player_name` FROM `market_offers` AS `mo` INNER JOIN `players` AS `p` ON `mo`.`player_id`=`p`.`id` WHERE `mo`.$condition ORDER BY `mo`.`price` ASC;"); + $historyOffers = mysql_select_multi("SELECT `id`, `itemtype` AS `item_id`, `amount`, `price`, `inserted`, `expires_at` FROM `market_history` WHERE $condition AND `state`='255' ORDER BY `price` ASC;"); + } + $buylist = false; + + // Markup + $itemname = (isset($items[$compare])) ? $items[$compare] : $compare; + if (!is_string($compare)) echo "

    Comparing item: ". $itemname ."

    "; + else echo "

    Search: ". stripslashes($compare) ."

    "; + ?> + +

    Active offers

    + + + + + + + + + + + + + + + + + + + +
    Item nameItemCountPrice for 1AddedBy
    " alt="Item Image">".$o['player_name'].""; ?>
    + +

    Want to buy:

    + + + + + + + + + + + + + + + + + + + +
    Item nameItemCountPrice for 1AddedBy
    " alt="Item Image">".$o['player_name'].""; ?>
    + +

    Old purchased offers

    + + + + + + + + + + + + + + + + + +
    Item nameItemCountPrice for 1Offer sold
    " alt="Item Image">
    + diff --git a/app/ZnoteAAC/monster_loot.php b/app/ZnoteAAC/monster_loot.php new file mode 100644 index 0000000..50aec30 --- /dev/null +++ b/app/ZnoteAAC/monster_loot.php @@ -0,0 +1,100 @@ + + + 7, + 'Semi Rare' => 2, + 'Rare' => 0.5, + 'Very Rare' => 0 +); +?> + + +Hide None | '; + echo 'Hide Not Found | '; + echo 'Hide Monsters Without Loot | '; + echo 'Hide All | '; + echo 'Use Normal Loot Rate | '; + echo 'Use Server Loot Rate'; +?> +

    + +Could not load items!'); + foreach($items->item as $v) + $itemList[(int)$v['id']] = $v['name']; + + if(isset($_GET['lootrate'])) { + $config = parse_ini_file($otdir . '/config.lua'); + $lootRate = $config['rate_loot']; + } + + $monsters = simplexml_load_file($otdir . '/data/monster/monsters.xml') or die('Could not load monsters!'); + foreach($monsters->monster as $monster) { + $loot = simplexml_load_file($otdir . '/data/monster/' . $monster['file']); + if($loot) { + if($item = $loot->loot->item) { + echo ' + [+] ' . $monster['name'] . ' +
    '; + } elseif(!isset($_GET['hideempty'])) + echo '[x] ' . $monster['name'] . '
    '; + } elseif(!isset($_GET['hidefail'])) + echo 'Failed to load monster ' . $monster[name] . ' (' . $monster[file] . ')
    '; + } + +function addLoot($loot, $level=1) { + foreach($loot as $test) { + $chance = $test['chance']; + if(!$chance) + $chance = $test['chance1']; + + printLoot($level, $test['id'], $test['countmax'], $chance); + foreach($test as $k => $v) + addLoot($v->item, $level + 1); + } +} + +function printLoot($level, $itemid, $count, $chance) { + global $itemList, $rarity; + + $chance /= 1000; + if(isset($_GET['lootrate'])) { + global $lootRate; + $chance *= $lootRate; + } + + foreach($rarity as $lootRarity => $percent){ + if($chance >= $percent) { + echo str_repeat("... ", $level) . '' . ($count ? $count : 1) . ' ' . $itemList[(int)$itemid] . ' - ' . $lootRarity . ' (' . $chance . '%)
    '; + break; + } + } +} +?> + diff --git a/app/ZnoteAAC/myaccount.php b/app/ZnoteAAC/myaccount.php new file mode 100644 index 0000000..f2cdcbd --- /dev/null +++ b/app/ZnoteAAC/myaccount.php @@ -0,0 +1,436 @@ +'; + } +} +#endregion + +// Variable used to check if main page should be rendered after handling POST (Change comment page) +$render_page = true; + +// Handle GET (verify email) +if (isset($_GET['authenticate']) && $config['mailserver']['myaccount_verify_email']): + // If we need to process email verification + if (isset($_GET['u']) && isset($_GET['k'])) { + // Authenticate user, fetch user id and activation key + $auid = (isset($_GET['u']) && (int)$_GET['u'] > 0) ? (int)$_GET['u'] : false; + $akey = (isset($_GET['k']) && (int)$_GET['k'] > 0) ? (int)$_GET['k'] : false; + if ($auid !== false && $akey !== false) { + // Find a match + $user = mysql_select_single("SELECT `id`, `active`, `active_email` FROM `znote_accounts` WHERE `account_id`='{$auid}' AND `activekey`='{$akey}' LIMIT 1;"); + if ($user !== false) { + $user = (int) $user['id']; + $active = (int) $user['active']; + $active_email = (int) $user['active_email']; + $verify_points = ($active_email == 0 && $config['mailserver']['verify_email_points'] > 0) + ? ", `points` = `points` + {$config['mailserver']['verify_email_points']}" + : ''; + // Enable the account to login + if ($active == 0 || $active_email == 0) { + $new_activeKey = rand(100000000, 999999999); + mysql_update("UPDATE `znote_accounts` SET `active`='1', `active_email`='1', `activekey`='{$new_activeKey}' {$verify_points} WHERE `id`= {$user} LIMIT 1;"); + } + echo '

    Congratulations!

    Your email has been verified.

    '; + if ($verify_points !== '') echo "

    As thanks for having a verified email, you have received {$config['mailserver']['verify_email_points']} shop points!

    "; + $user_znote_data['active_email'] = 1; + // Todo: Bonus points as thanks for verifying email + } else { + echo '

    Authentication failed

    Either the activation link is wrong, or your account is already activated.

    '; + } + } else { + echo '

    Authentication failed

    Either the activation link is wrong, or your account is already activated.

    '; + } + } else { // We need to send email verification + $verify_account_id = (int)$session_user_id; + $user = mysql_select_single("SELECT `id`, `activekey`, `active_email` FROM `znote_accounts` WHERE `account_id`='{$verify_account_id}' LIMIT 1;"); + if ($user !== false) { + $thisurl = config('site_url') . "myaccount.php"; + $thisurl .= "?authenticate&u=".$verify_account_id."&k=".$user['activekey']; + + $mailer = new Mail($config['mailserver']); + + $title = "Please authenticate your email at {$_SERVER['HTTP_HOST']}."; + + $body = "

    Please click on the following link to authenticate your account:

    "; + $body .= "

    {$thisurl}

    "; + $body .= "

    Thank you for verifying your email and enjoy your stay at {$config['mailserver']['fromName']}.

    "; + $body .= "

    I am an automatic no-reply e-mail. Any emails sent back to me will be ignored.

    "; + + $user_name = ($config['ServerEngine'] !== 'OTHIRE') ? $user_data['name'] : $user_data['id']; + //echo "

    " . $title . "

    " . $body; + $mailer->sendMail($user_data['email'], $title, $body, $user_name); + ?> +

    Email authentication sent

    +

    We have sent you an email with a verification link to your email address:

    +

    If you can't find the email within 5 minutes, check your junk/trash inbox (spam filter) as it may be misplaced there.

    + Authentication failed

    Failed to verify user when trying to send a verification email.

    '; + } + } +endif; + +// Handle POST +if (!empty($_POST['selected_character'])) { + if (!empty($_POST['action'])) { + // Validate token + if (!Token::isValid($_POST['token'])) { + exit(); + } + // Sanitize values + $action = getValue($_POST['action']); + $char_name = getValue($_POST['selected_character']); + + // Handle actions + switch($action) { + // Change character comment PAGE2 (Success). + case 'update_comment': + if (user_character_account_id($char_name) === $session_user_id) { + user_update_comment(user_character_id($char_name), getValue($_POST['comment'])); + echo 'Successfully updated comment.'; + } + break; + // end + + // Hide character + case 'toggle_hide': + $hide = (user_character_hide($char_name) == 1 ? 0 : 1); + if (user_character_account_id($char_name) === $session_user_id) { + user_character_set_hide(user_character_id($char_name), $hide); + } + break; + // end + + // DELETE character + case 'delete_character': + if (user_character_account_id($char_name) === $session_user_id) { + $charid = user_character_id($char_name); + if ($charid !== false) { + if ($config['ServerEngine'] === 'TFS_10') { + if (!user_is_online_10($charid)) { + if (guild_leader_gid($charid) === false) user_delete_character_soft($charid); + else echo 'Character is leader of a guild, you must disband the guild or change leadership before deleting character.'; + } else echo 'Character must be offline first.'; + } else { + $chr_data = user_character_data($charid, 'online'); + if ($chr_data['online'] != 1) { + if (guild_leader_gid($charid) === false) user_delete_character_soft($charid); + else echo 'Character is leader of a guild, you must disband the guild or change leadership before deleting character.'; + } else echo 'Character must be offline first.'; + } + } + } + break; + // end + + // CHANGE character name + case 'change_name': + $oldname = $char_name; + $newname = isset($_POST['newName']) ? getValue($_POST['newName']) : ''; + + $player = false; + if ($config['ServerEngine'] === 'TFS_10') { + $player = mysql_select_single("SELECT `id`, `account_id` FROM `players` WHERE `name` = '$oldname'"); + $player['online'] = (user_is_online_10($player['id'])) ? 1 : 0; + } else $player = mysql_select_single("SELECT `id`, `account_id`, `online` FROM `players` WHERE `name` = '$oldname'"); + + // Check if user is online + if ($player['online'] == 1) { + $errors[] = 'Character must be offline first.'; + } + + // Check if player has bough ticket + $accountId = $player['account_id']; + $order = mysql_select_single("SELECT `id`, `account_id` FROM `znote_shop_orders` WHERE `type`='4' AND `account_id` = '$accountId' LIMIT 1;"); + if ($order === false) { + $errors[] = 'Did not find any name change tickets, buy them in our shop!'; + } + + // Check if player and account matches + if ($session_user_id != $accountId || $session_user_id != $order['account_id']) { + if (empty($errors)) { + $errors[] = 'Failed to sync your account. :|'; + } + } + + $newname = validate_name($newname); + if ($newname === false) { + $errors[] = 'Your name can not contain more than 2 words.'; + } else { + if (empty($newname)) { + $errors[] = 'Please enter a name!'; + } else if (user_character_exist($newname) !== false) { + $errors[] = 'Sorry, that character name already exist.'; + } else if (!preg_match("/^[a-zA-Z_ ]+$/", $newname)) { + $errors[] = 'Your name may only contain a-z, A-Z and spaces.'; + } else if (strlen($newname) < $config['minL'] || strlen($newname) > $config['maxL']) { + $errors[] = 'Your character name must be between ' . $config['minL'] . ' - ' . $config['maxL'] . ' characters long.'; + } else if (!ctype_upper($newname[0])) { + $errors[] = 'The first letter of a name has to be a capital letter!'; + } + + // name restriction + $resname = explode(" ", $_POST['newName']); + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } else if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + } + + if (!empty($newname) && empty($errors)) { + echo 'You have successfully changed your character name to ' . $newname . '.'; + mysql_update("UPDATE `players` SET `name`='$newname' WHERE `id`='".$player['id']."' LIMIT 1;"); + mysql_delete("DELETE FROM `znote_shop_orders` WHERE `id`='".$order['id']."' LIMIT 1;"); + + } else if (!empty($errors)) { + echo ''; + echo output_errors($errors); + echo ''; + } + + break; + // end + + // Change character sex + case 'change_gender': + if (user_character_account_id($char_name) === $session_user_id) { + $char_id = (int)user_character_id($char_name); + $account_id = user_character_account_id($char_name); + + if ($config['ServerEngine'] == 'TFS_10') { + $chr_data['online'] = user_is_online_10($char_id) ? 1 : 0; + } else $chr_data = user_character_data($char_id, 'online'); + if ($chr_data['online'] != 1) { + // Verify that we are not messing around with data + if ($account_id != $user_data['id']) die("wtf? Something went wrong, try relogging."); + + // Fetch character tickets + $tickets = shop_account_gender_tickets($account_id); + if ($tickets !== false || $config['free_sex_change'] == true) { + // They are allowed to change gender + $last = false; + $infinite = false; + $tks = 0; + // Do we have any infinite tickets? + foreach ($tickets as $ticket) { + if ($ticket['count'] == 0) $infinite = true; + else if ($ticket > 0 && $infinite === false) $tks += (int)$ticket['count']; + } + if ($infinite === true) $tks = 0; + $dbid = (int)$tickets[0]['id']; + // If they dont have unlimited tickets, remove a count from their ticket. + if ($tickets[0]['count'] > 1) { // Decrease count + $tks--; + $tkr = ((int)$tickets[0]['count'] - 1); + shop_update_row_count($dbid, $tkr); + } else if ($tickets[0]['count'] == 1) { // Delete record + shop_delete_row_order($dbid); + $tks--; + } + + // Change character gender: + // + user_character_change_gender($char_name); + echo 'You have successfully changed gender on character '. $char_name .'.'; + if ($tks > 0) echo '
    You have '. $tks .' gender change tickets left.'; + else if ($infinite !== true) echo '
    You are out of tickets.'; + } else echo 'You don\'t have any character gender tickets, buy them in the SHOP!'; + } else echo 'Your character must be offline.'; + } + break; + // end + + // Change character comment PAGE1: + case 'change_comment': + $render_page = false; // Regular "myaccount" page should not render + if (user_character_account_id($char_name) === $session_user_id) { + $comment_data = user_znote_character_data(user_character_id($char_name), 'comment'); + ?> + +

    Change comment on:

    +
    +
      +
    • + + +
    • +
    • + Comment:
      + +
    • + +
    • +
    +
    + new DateTime()) + echo 'CAUTION! Your character with name ' . $delete['character_name'] . ' will be deleted on ' . $delete['time'] . '. Cancel this operation.
    '; + else { + user_delete_character(user_character_id($delete['character_name'])); + mysql_update('UPDATE `znote_deleted_characters` SET `done` = 1 WHERE `id` = '. $delete['id']. ''); + echo 'Character ' . $delete['character_name'] . ' has been deleted. This operation was requested by owner of this account.'; + $char_count--; + } + } + } + + ?> +
    +

    My account

    +

    Welcome to your account page,
    +
    Email: (Verified).
    Your email is not verified! Please verify it. +

    +

    Account security with Two-factor Authentication:

    +

    Character List: characters.

    + + + + + + '; + echo ''; + echo ''; + $characters[] = $value['name']; + } + ?> +
    NAMELEVELVOCATIONTOWNLAST LOGINSTATUSHIDE
    '. $value['name'] .''. $value['level'] .''. $value['vocation'] .''. $value['town_id'] .''. $value['lastlogin'] .''. $value['online'] .''. hide_char_to_name(user_character_hide($value['name'])) .'
    + +
    + + + + + + +
    + + + + + + +
    +
    + create one?'; + } + ?> +
    + + + + diff --git a/app/ZnoteAAC/onlinelist.php b/app/ZnoteAAC/onlinelist.php new file mode 100644 index 0000000..d03369a --- /dev/null +++ b/app/ZnoteAAC/onlinelist.php @@ -0,0 +1,74 @@ + + +

    Who is online?

    +setExpiration(30); +if ($cache->hasExpired()) { + // Load online list data from SQL + if ($config['ServerEngine'] == 'TFS_10') { + $array = ($loadFlags === true) ? mysql_select_multi("SELECT `p`.`name` AS `name`, `p`.`level` AS `level`, `p`.`vocation` AS `vocation`, `g`.`name` AS `gname`, `za`.`flag` AS `flag` $outfitQuery FROM `players_online` AS `o` INNER JOIN `players` AS `p` ON `o`.`player_id` = `p`.`id` INNER JOIN `znote_accounts` AS `za` ON `p`.`account_id` = `za`.`account_id` LEFT JOIN `guild_membership` AS `gm` ON `o`.`player_id` = `gm`.`player_id` LEFT JOIN `guilds` AS `g` ON `gm`.`guild_id` = `g`.`id`;") : mysql_select_multi("SELECT `p`.`name` AS `name`, `p`.`level` AS `level`, `p`.`vocation` AS `vocation`, `g`.`name` AS `gname` $outfitQuery FROM `players_online` AS `o` INNER JOIN `players` AS `p` ON `o`.`player_id` = `p`.`id` LEFT JOIN `guild_membership` AS `gm` ON `o`.`player_id` = `gm`.`player_id` LEFT JOIN `guilds` AS `g` ON `gm`.`guild_id` = `g`.`id`;"); + } else { + $array = ($loadFlags === true) ? mysql_select_multi("SELECT `p`.`name` as `name`, `p`.`level` as `level`, `p`.`vocation` as `vocation`, `g`.`name` as `gname`, `za`.`flag` as `flag` $outfitQuery FROM `players` as `p` INNER JOIN `znote_accounts` as `za` ON `za`.`account_id` = `p`.`account_id` LEFT JOIN `guild_ranks` as `gr` ON `gr`.`id` = `p`.`rank_id` LEFT JOIN `guilds` as `g` ON `gr`.`guild_id` = `g`.`id` WHERE `p`.`online` = '1' ORDER BY `p`.`name` DESC;") : mysql_select_multi("SELECT `p`.`name` as `name`, `p`.`level` as `level`, `p`.`vocation` as `vocation`, `g`.`name` as `gname` $outfitQuery FROM `players` as `p` LEFT JOIN `guild_ranks` as `gr` ON `gr`.`id` = `p`.`rank_id` LEFT JOIN `guilds` as `g` ON `gr`.`guild_id` = `g`.`id` WHERE `p`.`online` = '1' ORDER BY `p`.`name` DESC;"); + } + // End loading data from SQL + $cache->setContent($array); + $cache->save(); +} else { + $array = $cache->load(); +} +// End cache + +if (!empty($array) && $array !== false) { + ?> + + + + Outfit"; ?> + + + + + + 1) ? ' ' : ''; + $guildname = (!empty($value['gname'])) ? ''. $value['gname'] .'' : ''; + ?> + + + + + + + + + + +
    Name:Guild:Level:Vocation:
    img
    + + + diff --git a/app/ZnoteAAC/pagseguro_ipn.php b/app/ZnoteAAC/pagseguro_ipn.php new file mode 100644 index 0000000..a26921e --- /dev/null +++ b/app/ZnoteAAC/pagseguro_ipn.php @@ -0,0 +1,114 @@ +status; + $paymentCode = sanitize($payment->code); + + report($notificationCode, $rawPayment); + + // Updating Payment Status + mysql_update('UPDATE `znote_pagseguro` SET `payment_status` = ' . $paymentStatus . ' WHERE `transaction` = \'' . $paymentCode . '\' '); + + // Check that the payment_status is Completed + if ($paymentStatus == 3) { + + // Check that transaction has not been previously processed + $transaction = mysql_select_single('SELECT `transaction`, `completed` FROM `znote_pagseguro` WHERE `transaction`= \'' . $paymentCode .'\''); + $status = true; + $custom = (int) $payment->reference; + + if ($transaction['completed'] == '1') { + $status = false; + } + + if ($payment->grossAmount == 0.0) $status = false; // Wrong ammount of money + $item = $payment->items->item[0]; + if ($item->amount != ($pagseguro['price'] / 100)) $status = false; + + if ($status) { + // transaction log + mysql_update('UPDATE `znote_pagseguro` SET `completed` = 1 WHERE `transaction` = \'' . $paymentCode . '\''); + + // Process payment + $data = mysql_select_single("SELECT `points` AS `old_points` FROM `znote_accounts` WHERE `account_id`='$custom';"); + + // Give points to user + $new_points = $data['old_points'] + $item->quantity; + mysql_update("UPDATE `znote_accounts` SET `points`='$new_points' WHERE `account_id`='$custom'"); + } + } else if ($paymentStatus == 7) { + mysql_update('UPDATE `znote_pagseguro` SET `completed` = 1 WHERE `transaction` = \'' . $paymentCode . '\' '); + } +?> diff --git a/app/ZnoteAAC/pagseguro_retorno.php b/app/ZnoteAAC/pagseguro_retorno.php new file mode 100644 index 0000000..abbbd8d --- /dev/null +++ b/app/ZnoteAAC/pagseguro_retorno.php @@ -0,0 +1,108 @@ +status; + $completed = ($transactionStatus != 7) ? 0 : 1; + + $custom = (int) $transaction->reference; + $item = $transaction->items->item[0]; + $points = $item->quantity; + $price = $points * ($pagseguro['price'] / 100); + mysql_insert('INSERT INTO `znote_pagseguro` VALUES (null, \'' . sanitize($transaction->code) . '\', ' . $custom . ', \'' . $price . '\', \'' . $points . '\', ' . $transactionStatus . ', ' . $completed . ')'); + + header('Location: shop.php?callback=processing'); diff --git a/app/ZnoteAAC/paygol_ipn.php b/app/ZnoteAAC/paygol_ipn.php new file mode 100644 index 0000000..ee6d9dd --- /dev/null +++ b/app/ZnoteAAC/paygol_ipn.php @@ -0,0 +1,55 @@ + +
    + + 0) + $today = false; + } else { + $znotePlayers = mysql_select_multi('SELECT `a`.`id`, `b`.`player_id`, `a`.`name`, `a`.`vocation`, `a`.`level`, `a`.`group_id`, `a`.`experience`, `b`.`exphist_lastexp`, `b`.`exphist1`, `b`.`exphist2`, `b`.`exphist3`, `b`.`exphist4`, `b`.`exphist5`, `b`.`exphist6`, `b`.`exphist7`, (`a`.`experience` - `b`.`exphist_lastexp`) AS `expdiff` FROM `players` `a` JOIN `znote_players` `b` ON `a`.`id` = `b`.`player_id` WHERE `a`.`group_id` < 2 ORDER BY `expdiff` DESC LIMIT '.$limit); + } + $limit = $config['powergamers']['limit']; + + if(!empty($days) && !empty($vocation)) + $znotePlayers = mysql_select_multi('SELECT `a`.`id`, `b`.`player_id`, `a`.`name`, `a`.`vocation`, `a`.`level`, `a`.`group_id`, `a`.`experience`, `b`.`exphist_lastexp`, `b`.`exphist1`, `b`.`exphist2`, `b`.`exphist3`, `b`.`exphist4`, `b`.`exphist5`, `b`.`exphist6`, `b`.`exphist7`, (`a`.`experience` - `b`.`exphist_lastexp`) AS `expdiff` FROM `players` `a` JOIN `znote_players` `b` ON `a`.`id` = `b`.`player_id` WHERE `a`.`group_id` < 2 AND `a`.`vocation`='. (int)$vocation .' OR `a`.`vocation`='. ((int)$vocation +4) .' ORDER BY `exphist' . (int)$days . '` DESC LIMIT '.$limit); + elseif(empty($days) && !empty($vocation)) { + $znotePlayers = mysql_select_multi('SELECT `a`.`id`, `b`.`player_id`, `a`.`name`, `a`.`vocation`, `a`.`level`, `a`.`group_id`, `a`.`experience`, `b`.`exphist_lastexp`, `b`.`exphist1`, `b`.`exphist2`, `b`.`exphist3`, `b`.`exphist4`, `b`.`exphist5`, `b`.`exphist6`, `b`.`exphist7`, (`a`.`experience` - `b`.`exphist_lastexp`) AS `expdiff` FROM `players` `a` JOIN `znote_players` `b` ON `a`.`id` = `b`.`player_id` WHERE `a`.`group_id` < 2 AND `a`.`vocation`='. (int)$vocation .' OR `a`.`vocation`='. ((int)$vocation +4) .' ORDER BY `expdiff` DESC LIMIT '.$limit); + }elseif(!empty($days) && empty($vocation)) + $znotePlayers = mysql_select_multi('SELECT `a`.`id`, `b`.`player_id`, `a`.`name`, `a`.`vocation`, `a`.`level`, `a`.`group_id`, `a`.`experience`, `b`.`exphist_lastexp`, `b`.`exphist1`, `b`.`exphist2`, `b`.`exphist3`, `b`.`exphist4`, `b`.`exphist5`, `b`.`exphist6`, `b`.`exphist7`, (`a`.`experience` - `b`.`exphist_lastexp`) AS `expdiff` FROM `players` `a` JOIN `znote_players` `b` ON `a`.`id` = `b`.`player_id` WHERE `a`.`group_id` < 2 ORDER BY `exphist' . (int)$days . '` DESC LIMIT '.$limit); + else + $znotePlayers = mysql_select_multi('SELECT `a`.`id`, `b`.`player_id`, `a`.`name`, `a`.`vocation`, `a`.`level`, `a`.`group_id`, `a`.`experience`, `b`.`exphist_lastexp`, `b`.`exphist1`, `b`.`exphist2`, `b`.`exphist3`, `b`.`exphist4`, `b`.`exphist5`, `b`.`exphist6`, `b`.`exphist7`, (`a`.`experience` - `b`.`exphist_lastexp`) AS `expdiff` FROM `players` `a` JOIN `znote_players` `b` ON `a`.`id` = `b`.`player_id` WHERE `a`.`group_id` < 2 ORDER BY `expdiff` DESC LIMIT '.$limit); + + $showVoc = (!empty($vocation)) ? $vocation : 0; + ?> +
    +
    +
    + + +
    + 0) ? 'Showing only '. strtolower(vocation_id_to_name($vocation)).'s and' : 'Showing all vocations and'; ?> + 0) ? 'sorted by '. $days .' days': 'sorted by today'; ?>. +
    +
    +
    + + + + = 2; $i--) + echo ($days == $i) ? '' : ''; + echo ($days == 1) ? '' : ''; + echo ($today) ? '' : ''; + echo ($days == 4) ? '' : ''; + echo ''; + + $number_of_rows = 0; + if($znotePlayers) { + foreach($znotePlayers as $player) + { + $number_of_rows++; + echo ''; + echo '' : ''; + echo ($days == 2) ? '' : ''; + echo ($days == 1) ? '' : ''; + echo ($today == true) ? '' : ''; + echo ''; + } + } + ?> +
    #
    Name'.$i.' Days AgoYesterdayTodayTotal
    '. $number_of_rows . '.
    ' .$player['name']. ''; + echo '
    '. ($player['level']. ' '.htmlspecialchars(vocation_id_to_name($player['vocation'])) ).' '; + echo ($days == 3) ? '
    '. number_format($player['exphist3']) .'
    '. $player['exphist2'] .'
    '. $player['exphist1'] .'
    '. ($player['experience']-$player['exphist_lastexp']) .'
    +
    +
    + + +

    STOP!

    +

    Ummh... Why are you sniffing around here?

    + + + +

    Sorry, you need to be logged in to do that!

    +

    Please register or log in.

    + + diff --git a/app/ZnoteAAC/queststatus.php b/app/ZnoteAAC/queststatus.php new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/app/ZnoteAAC/queststatus.php @@ -0,0 +1,54 @@ + + [Completed]'; + $notstarted = ''; + function Progress($min, $max, $design = '[x%]') { + $design = explode("x%",$design); + $percent = ($min / $max) * 100; + return $design[0] . $percent . $design[1]; + } + $quests = array( + // Simple quests + 'Bearslayer' => 1050, + 'Sword Quest' => 1337, + + // Advanced quest with progress par: + 'Postman Quest' => array( + 1338, + 3, + ), + ); + ?> + + + + + $quest) { + + // Is quest NOT an array (advanced quest?) + if (!is_array($quest)) { + // Query to find quest results + $query = mysql_select_single("SELECT `value` FROM `player_storage` WHERE `key`='$quest' AND `player_id`='$user_id' AND `value`='1' LIMIT 1;"); + + if ($query !== false) $quest = $completed; + else $quest = $notstarted; + + } else { + $query = mysql_select_single("SELECT `value` FROM `player_storage` WHERE `key`='".$quest[0]."' AND `player_id`='$user_id' AND `value`>'0' LIMIT 1;"); + if (!$query) $quest = $notstarted; + else { + if ($query['value'] >= $quest[1]) $quest = $completed; + else $quest = Progress($query['value'], $quest[1]); + } + } + ?> + + + + + +
    Quest NameStatus
    \ No newline at end of file diff --git a/app/ZnoteAAC/recovery.php b/app/ZnoteAAC/recovery.php new file mode 100644 index 0000000..8967554 --- /dev/null +++ b/app/ZnoteAAC/recovery.php @@ -0,0 +1,212 @@ +Account Recovery"; + $body .= "

    Your username is: $user[name]
    "; + $body .= "Enjoy your stay at ".$config['mailserver']['fromName'].".
    "; + $body .= "


    I am an automatic no-reply e-mail. Any emails sent back to me will be ignored.

    "; + $mailer->sendMail($email, $title, $body, $user['name']); + + ?> +

    Account Found!

    +

    We have sent your username to .

    +

    If you can't find the email within 5 minutes, check your junk/trash inbox as it may be mislocated there.

    + +

    Account recovery failed!

    +

    Submitted data is wrong.

    + Account Recovery"; + $body .= "

    Your new password is: $newpass
    "; + $body .= "We recommend you to login and change it before you continue playing.
    "; + $body .= "Enjoy your stay at ".$config['mailserver']['fromName'].".
    "; + $body .= "


    I am an automatic no-reply e-mail. Any emails sent back to me will be ignored.

    "; + $mailer->sendMail($email, $title, $body, $user['name']); + ?> +

    Account Found!

    +

    We have sent your new password to .

    +

    If you can't find the email within 5 minutes, check your junk/trash inbox as it may be mislocated there.

    + +

    Account recovery failed!

    +

    Submitted data is wrong.

    + Remove Two-Factor Authentication"; + $body .= "

    If you really want to remove Two-Factor Authentication, click on the following link:
    "; + $body .= "$recoverylink
    "; + $body .= "Enjoy your stay at ".$config['mailserver']['fromName'].".
    "; + $body .= "


    I am an automatic no-reply e-mail. Any emails sent back to me will be ignored.

    "; + $mailer->sendMail($email, $title, $body, $user['name']); + ?> +

    Confirm your action through email

    +

    We have sent a confirmation link to .

    +

    You must click the link before we remove Two-factor authentication.

    +

    If you can't find the email within 5 minutes, check your junk/trash inbox as it may be mislocated there.

    + +

    Account recovery failed!

    +

    Submitted data is wrong.

    + +

    Two-Factor Authentication disabled.

    +

    You may now login with just your username and password.

    + +

    Failed verify your request.

    +

    We are unable to authenticate your account.

    + +

    Account Recovery

    + + +
    +
    +
    + Username:
    '; + } elseif ($mode === 'username') { + echo '
    '; + } elseif ($mode === 'token') { + echo '
    '; + echo '
    '; + } + + if ($config['use_captcha']) { + ?> +
    + + +
    + +

    Do you wish to recover your username, password or remove Two-factor authentication?

    + +

    Do you wish to recover your username or password?

    + +

    System Disabled

    +

    The admin have disabled automatic account recovery.

    + diff --git a/app/ZnoteAAC/register.php b/app/ZnoteAAC/register.php new file mode 100644 index 0000000..cd469a0 --- /dev/null +++ b/app/ZnoteAAC/register.php @@ -0,0 +1,231 @@ +$value) { + if (empty($value) && in_array($key, $required_fields) === true) { + $errors[] = 'You need to fill in all fields.'; + break 1; + } + } + + // check errors (= user exist, pass long enough + if (empty($errors) === true) { + /* Token used for cross site scripting security */ + if (!Token::isValid($_POST['token'])) { + $errors[] = 'Token is invalid.'; + } + + if ($config['use_captcha']) { + if(!verifyGoogleReCaptcha($_POST['g-recaptcha-response'])) { + $errors[] = "Please confirm that you're not a robot."; + } + } + + if (user_exist($_POST['username']) === true) { + $errors[] = 'Sorry, that username already exist.'; + } + + // Don't allow "default admin names in config.php" access to register. + $isNoob = in_array(strtolower($_POST['username']), $config['page_admin_access']) ? true : false; + if ($isNoob) { + $errors[] = 'This account name is blocked for registration.'; + } + if ($config['ServerEngine'] !== 'OTHIRE' && $config['client'] >= 830) { + if (preg_match("/^[a-zA-Z0-9]+$/", $_POST['username']) == false) { + $errors[] = 'Your account name can only contain characters a-z, A-Z and 0-9.'; + } + } else { + if (preg_match("/^[0-9]+$/", $_POST['username']) == false) { + $errors[] = 'Your account can only contain numbers 0-9.'; + } + if ((int)$_POST['username'] < 100000 || (int)$_POST['username'] > 999999999) { + $errors[] = 'Your account number must be a value between 6-8 numbers long.'; + } + } + // name restriction + $resname = explode(" ", $_POST['username']); + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } + else if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + if (strlen($_POST['username']) > 32) { + $errors[] = 'Your account name must be less than 33 characters.'; + } + // end name restriction + if (strlen($_POST['password']) < 6) { + $errors[] = 'Your password must be at least 6 characters.'; + } + if (strlen($_POST['password']) > 29) { + $errors[] = 'Your password must be less than 30 characters.'; + } + if ($_POST['password'] !== $_POST['password_again']) { + $errors[] = 'Your passwords do not match.'; + } + if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) === false) { + $errors[] = 'A valid email address is required.'; + } + if (user_email_exist($_POST['email']) === true) { + $errors[] = 'That email address is already in use.'; + } + if ($_POST['selected'] != 1) { + $errors[] = 'You are only allowed to have an account if you accept the rules.'; + } + if ($config['validate_IP'] === true) { + if (validate_ip(getIP()) === false) { + $errors[] = 'Failed to recognize your IP address. (Not a valid IPv4 address).'; + } + } + if (strlen($_POST['flag']) < 1) { + $errors[] = 'Please choose country.'; + } + } +} + +?> +

    Register Account

    + +

    Email authentication required

    +

    We have sent you an email with an activation link to your submitted email address.

    +

    If you can't find the email within 5 minutes, check your junk/trash inbox (spam filter) as it may be mislocated there.

    + 0) ? (int)$_GET['u'] : false; + $akey = (isset($_GET['k']) && (int)$_GET['k'] > 0) ? (int)$_GET['k'] : false; + // Find a match + $user = mysql_select_single("SELECT `id`, `active`, `active_email` FROM `znote_accounts` WHERE `account_id`='$auid' AND `activekey`='$akey' LIMIT 1;"); + if ($user !== false) { + $user = (int) $user['id']; + $active = (int) $user['active']; + $active_email = (int) $user['active_email']; + // Enable the account to login + if ($active == 0 || $active_email == 0) { + mysql_update("UPDATE `znote_accounts` SET `active`='1', `active_email`='1' WHERE `id`= $user LIMIT 1;"); + } + echo '

    Congratulations!

    Your account has been created. You may now login to create a character.

    '; + } else { + echo '

    Authentication failed

    Either the activation link is wrong, or your account is already activated.

    '; + } +} else { + if (empty($_POST) === false && empty($errors) === true) { + if ($config['log_ip']) { + znote_visitor_insert_detailed_data(1); + } + + //Register + if ($config['ServerEngine'] !== 'OTHIRE') { + $register_data = array( + 'name' => $_POST['username'], + 'password' => $_POST['password'], + 'email' => $_POST['email'], + 'created' => time(), + 'ip' => getIPLong(), + 'flag' => $_POST['flag'] + ); + } else { + $register_data = array( + 'id' => $_POST['username'], + 'password' => $_POST['password'], + 'email' => $_POST['email'], + 'created' => time(), + 'ip' => getIPLong(), + 'flag' => $_POST['flag'] + ); + } + + user_create_account($register_data, $config['mailserver']); + if (!$config['mailserver']['debug']) header('Location: register.php?success'); + exit(); + //End register + + } else if (empty($errors) === false){ + echo ''; + echo output_errors($errors); + echo ''; + } +?> +
    +
      +
    • + Account Name:
      + +
    • +
    • + Password:
      + +
    • +
    • + Password again:
      + +
    • +
    • + Email:
      + +
    • +
    • + Country:
      + +
    • + +
    • +
      +
    • + +
    • +

      Server Rules

      +

      The golden rule: Have fun.

      +

      If you get pwn3d, don't hate the game.

      +

      No cheating allowed.

      +

      No botting allowed.

      +

      The staff can delete, ban, do whatever they want with your account and your
      + submitted information. (Including exposing and logging your IP).

      +
    • +
    • + Do you agree to follow the server rules?
      + +
    • + +
    • + +
    • +
    +
    + diff --git a/app/ZnoteAAC/serverinfo.php b/app/ZnoteAAC/serverinfo.php new file mode 100644 index 0000000..48b183e --- /dev/null +++ b/app/ZnoteAAC/serverinfo.php @@ -0,0 +1,411 @@ + 0) + $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24; + if (isset($duration['hour'])) { + if (($duration['hour'] - (int)$duration['hour']) > 0) + $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60; + if (isset($duration['minute'])) { + if (($duration['minute'] - (int)$duration['minute']) > 0) + $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60; + } + } + $tmp = array(); + foreach ($duration as $type => $value) { + if ($value >= 1) { + $pluralType = ((int)$value === 1) ? $type : $type . 's'; + if ($type !== 'second') $tmp[] = (int)$value . " $pluralType"; + else $tmp[] = $value . " $pluralType"; + } + } + return implode(', ', $tmp); +} +function toYesNo($bool) { + return ($bool) ? 'Yes' : 'No'; +} +// Loading stage list +$cache = new Cache('engine/cache/stages'); +if (user_logged_in() && is_admin($user_data)) { + if (isset($_GET['loadStages'])) { + echo "

    Logged in as admin, loading engine/XML/stages.xml file and updating cache.

    "; + // STAGES XML TO PHP ARRAY + $stagesXML = simplexml_load_file("engine/XML/stages.xml"); + if ($stagesXML !== false) { + $stagesData = array(); + // Load config (stages enabled or disabled) + if ($config['ServerEngine'] == 'TFS_10') + foreach ($stagesXML->config->attributes() as $name => $value) + $stagesData["$name"] = "$value"; + // Load stage levels + // Each stage XML object + if ($config['ServerEngine'] == 'TFS_10') { + foreach ($stagesXML->stage as $stage) { + $rowData = array(); + // Each attribute name and values on current stage object + foreach ($stage->attributes() as $name => $value) { + $rowData["$name"] = "$value"; + } + // Populate XML assoc array + $stagesData['stages'][] = $rowData; + } + } else { + // TFS 0.3/4 + foreach ($stagesXML->world as $world) { + foreach ($world->stage as $stage) { + $rowData = array(); + // Each attribute name and values on current stage object + foreach ($stage->attributes() as $name => $value) { + $rowData["$name"] = "$value"; + } + // Populate XML assoc array + $stagesData['stages'][] = $rowData; + } + } + } + $cache->setContent($stagesData); + $cache->save(); + } + } else { + $stagesData = $cache->load(); + ?> +
    + +
    + load(); +} +// End loading stage list + +// Loading config.lua +$cache = new Cache('engine/cache/luaconfig'); +if (user_logged_in() && is_admin($user_data)) { + if (isset($_POST['loadConfig']) && isset($_POST['configData'])) { + // Whitelist for values we are interested in + $whitelist = array( // Etc 'maxPlayers' + 'worldType', + 'hotkeyAimbotEnabled', + 'protectionLevel', + 'killsToRedSkull', + 'killsToBlackSkull', + 'pzLocked', + 'removeChargesFromRunes', + 'timeToDecreaseFrags', + 'whiteSkullTime', + 'stairJumpExhaustion', + 'experienceByKillingPlayers', + 'expFromPlayersLevelRange', + 'loginProtocolPort', + 'maxPlayers', + 'motd', + 'onePlayerOnlinePerAccount', + 'deathLosePercent', + 'housePriceEachSQM', + 'houseRentPeriod', + 'marketOfferDuration', + 'premiumToCreateMarketOffer', + 'maxMarketOffersAtATimePerPlayer', + 'allowChangeOutfit', + 'freePremium', + 'kickIdlePlayerAfterMinutes', + 'rateExp', + 'rateSkill', + 'rateLoot', + 'rateMagic', + 'rateSpawn', + 'staminaSystem', + 'experienceStages' + ); + // TFS 0.3/4 compatibility, convert config value names to TFS 1.0 values + $tfs03to10 = array( + // TFS 0.3/4 TFS 1.0 + 'rateExperience' => 'rateExp', + 'loginPort' => 'loginProtocolPort', + 'rateExperienceFromPlayers' => 'experienceByKillingPlayers', + 'dailyFragsToRedSkull' => 'killsToRedSkull', + 'dailyFragsToBlackSkull' => 'killsToBlackSkull', + 'removeRuneCharges' => 'removeChargesFromRunes', + 'stairhopDelay' => 'stairJumpExhaustion', + 'housePriceEachSquare' => 'housePriceEachSQM', + 'idleKickTime' => 'kickIdlePlayerAfterMinutes', + ); + // This will be the populated array with filtered relevant data + $luaConfig = array(); + + // Remove everything between first { and last } + $poststring = $_POST['configData']; + $first = strpos($poststring, '{'); + if ($first !== false) { + $last = strripos($poststring, '}'); + if ($last !== false) { + $sliced_string = substr($poststring, 0, $first).substr($poststring, $last+1); + $poststring = $sliced_string; + } else { + die("Lua process error: Syntax error in config.lua"); + } + } + + // Explode the string into string array by newline + $rawLua = explode("\n", $poststring); + // Clean up the array + $length = count($rawLua); + for ($i = 0; $i < $length; $i++) { + // We only care about lines that have the = symbol + if (strpos($rawLua[$i], '=') !== false) { + // Look for inline Lua comments and remove them + $comment = strpos($rawLua[$i], '--'); + if ($comment !== false) + $rawLua[$i] = substr($rawLua[$i], 0, $comment); + $rawLua[$i] = trim($rawLua[$i]); // Remove unnecessary whitespace + // If for some reason the line is empty, ignore it. (Could be a "=" symbol inside an inline Lua comment that we sliced away) + if (!empty($rawLua[$i])) { + // Built a relevant data array + $data = explode('=', $rawLua[$i]); + // Remove unnecessary whitespace + $data[0] = trim($data[0]); + $data[1] = trim($data[1]); + // TFS 0.3/4 compatibility + if (isset($tfs03to10[$data[0]])) { + $data[0] = $tfs03to10[$data[0]]; + if (isset($tfs03to10[$data[1]])) { + $data[1] = $tfs03to10[$data[1]]; + } + } + if (in_array($data[0], $whitelist)) { + // Type cast: boolean + if (in_array(strtolower($data[1]), array('true', 'false'))) { + $data[1] = (strtolower($data[1]) === 'true') ? true : false; + } else { + if (strpos($data[1], '"') === false) { + if (!in_array($data[1], array_keys($luaConfig))) { + // Type cast: integer + if (strlen($data[1]) > 0) { + $data[1] = eval('return (' . $data[1] . ');'); + } + } else { + // Type cast: Load value from another key + $data[1] = (isset($luaConfig[$data[1]])) ? $luaConfig[$data[1]] : null; + } + } else { + // Type cast: string, just remove the quote we earlier used to determine if it was a string. + $data[1] = str_replace('"', '', $data[1]); + } + } + // Add the results + $luaConfig[$data[0]] = $data[1]; + } // End whitelisted row + } // End not empty row + } // Line has \= symbol + } // for loop + $cache->setContent($luaConfig); + $cache->save(); + } else { + $luaConfig = $cache->load(); + ?> +
    +
    +
    +
    + +
    + load(); +} +// End loading config.lua + +$stages = false; + +// Render HTML +?> + +

    Server Information

    +

    Here you will find all basic information about

    + + +

    Server rates

    + + + + + + + + + + + + + + + +
    Minimum levelMaximum levelMultiplier
    x
    + + + + + + + + + + + + + + + + + + + + + + +
    Experience rateSkills rateMagic rateLoot rate
    + +

    Miscellaneous information

    + + + + + + + + + + + + + + + + + + +
    Connection information
    Client
    IP
    Port
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PvP information
    World type
    Hotkey aimbot
    Protection level
    Kills to red skull
    Kills to black skull
    Remove rune charges
    Time to decrease frags
    Experience by killing players
    Experience gain kill threshold:% of your level
    White skull duration
    Protection zone lock (non lethal attack)
    Stair jump exhaust
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Other information
    Free premium
    House rent period
    House SQM price gp
    AFK kickout
    One player online per account
    Max players online server limit 0) ? $luaConfig['maxPlayers'] : 'Unlimited'; ?>
    Allow outfit change
    Stamina system
    Premium to add items to market
    Market offer duration
    + +

    The server administrator has yet to import server information to this page.

    + diff --git a/app/ZnoteAAC/settings.php b/app/ZnoteAAC/settings.php new file mode 100644 index 0000000..2029fc1 --- /dev/null +++ b/app/ZnoteAAC/settings.php @@ -0,0 +1,93 @@ +$value) { + if (empty($value) && in_array($key, $required_fields) === true) { + $errors[] = 'You need to fill in all fields.'; + break 1; + } + } + + if (empty($errors) === true) { + if (filter_var($_POST['new_email'], FILTER_VALIDATE_EMAIL) === false) { + $errors[] = 'A valid email address is required.'; + } else if (user_email_exist($_POST['new_email']) === true && $user_data['email'] !== $_POST['new_email']) { + $errors[] = 'That email address is already in use.'; + } + } +} +?> +

    Settings

    + + $_POST['new_email'] + ); + + $update_znote_data = array( + 'flag' => getValue($_POST['new_flag']), + 'active_email' => '0' + ); + + // If he had previously verified his email address, remove the previously aquired bonus points + if ($user_znote_data['active_email'] > 0) { + $update_znote_data['points'] = $user_znote_data['points'] - $config['mailserver']['verify_email_points']; + } + + user_update_account($update_data); + user_update_znote_account($update_znote_data); + header('Location: settings.php?success'); + exit(); + + } else if (empty($errors) === false) { + echo output_errors($errors); + } + ?> + +
    +
      +
    • + email:
      + +
    • +
    • + Country:
      + +
    • + +
    • + +
    • +
    +
    + diff --git a/app/ZnoteAAC/shop.php b/app/ZnoteAAC/shop.php new file mode 100644 index 0000000..768dcce --- /dev/null +++ b/app/ZnoteAAC/shop.php @@ -0,0 +1,398 @@ +alert("Seu pagamento está sendo processado pelo PagSeguro...");'; +} + +// Import from config: +$shop = $config['shop']; +if ($shop['loginToView'] === true) protect_page(); +$loggedin = user_logged_in(); + +$shop_list = $config['shop_offers']; + +if ($loggedin === true) { + if (!empty($_POST['buy']) && $_SESSION['shop_session'] == $_POST['session']) { + $time = time(); + $player_points = (int)$user_znote_data['points']; + $cid = (int)$user_data['id']; + // Sanitizing post, setting default buy value + $buy = false; + $post = (int)$_POST['buy']; + + foreach ($shop_list as $key => $value) { + if ($key === $post) { + $buy = $value; + } + } + if ($buy === false) die("Error: Shop offer ID mismatch."); + + // Verify that user can afford this offer. + if ($player_points >= $buy['points']) { + $data = mysql_select_single("SELECT `points` FROM `znote_accounts` WHERE `account_id`='$cid';"); + if (!$data) die("0: Account is not converted to work with Znote AAC"); + $old_points = $data['points']; + if ((int)$old_points != (int)$player_points) die("1: Failed to equalize your points."); + // Remove points if they can afford + // Give points to user + $expense_points = $buy['points']; + $new_points = $old_points - $expense_points; + $update_account = mysql_update("UPDATE `znote_accounts` SET `points`='$new_points' WHERE `account_id`='$cid'"); + + $data = mysql_select_single("SELECT `points` FROM `znote_accounts` WHERE `account_id`='$cid';"); + $verify = $data['points']; + if ((int)$old_points == (int)$verify) die("2: Failed to equalize your points.". var_dump((int)$old_points, (int)$verify, $new_points, $expense_points)); + + // If this is an outfit offer, convert array into an integer. + if ($buy['type'] == 5) { + if (is_array($buy['itemid'])) { + if (COUNT($buy['itemid']) == 2) $buy['itemid'] = ($buy['itemid'][0] * 1000) + $buy['itemid'][1]; + else $buy['itemid'] = $buy['itemid'][0]; + } + } + + // Do the magic (insert into db, or change sex etc) + // If type is 2 or 3 + if ($buy['type'] == 2) { + // Add premium days to account + user_account_add_premdays($cid, $buy['count']); + echo 'You now have '.$buy['count'].' additional days of premium membership.'; + } else if ($buy['type'] == 3) { + // Character Gender + mysql_insert("INSERT INTO `znote_shop_orders` (`account_id`, `type`, `itemid`, `count`, `time`) VALUES ('$cid', '". $buy['type'] ."', '". $buy['itemid'] ."', '". $buy['count'] ."', '$time')"); + echo 'You now have access to change character gender on your characters. Visit My Account to select character and change the gender.'; + } else if ($buy['type'] == 4) { + // Character Name + mysql_insert("INSERT INTO `znote_shop_orders` (`account_id`, `type`, `itemid`, `count`, `time`) VALUES ('$cid', '". $buy['type'] ."', '". $buy['itemid'] ."', '". $buy['count'] ."', '$time')"); + echo 'You now have access to change character name on your characters. Visit My Account to select character and change the name.'; + } else { + mysql_insert("INSERT INTO `znote_shop_orders` (`account_id`, `type`, `itemid`, `count`, `time`) VALUES ('$cid', '". $buy['type'] ."', '". $buy['itemid'] ."', '". $buy['count'] ."', '$time')"); + echo 'Your order is ready to be delivered. Write this command in-game to get it: [!shop].
    Make sure you are in depot and can carry it before executing the command!
    '; + } + + // No matter which type, we will always log it. + mysql_insert("INSERT INTO `znote_shop_logs` (`account_id`, `player_id`, `type`, `itemid`, `count`, `points`, `time`) VALUES ('$cid', '0', '". $buy['type'] ."', '". $buy['itemid'] ."', '". $buy['count'] ."', '". $buy['points'] ."', '$time')"); + + } else echo 'You need more points, this offer cost '.$buy['points'].' points.'; + //var_dump($buy); + //echo ''. $_POST['buy'] .''; + } +} + +if ($shop['enabled']) { +?> + +

    Shop Offers

    += $buy['points']) { + ?>You have points. (Buy points).You have points. (Buy points).You have points. (Buy points). +

    Interested in buying characters? View the character auction page!

    +

    You need to be logged in to use the shop.

    $offer) { + + switch ($offer['type']) { + case 1: + $category_items[$key] = $offer; + break; + case 2: + $category_premium[$key] = $offer; + break; + case 3: + $category_misc[$key] = $offer; + break; + case 4: + $category_misc[$key] = $offer; + break; + case 5: + $category_outfits[$key] = $offer; + break; + case 6: + $category_mounts[$key] = $offer; + break; + default: + $category_misc[$key] = $offer; + break; + } +} + +// Render a bunch of tables (one for each category) +?> + + + + + + + + + + + + + + $offers): ?> + + + + + + + + + + + + +
    Item:Image:Count:Points:Action:
    imgx +
    + + + +
    +
    + + + + + + + + + + + + $offers): ?> + + + + + + + + + + + + +
    Description:Image:Duration:Points:Action:
    img Days +
    + + + +
    +
    + + + + + + + + + + + $offers): + if (!is_array($offers['itemid'])) $offers['itemid'] = [$offers['itemid']]; + if (COUNT($offers['itemid']) > 2): ?> + + + + + + + + + + + + + + + +
    Description:Image:Points:Action:
    +

    Error: Outfit offer don't support more than 2 outfits. configured. +
    []

    +
    + img + +
    + + + +
    +
    + + + + + + + + + + + $offers): ?> + + + + + + + + + + + +
    Description:Image:Points:Action:
    img +
    + + + +
    +
    + + + + + + + + + + + + $offers): ?> + + + + + + + + + + + + + + + +
    Description:Image:Count/duration:Points:Action:
    imgUnlimitedx +
    + + + +
    +
    + + + + + +Buy Points system disabled.

    Sorry, this functionality is disabled.

    '; +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/special/.htaccess b/app/ZnoteAAC/special/.htaccess new file mode 100644 index 0000000..8fdc8aa --- /dev/null +++ b/app/ZnoteAAC/special/.htaccess @@ -0,0 +1,3 @@ +order deny,allow +deny from all +allow from 127.0.0.1 localhost ::1 \ No newline at end of file diff --git a/app/ZnoteAAC/special/convertoldshoppoints.php b/app/ZnoteAAC/special/convertoldshoppoints.php new file mode 100644 index 0000000..30c0f11 --- /dev/null +++ b/app/ZnoteAAC/special/convertoldshoppoints.php @@ -0,0 +1,32 @@ + + +

    Gesior and Modern shop points to Znote AAC shop points

    +

    Convert donation/shop points from previous Gesior/Modern installation to Znote AAC:

    +'0';"); + $accountids = array(); + foreach ($accounts as $acc) $accountids[] = $acc['id']; + $accidlist = join(',',$accountids); + + if ($accounts !== false) echo "

    Detected: ". count($accounts) ." accounts who have points in old system.

    "; + else die("

    All accounts already converted. :)

    "); + + $znote_accounts = mysql_select_multi("SELECT `account_id`, `points` FROM `znote_accounts` WHERE `account_id` IN ($accidlist);"); + + if (count($accounts) !== count($znote_accounts)) die("

    Failed to syncronize accounts. You need to convert all accounts to Znote AAC first!

    "); + + // Order old accounts by id. + $idaccounts = array(); + foreach ($accounts as $acc) { + $idaccounts[$acc['id']] = $acc['premium_points']; + } + foreach ($znote_accounts as $acc) { + mysql_update("UPDATE `znote_accounts` SET `points`='". ($acc['points'] + $idaccounts[$acc['account_id']]) ."' WHERE `account_id`='". $acc['account_id'] ."' LIMIT 1;"); + } + mysql_update("UPDATE `accounts` SET `premium_points`='0';"); + + echo "

    Successfully converted all points!

    "; +?> \ No newline at end of file diff --git a/app/ZnoteAAC/special/database2znoteaac.php b/app/ZnoteAAC/special/database2znoteaac.php new file mode 100644 index 0000000..bd4944f --- /dev/null +++ b/app/ZnoteAAC/special/database2znoteaac.php @@ -0,0 +1,153 @@ + + +

    Old database to Znote AAC compatibility converter:

    +

    Converting accounts and characters to work with Znote AAC:

    + 0) ? $accounts : false; + } + + function user_count_znote_accounts() { + $data = mysql_select_single("SELECT COUNT(`account_id`) AS `count` from `znote_accounts`;"); + return ($data !== false) ? $data['count'] : 0; + } + + function user_character_is_compatible($pid) { + $data = mysql_select_single("SELECT COUNT(`player_id`) AS `count` from `znote_players` WHERE `player_id` = '$pid';"); + return ($data !== false) ? $data['count'] : 0; + } + + function fetch_znote_accounts() { + $results = mysql_select_multi("SELECT `account_id` FROM `znote_accounts`"); + $accounts = array(); + foreach ($results as $row) { + $accounts[] = $row['account_id']; + } + return (count($accounts) > 0) ? $accounts : false; + } + // end install functions + + // count all accounts, znote accounts, find out which accounts needs to be converted. + $all_account = fetch_all_accounts(); + $znote_account = fetch_znote_accounts(); + if ($all_account !== false) { + if ($znote_account !== false) { // If existing znote compatible account exists: + foreach ($all_account as $all) { // Loop through every element in znote_account array + if (!in_array($all, $znote_account)) { + $old_accounts[] = $all; + } + } + } else { + foreach ($all_account as $all) { + $old_accounts[] = $all; + } + } + } + // end ^ + + // Send count status + if (isset($all_account) && $all_account !== false) { + echo '
    '; + echo 'Total accounts detected: '. count($all_account) .'.'; + + if (isset($znote_account) && $znote_account !== false) { + echo '
    '; + echo 'Znote compatible accounts detected: '. count($znote_account) .'.'; + + if (isset($old_accounts)) { + echo '
    '; + echo 'Old accounts detected: '. count($old_accounts) .'.'; + } + } else { + echo '
    '; + echo 'Znote compatible accounts detected: 0.'; + } + echo '
    '; + echo '
    '; + } else { + echo '
    '; + echo 'Total accounts detected: 0.'; + } + // end count status + + // validate accounts + if (isset($old_accounts) && $old_accounts !== false) { + $time = time(); + foreach ($old_accounts as $old) { + + // Make acc data compatible: + mysql_insert("INSERT INTO `znote_accounts` (`account_id`, `ip`, `created`, `flag`) VALUES ('$old', '0', '$time', '')"); + $updated_acc += 1; + + // Fetch unsalted password + if ($config['ServerEngine'] == 'TFS_03' && $config['salt'] === true) { + $password = user_data($old, 'password', 'salt'); + $p_pass = str_replace($password['salt'],"",$password['password']); + } + if ($config['ServerEngine'] == 'TFS_02' || $config['salt'] === false) { + $password = user_data($old, 'password'); + $p_pass = $password['password']; + } + + // Verify lenght of password is less than 28 characters (most likely a plain password) + if (strlen($p_pass) < 28 && $old > 1) { + // encrypt it with sha1 + if ($config['ServerEngine'] == 'TFS_02' || $config['salt'] === false) $p_pass = sha1($p_pass); + if ($config['ServerEngine'] == 'TFS_03' && $config['salt'] === true) $p_pass = sha1($password['salt'].$p_pass); + + // Update their password so they are sha1 encrypted + mysql_update("UPDATE `accounts` SET `password`='$p_pass' WHERE `id`='$old';"); + $updated_pass += 1; + } + + } + } + + // validate players + if ($all_account !== false) { + $time = time(); + foreach ($all_account as $all) { + + $chars = user_character_list_player_id($all); + if ($chars !== false) { + // since char list is not false, we found a character list + + // Lets loop through the character list + foreach ($chars as $c) { + // Is character not compatible yet? + if (user_character_is_compatible($c['id']) == 0) { + // Then lets make it compatible: + $cid = $c['id']; + mysql_insert("INSERT INTO `znote_players` (`player_id`, `created`, `hide_char`, `comment`) VALUES ('$cid', '$time', '0', '')"); + $updated_char += 1; + + } + } + } + } + } + + echo "
    SUCCESS

    "; + echo 'Updated accounts: '. $updated_acc .'
    '; + echo 'Updated characters: : '. $updated_char .'
    '; + echo 'Detected:'. $updated_pass .' accounts with plain passwords. These passwords has been given sha1 encryption.
    '; + echo '
    All accounts and characters are compatible with Znote AAC
    '; +?> diff --git a/app/ZnoteAAC/special/milestone.txt b/app/ZnoteAAC/special/milestone.txt new file mode 100644 index 0000000..b71a2b1 --- /dev/null +++ b/app/ZnoteAAC/special/milestone.txt @@ -0,0 +1,5 @@ +Milestone - What I wish to add to Znote AAC in the future. (Znote AAC TODO/wish list). +- Character auction page for donation points. +- Semi-live communication with OT. +- Live ban, kick, broadcast message, open/close server and Custom commands. +- Sub-page system. diff --git a/app/ZnoteAAC/special/repairSkills.php b/app/ZnoteAAC/special/repairSkills.php new file mode 100644 index 0000000..31e8200 --- /dev/null +++ b/app/ZnoteAAC/special/repairSkills.php @@ -0,0 +1,53 @@ + +

    Script run status:

    +

    Players detected:

    +

    Players already fixed:

    +

    Repaired player accounts:

    + +

    No players detected.

    +

    Something went wrong.

    + + +

    Script run completed.

    diff --git a/app/ZnoteAAC/spells.php b/app/ZnoteAAC/spells.php new file mode 100644 index 0000000..db85592 --- /dev/null +++ b/app/ZnoteAAC/spells.php @@ -0,0 +1,277 @@ +Logged in as admin, loading engine/XML/spells.xml file and updating cache.

    "; + // SPELLS XML TO PHP ARRAY + $spellsXML = simplexml_load_file("engine/XML/spells.xml"); + if ($spellsXML !== false) { + $types = array(); + $type_attr = array(); + $groups = array(); + + // This empty array will eventually contain all spells grouped by type and indexed by spell name + $spells = array(); + + // Loop through each XML spell object + foreach ($spellsXML as $type => $spell) { + // Get spell types + if (!in_array($type, $types)) { + $types[] = $type; + $type_attr[$type] = array(); + } + // Get spell attributes + $attributes = array(); + // Extract attribute values from the XML object and store it in a more manage friendly way $attributes + foreach ($spell->attributes() as $aName => $aValue) + $attributes["$aName"] = "$aValue"; + // Remove unececsary attributes + if (isset($attributes['script'])) unset($attributes['script']); + if (isset($attributes['spellid'])) unset($attributes['spellid']); + //if (isset($attributes['id'])) unset($attributes['id']); + //if (isset($attributes['conjureId'])) unset($attributes['conjureId']); + if (isset($attributes['function'])) unset($attributes['function']); + + // Alias attributes + if (isset($attributes['level'])) $attributes['lvl'] = $attributes['level']; + if (isset($attributes['magiclevel'])) $attributes['maglv'] = $attributes['magiclevel']; + + // Populate type attributes + foreach (array_keys($attributes) as $attr) { + if (!in_array($attr, $type_attr[$type])) + $type_attr[$type][] = $attr; + } + // Get spell groups + if (isset($attributes['group'])) { + if (!in_array($attributes['group'], $groups)) + $groups[] = $attributes['group']; + } + // Get spell vocations + $vocations = array(); + foreach ($spell->vocation as $vocation) { + foreach ($vocation->attributes() as $attributeName => $attributeValue) { + if ("$attributeName" == "name") { + $vocId = vocation_name_to_id("$attributeValue"); + $vocations[] = ($vocId !== false) ? $vocId : "$attributeValue"; + } elseif ("$attributeName" == "id") { + $vocations[] = (int)"$attributeValue"; + } + } + } + // Exclude monster spells (Monster spells looks like this on the ORTS data pack) + $words = (isset($attributes['words'])) ? $attributes['words'] : false; + // Also exclude "house spells" such as aleta sio. + $name = (isset($attributes['name'])) ? $attributes['name'] : false; + if (substr($words, 0, 3) !== '###' && substr($name, 0, 5) !== 'House') { + // Build full spell list where the spell name is the key to the spell array. + $spells[$type][$name] = array('vocations' => $vocations); + // Populate spell array with potential relevant attributes for the spell type + foreach ($type_attr[$type] as $att) + $spells[$type][$name][$att] = (isset($attributes[$att])) ? $attributes[$att] : false; + } + } + + // Sort the spell list properly + foreach (array_keys($spells) as $type) { + usort($spells[$type], function ($a, $b) { + if (isset($a['lvl'])) + return $a['lvl'] - $b['lvl']; + if (isset($a['maglv'])) + return $a['maglv'] - $b['maglv']; + return -1; + }); + } + $spellsCache->setContent($spells); + $spellsCache->save(); + } else { + echo "

    Failed to load engine/XML/spells.xml file.

    "; + } + } else { + $spells = $spellsCache->load(); + ?> +
    + +
    + load(); +} +// End loading spell list + +if ($spells) { + // Preparing data + $configVoc = $config['vocations']; + $types = array_keys($spells); + $itemServer = 'http://'.$config['shop']['imageServer'].'/'; + + // Filter spells by vocation + $getVoc = (isset($_GET['vocation'])) ? getValue($_GET['vocation']) : 'all'; + if ($getVoc !== 'all') { + $getVoc = (int)$getVoc; + foreach ($types as $type) + foreach ($spells[$type] as $name => $spell) + if (!empty($spell['vocations'])) + if (!in_array($getVoc, $spell['vocations'])) + unset($spells[$type][$name]); + } + + // Render HTML + ?> + +

    Spells

    + +
    + + + +
    + +

    Spell types:

    +
      + +
    • + +
    + +

    Instant Spells

    + Jump to top + + + + + + + + + + + + + + + + + + + +
    NameWordsLevelManaVocations
    ', $names); + } + } + ?>
    + +

    Magical Runes

    + Jump to top + + + + + + + + + + + + + + + + + + + +
    NameLevelMagic LevelImageVocations
    Rune image', $names); + } + } + ?>
    + + +

    Conjure Spells

    + Jump to top + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameWordsLevelManaSoulChargesImageVocations
    Rune image', $names); + } + } + ?>
    + Jump to top + + +

    Spells

    +

    Spells have currently not been loaded into the website by the server admin.

    + $spells) { + data_dump($spells, false, "Type: $type"); +} + +// All spell attributes? +'group', 'words', 'lvl', 'level', 'maglv', 'magiclevel', 'charges', 'allowfaruse', 'blocktype', 'mana', 'soul', 'prem', 'aggressive', 'range', 'selftarget', 'needtarget', 'blockwalls', 'needweapon', 'exhaustion', 'groupcooldown', 'needlearn', 'casterTargetOrDirection', 'direction', 'params', 'playernameparam', 'conjureId', 'reagentId', 'conjureCount', 'vocations' +*/ +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/sub.php b/app/ZnoteAAC/sub.php new file mode 100644 index 0000000..e35fc59 --- /dev/null +++ b/app/ZnoteAAC/sub.php @@ -0,0 +1,10 @@ +Sub page not recognized.

    The sub page you requested is not recognized.

    '; + } +} +else echo '

    System disabled.

    The sub page system is disabled.

    '; +require_once 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/success.php b/app/ZnoteAAC/success.php new file mode 100644 index 0000000..b53d677 --- /dev/null +++ b/app/ZnoteAAC/success.php @@ -0,0 +1,4 @@ + +

    Success!

    +Go + \ No newline at end of file diff --git a/app/ZnoteAAC/support.php b/app/ZnoteAAC/support.php new file mode 100644 index 0000000..0918414 --- /dev/null +++ b/app/ZnoteAAC/support.php @@ -0,0 +1,54 @@ +

    Support in-game

    hasExpired()) { + // Fetch all staffs in-game. + if ($config['ServerEngine'] == 'TFS_03') { + $staffs = support_list03(); + } else $staffs = support_list(); + // Fetch group ids and names from config.php + $groups = $config['ingame_positions']; + // Loops through groups, separating each group element into an ID variable and name variable + foreach ($groups as $group_id => $group_name) { + // Loops through list of staffs + if (!empty($staffs)) + foreach ($staffs as $staff) { + if ($staff['group_id'] == $group_id) $srtGrp[$group_name][] = $staff; + } + } + if (!empty($srtGrp)) { + $cache->setContent($srtGrp); + $cache->save(); + } +} else { + $srtGrp = $cache->load(); +} +$writeHeader = true; +if (!empty($srtGrp)) { + foreach (array_reverse($srtGrp) as $grpName => $grpList) { + ?> + + + + + + + + '; + echo ""; + echo ''; + echo ""; + echo ''; + } + } + ?> +
    GroupNameStatus
    ". $grpName ."'. $char['name'] .'". online_id_to_name($char['online']) ."
    + '; include 'layout/overall/footer.php'; ?> \ No newline at end of file diff --git a/app/ZnoteAAC/topguilds.php b/app/ZnoteAAC/topguilds.php new file mode 100644 index 0000000..fc4d18b --- /dev/null +++ b/app/ZnoteAAC/topguilds.php @@ -0,0 +1,155 @@ +hasExpired()) { + $guilds = mysql_select_multi("SELECT `g`.`id` AS `id`, `g`.`name` AS `name`, COUNT(`g`.`name`) as `frags` FROM `players` p LEFT JOIN `player_deaths` pd ON `pd`.`killed_by` = `p`.`name` LEFT JOIN `guild_membership` gm ON `p`.`id` = `gm`.`player_id` LEFT JOIN `guilds` g ON `gm`.`guild_id` = `g`.`id` WHERE `pd`.`unjustified` = 1 GROUP BY `name` ORDER BY `frags` DESC, `name` ASC LIMIT 0, 10;"); + + $cache->setContent($guilds); + $cache->save(); + } else { + $guilds = $cache->load(); + } + $count = 1; + + function convert_number_to_words($number) { + + $hyphen = '-'; + $conjunction = ' and '; + $separator = ', '; + $negative= 'negative '; + $decimal = ' point '; + $dictionary = array( + 0 => 'zero', + 1 => 'first', + 2 => 'second', + 3 => 'third', + 4 => 'fourth', + 5 => 'fifth', + 6 => 'sixth', + 7 => 'seventh', + 8 => 'eighth', + 9 => 'ninth', + 10 => 'tenth', + 11 => 'eleventh', + 12 => 'twelve', + 13 => 'thirteen', + 14 => 'fourteen', + 15 => 'fifteen', + 16 => 'sixteen', + 17 => 'seventeen', + 18 => 'eighteen', + 19 => 'nineteen', + 20 => 'twenty', + 30 => 'thirty', + 40 => 'fourty', + 50 => 'fifty', + 60 => 'sixty', + 70 => 'seventy', + 80 => 'eighty', + 90 => 'ninety', + 100 => 'hundred', + 1000 => 'thousand', + 1000000 => 'million', + 1000000000 => 'billion', + 1000000000000 => 'trillion', + 1000000000000000 => 'quadrillion', + 1000000000000000000 => 'quintillion' + ); + + if (!is_numeric($number)) { + return false; + } + + if (($number >= 0 && (int) $number < 0) || (int) $number < 0 - PHP_INT_MAX) { + // overflow + trigger_error( + 'convert_number_to_words only accepts numbers between -' . PHP_INT_MAX . ' and ' . PHP_INT_MAX, + E_USER_WARNING + ); + return false; + } + + if ($number < 0) { + return $negative . convert_number_to_words(abs($number)); + } + + $string = $fraction = null; + + if (strpos($number, '.') !== false) { + list($number, $fraction) = explode('.', $number); + } + + switch (true) { + case $number < 21: + $string = $dictionary[$number]; + break; + case $number < 100: + $tens = ((int) ($number / 10)) * 10; + $units = $number % 10; + $string = $dictionary[$tens]; + if ($units) { + $string .= $hyphen . $dictionary[$units]; + } + break; + case $number < 1000: + $hundreds = $number / 100; + $remainder = $number % 100; + $string = $dictionary[$hundreds] . ' ' . $dictionary[100]; + if ($remainder) { + $string .= $conjunction . convert_number_to_words($remainder); + } + break; + default: + $baseUnit = pow(1000, floor(log($number, 1000))); + $numBaseUnits = (int) ($number / $baseUnit); + $remainder = $number % $baseUnit; + $string = convert_number_to_words($numBaseUnits) . ' ' . $dictionary[$baseUnit]; + if ($remainder) { + $string .= $remainder < 100 ? $conjunction : $separator; + $string .= convert_number_to_words($remainder); + } + break; + } + + if (null !== $fraction && is_numeric($fraction)) { + $string .= $decimal; + $words = array(); + foreach (str_split((string) $fraction) as $number) { + $words[] = $dictionary[$number]; + } + $string .= implode(' ', $words); + } + + return $string; +} + +if (!empty($guilds) && $guilds !== false) { + ?> +

    Top 10 guilds with most frags

    + + + + + + + + + + + + + +
    #Name:Frags:
    + No frags yet.'; +} +include 'layout/overall/footer.php'; ?> diff --git a/app/ZnoteAAC/toponline.php b/app/ZnoteAAC/toponline.php new file mode 100644 index 0000000..308bb29 --- /dev/null +++ b/app/ZnoteAAC/toponline.php @@ -0,0 +1,81 @@ +'.$hours.'h '.$minutes.'m'; +} +function hours_and_minutes($value, $color = 1) +{ + $hours = floor($value / 3600); + $value = $value - $hours * 3600; + $minutes = floor($value / 60); + if($color != 1) + return ''.$hours.'h '.$minutes.'m'; + else + if($hours >= 12) + return ''.$hours.'h '.$minutes.'m'; + elseif($hours >= 6) + return ''.$hours.'h '.$minutes.'m'; + else + return ''.$hours.'h '.$minutes.'m'; +} +if(empty($type)) + $znotePlayers = mysql_select_multi('SELECT * FROM `znote_players` AS `z` JOIN `players` AS `p` WHERE `p`.`id`=`z`.`player_id` and `p`.`group_id` < 3 ORDER BY `onlinetimetoday` DESC LIMIT '.$limit); +elseif($type == "sum") + $znotePlayers = mysql_select_multi('SELECT * FROM `znote_players` AS `z` JOIN `players` AS `p` WHERE `p`.`id`=`z`.`player_id` and `p`.`group_id` < 3 ORDER BY `z`.`onlinetimeall` DESC LIMIT '. $limit); +elseif($type >= 1 && $type <= 4) + $znotePlayers = mysql_select_multi('SELECT * FROM `znote_players` AS `z` JOIN `players` AS `p` WHERE `p`.`id`=`z`.`player_id` and `p`.`group_id` < 3 ORDER BY `onlinetime' . (int) $type . '` DESC LIMIT '.$limit); + +echo '

    Most online on' .$config['site_title'] . '

    +
    + + + '; +if($type == "sum") + echo ''; +else + echo ''; +for($i = 3; $i >= 2; $i--) +{ + if($type == $i) + echo ''; + else + echo ''; +} +if($type == 1) + echo ''; +else + echo ''; +if(empty($type)) + echo ''; +else + echo ''; +echo ''; +$number_of_rows = 1; +if($znotePlayers) +foreach($znotePlayers as $player) +{ + echo ''; + echo ''; + $number_of_rows++; + echo ''; +} +echo '
    #
    Name
    Total
    Total
    '.$i.' Days Ago
    '.$i.' Days Ago
    1 Day Ago
    1 Day Ago
    Today
    Today
    '. $number_of_rows . '.
    ' .$player['name']. ''; + echo '
    ' .$player['level']. ' '.htmlspecialchars(vocation_id_to_name($player['vocation'])).' '; + echo '
    ' .onlineTimeTotal($player['onlinetimeall']).'
    '.hours_and_minutes($player['onlinetime3']).'
    '.hours_and_minutes($player['onlinetime2']).'
    '.hours_and_minutes($player['onlinetime1']).'
    '.hours_and_minutes($player['onlinetimetoday']).'
    '; +?> + diff --git a/app/ZnoteAAC/twofa.php b/app/ZnoteAAC/twofa.php new file mode 100644 index 0000000..c90a5c2 --- /dev/null +++ b/app/ZnoteAAC/twofa.php @@ -0,0 +1,57 @@ + +

    Server compatibility error

    +

    Sorry, this server is not compatible with Two-Factor Authentication.
    + TFS 1.2 or higher is required to run two-factor authentication, grab it + here.

    + +

    Two-Factor Authentication

    +

    Account security with Two-factor Authentication: .

    + + +

    Login with a token generated from this QR code to activate:

    + +

    Click HERE to disable Two-Factor Authentication and generate a new QR code.

    + + + " + alt="Two-Factor Authentication QR code image for this account." + /> + +

    How to use:

    +
      +
    1. Download an authenticator app for free on your mobile phone like Authy (Android), (iPhone) or Google Authenticator (Android), (iPhone).
    2. +
    3. Scan the QR image with the app on your phone to create a Two-Factor account for this server.
    4. +
    5. Logout, then login with username, password and token generated from your phone to enable Two-Factor Authentication.
    6. +
    + diff --git a/app/ZnoteAAC/twtrNews.php b/app/ZnoteAAC/twtrNews.php new file mode 100644 index 0000000..9c672f6 --- /dev/null +++ b/app/ZnoteAAC/twtrNews.php @@ -0,0 +1,10 @@ + + diff --git a/app/ZnoteAAC/voting.php b/app/ZnoteAAC/voting.php new file mode 100644 index 0000000..231c763 --- /dev/null +++ b/app/ZnoteAAC/voting.php @@ -0,0 +1,73 @@ +Something went wrong! Could not make a vote request.

    '; + } else { + header('Location: ' . $result['voteLink']); + die; + } + } else { + $result = checkHasVoted($user_data['id'], $otservers_eu_voting); + if ($result !== false) { + if ($result['voted'] === true) { + $points = $otservers_eu_voting['points']; + $pointsText = $points === '1' ? 'point' : 'points'; + mysql_update("UPDATE `znote_accounts` SET `points` = `points` + '$points' WHERE `account_id`=" . $user_data['id']); + echo "

    Thank you for voting! You have been rewarded with $points $pointsText!

    "; + } else { + echo '

    It does not seem like you have voted.

    '; + } + } else { + echo '

    Could not verify that you have voted.

    '; + } + } + } else { + header('Location: ' . $otservers_eu_voting['simpleVoteUrl']); + die; + } +} else { + echo '

    Voting is not enabled.

    '; +} + +include 'layout/overall/footer.php'; + +function vote($otUserId, $otservers_eu_voting) { + $context = stream_context_create([ + 'http' => [ + 'header' => "Content-type: application/json", + 'method' => 'POST', + 'content' => json_encode([ + 'otUserId' => $otUserId, + 'secretToken' => $otservers_eu_voting['secretToken'], + 'landingPage' => $otservers_eu_voting['landingPage'] + ]) + ] + ]); + $result = file_get_contents($otservers_eu_voting['voteUrl'], false, $context); + return $result !== false ? json_decode($result, true) : false; +} + +function checkHasVoted($otUserId, $otservers_eu_voting) { + $context = stream_context_create([ + 'http' => [ + 'header' => "Content-type: application/json", + 'method' => 'POST', + 'content' => json_encode([ + 'otUserId' => $otUserId, + 'secretToken' => $otservers_eu_voting['secretToken'], + 'consume' => true + ]) + ] + ]); + $result = file_get_contents($otservers_eu_voting['voteCheckUrl'], false, $context); + return $result !== false ? json_decode($result, true) : false; +} diff --git a/b00kw0rm.txt b/b00kw0rm.txt new file mode 100644 index 0000000..d94e99a --- /dev/null +++ b/b00kw0rm.txt @@ -0,0 +1 @@ +libmariadb3 libluajit-5.1-2 libboost-filesystem1.74.0 libpugixml1v5 libcrypto++8 diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..8c3aba7 --- /dev/null +++ b/dockerfile @@ -0,0 +1,24 @@ +FROM debian:bullseye +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt update && apt-get upgrade -y && apt-get install -y git cmake build-essential libluajit-5.1-dev libmariadb-dev-compat libboost-date-time-dev libboost-system-dev libboost-iostreams-dev libpugixml-dev libcrypto++-dev libfmt-dev zlib1g-dev libgmp-dev libboost-all-dev curl iproute2 + +#RUN useradd -m -s /bin/bash tibia && mv /app/SabrehavenServer /home/tibia && chown -R tibia:tibia /home/tibia/SabrehavenServer +RUN useradd -m -s /bin/bash tibia +RUN echo "tibia:tibia" | chpasswd +#USER tibia + +WORKDIR /home/tibia + + +COPY app /home/tibia + + +#RUN mv SabrehavenServer /home/tibia + +#RUN chown -R tibia:tibia /home/tibia/SabrehavenServer + +ENTRYPOINT ["/bin/bash"] + +ENV TIBIA_VERSION="8.0"