Jump to content

Tokei

Members
  • Posts

    690
  • Joined

  • Last visited

  • Days Won

    99

Everything posted by Tokei

  1. Hmm, is that even a GRF...? You'd have to show me the first ~50 bytes of the GRF (or upload the GRF somewhere) to have a look. If you have to manually change the header, something's definitely not right here. The magic header is always Master of Magic. (Also, that color theme is quite broken. That's not how it's supposed to look at all.)
  2. That's normal. It doesn't really make sense to send a new walking packet for your own character after using @refresh. But you can forcefully send it again in the clif_refresh function (in clif.cpp). Just add the following at the end of the function. struct unit_data* ud = unit_bl2ud(&sd->bl); if (ud && ud->walktimer != INVALID_TIMER) { unit_walktoxy(&sd->bl, ud->to_x, ud->to_y, 4); } Though, it will look a tad weird because the character has to walk from a center cell, so it will "walk back" when using @refresh. You could also send a "unit_stop_walking(&sd->bl, USW_NONE);" if you want the character to stop moving instead. Whichever you prefer.
  3. Hello, I'll be adding a better guideline on the encryption window as I can understand the confusion there. You are correct, the GRF Encryption tool window is only meant to create the client files (client.exe + cps.dll). The "Encrypt GRF" option was removed from that window because it was redundant. To encrypt your GRF, you have to right-click a folder and select encrypt: This does the same thing as the previously removed option. That is also how you're supposed to encrypt your Thor patches when you want to patch encrypted content. As for the password prompt when opening the GRF, that is mostly for visual effect in the first place. An encrypted GRF may not have this prompt. Also, the GRFs can be partially encrypted, so if you choose "Cancel" when this prompt shows up, you can still browse the GRF depending on what you did.
  4. Updated to 1.8.8.4 to fix this issue. The flat maps tool has been updated as well. Hello, Which client version are you using? Which version of GRF Editor are you using? Which Visual C++ redistributables version did you install?
  5. The emblem issue should be fixed in 1.8.8.2.
  6. Hmm, alright, since I'm unable to reproduce this issue on my end, I can only go with a trial and error approach. @Mister Noob @LearningRO @Vashtido Please PM me on Discord (tokei / 95599057182920704) if you're willing to help figure out the issue. I'll be sending you unprotected debug versions of the encryption tool.
  7. I was unable to reproduce the issue. Can you show me what the emblem received by the emulator was? It will be stored in your SQL database in the "guild" table under the "emblem_data" field. (You should check the previous field first of course.)
  8. Heya, This tool is no longer supported and will not work with recent rAthena versions.
  9. If you're asking how to make a similar GRF, that is from File > Save advanced > Encrypt File Table. If you're asking how to read a similar GRF, that is not made available.
  10. Updated to 1.8.7.7: Updated the encryption library for added security. This new version may not work in older clients (hard to test on my end, do let me know). This version requires Microsoft Visual C++ 2022 (x86): https://aka.ms/vs/17/release/vc_redist.x86.exe This is also required for your players. It is usually already installed for most, but some may not have it. The previous version of the encryption required VC++ 2010 (x86). A custom approach as mentioned in the above post (https://rathena.org/board/topic/77080-grf-grf-editor/?do=findComment&comment=432591) will always be more secure. If you do decide to rename cps.dll to something else, then you'll have to ensure your previous encryption cps.dll no longer exists. Otherwise, this will be pointless. You can leave the regular cps.dll from Gravity, that will not conflict.
  11. Updated to 1.8.7.6: Added support for using different GRF magic headers (Master of Magic). It can be modified via Container options > Grf type properties > Magic header (This also means that modifying your GRF header now has no real purpose.) Added support for GRF version 0x300 (from jRO). You can refer to this post: Look at the compress/uncompress methods. The project files are mostly about encryption, but if you ignore that part, the compress/uncompress methods are still going to be used by the client. Just copy the method signatures and make a new project from scratch, or copy this one. uncompress is meant for decompressing the GRF entry. compress is meant for compressing emblems (it is used by the emulator, so leave zlib compression for this; unless you want to change the compression server-side as well). compress2 is where you'd define your own compression method (this is a custom exported method used for GRF Editor, to not conflict with the default compress method; if not defined, it will load compress instead).
  12. It took me some time to figure out what the concern was, but I think I got it. This comes from a confusion on what the while-loop condition means and the misconception of how timers work in the emulator. First thing first, sleep_db is a linked list, each node is connected to the next node, and the final node is connected to a null pointer. So eventually, node->next will return a null pointer, and it will break out of the while-loop. This is your typical way of going through a linked list and this is what we're doing here. We're just going through a linked list, nothing more. As for the "while (node && st->sleep.timer != INVALID_TIMER)" condition, think of it more like this: if (st->sleep.timer != INVALID_TIMER) { while (node) { if (something) { ... break; } node = node->next; } } It simply combines the condition for simplicity. If the timer is already marked as INVALID_TIMER, then it doesn't need to remove the linked node nor does it need to delete the timer. This has nothing to do with "waiting for the timer to finish", it doesn't do that at all. It's a condition check for whether or not it should bother deleting the timer. As you've said before, rAthena is indeed a single threaded application, so waiting for a timer to finish directly doesn't make sense and I want to reiterate that this isn't what this code does. Also, I said this previously, but this code is copied from run_script_timer. The code you're worried about is already currently running on your server, so if there was a problem with it, you'd have known by now (it's being executed dozens of time every second as well). __ Quick note on timers: after using add_timer, no threads are created. Your timer tick is added to a static list and then checked constantly through the core loop of the emulator in core.cpp (via do_timer): #ifndef MINICORE if( !this->m_run_once ){ // Main runtime cycle while( this->get_status() == e_core_status::RUNNING ){ t_tick next = do_timer( gettick_nocache() ); this->handle_main( next ); } } #endif Once the your timer tick is expired, the code will be executed and the timer will be removed. It's rather simple in the end Though on that note... That's also why you'll notice that using small sleep timers (like "sleep 100;" in a script) is often not recommend if you rely on these small amounts. The timers are always delayed by 0~20 ms because the core loop for timers only check at a minimum interval to not overload the emulator. So in such a scenario, you'll notice that doing "sleep 100;" 50 times in a row will take roughly 6 seconds rather than 5 seconds. It's quite noticeable after a while, and that's why you should rely more on OnTimer labels instead (those are still delayed by that 0~20 ms, but the delays don't stack up, so they end up being more accurate). Anyhow, hopefully this helps some people deal with timers both in the source and in scripts. (So yes, this while-loop is safe!)
  13. You mean the loop in script_sleep_resume? It will leave the loop once the timer is found in sleep_db. Then it's erased/deleted and set to INVALID_TIMER. There is nothing blocking as far as I can tell. This code is copied from the run_script_timer timer function, which resumes a script after sleep/sleep2 is executed. Or are you talking about another loop or am I missing something here?
  14. Heya, Personally, I always disliked that command because it's awkward to use. The answer from Winterfox probably works, but if you want to have more "freedom" when using the command, you can instead my custom command "unitwalk_wait" (you'll have to apply the diff file yourself, of course). /// Makes the unit walk to target position. /// Returns if it was successful. /// /// unitwalk_wait(<unit_id>,<x>,<y>{,<timeout>}) -> <bool> Basically, the script will pause until the unit has reached its destination (or until it times out). Here is a script example: prontera,150,186,4 script test_walk 77,{ mes "Move the NPC."; L_Again: getmapxy(.@m$, .@npc_x, .@npc_y, BL_NPC, getnpcid(0)); switch(select("Left:Right:Scripted path")) { case 1: unitwalk_wait getnpcid(0), .@npc_x - 3, .@npc_y; break; case 2: unitwalk_wait getnpcid(0), .@npc_x + 3, .@npc_y; break; case 3: clear; mes "Give me a moment...!"; close2; donpcevent strnpcinfo(0) + "::OnWalk"; end; } goto L_Again; end; OnWalk: .@npcid = getnpcid(0); unitwalk_wait .@npcid, 150, 187; misceffect 150; npctalk "Here I go!"; sleep 1500; unitwalk_wait .@npcid, 155, 187; unitwalk_wait .@npcid, 160, 187; emotion E_GASP; npctalk "What a rough corner..."; sleep 1500; unitwalk_wait .@npcid, 160, 180; npctalk "I have walked plenty, no more sir."; end; } Source changes: diff --git a/src/map/script.cpp b/src/map/script.cpp index 8fdc6fabc..16a54d81b 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -4256,6 +4256,24 @@ TIMER_FUNC(run_script_timer){ return 0; } +int script_sleep_resume(struct script_state* st) { + struct linkdb_node* node = (struct linkdb_node*)sleep_db; + + while (node && st->sleep.timer != INVALID_TIMER) { + if ((int)__64BPRTSIZE(node->key) == st->oid && ((struct script_state*)node->data)->sleep.timer == st->sleep.timer) { + script_erase_sleepdb(node); + delete_timer(st->sleep.timer, run_script_timer); + st->sleep.timer = INVALID_TIMER; + break; + } + node = node->next; + } + if (st->state != RERUNLINE) + st->sleep.tick = 0; + run_script_main(st); + return 0; +} + /** * Remove sleep timers from the NPC * @param id: NPC ID @@ -19782,6 +19800,66 @@ BUILDIN_FUNC(unitwalk) return SCRIPT_CMD_SUCCESS; } +/// Makes the unit walk to target position. +/// Returns if it was successful. +/// +/// unitwalk_wait(<unit_id>,<x>,<y>{,<timeout>}) -> <bool> +BUILDIN_FUNC(unitwalk_wait) +{ + struct block_list* bl; + struct unit_data* ud = NULL; + const char* cmd = script_getfuncname(st); + + if (!script_rid2bl(2, bl)) + { + script_pushint(st, 0); + return SCRIPT_CMD_FAILURE; + } + + ud = unit_bl2ud(bl); + + // Timeout reached, the unit didn't walk to the destination... just ignore and move to the next script command. + if (st->sleep.tick != 0) { + st->state = RUN; + st->sleep.tick = 0; + return SCRIPT_CMD_SUCCESS; + } + + // Unit was already forced to walk. + if (ud != nullptr && ud->state.force_walk) { + script_pushint(st, 0); + ShowWarning("buildin_%s: Unit has already been forced to walk and not reached it's destination yet.\n", cmd); + return SCRIPT_CMD_FAILURE; + } + + if (bl->type == BL_NPC) { + if (!((TBL_NPC*)bl)->status.hp) + status_calc_npc(((TBL_NPC*)bl), SCO_FIRST); + else + status_calc_npc(((TBL_NPC*)bl), SCO_NONE); + } + + int x = script_getnum(st, 3); + int y = script_getnum(st, 4); + int timeout = script_hasdata(st, 5) ? script_getnum(st, 5) : 5000; + + if (script_pushint(st, unit_can_reach_pos(bl, x, y, 0))) { + if (ud != nullptr) + ud->state.force_walk = true; + add_timer(gettick() + 50, unit_delay_walktoxy_timer, bl->id, (x << 16) | (y & 0xFFFF)); // Need timer to avoid mismatches + } + + struct npc_data* nd = map_id2nd(st->oid); + + if (ud && nd) { + st->state = RERUNLINE; + st->sleep.tick = timeout; + ud->walk_done_script = st->id; + } + + return SCRIPT_CMD_SUCCESS; +} + /// Kills the unit. /// /// unitkill <unit_id>; @@ -27693,6 +27771,7 @@ struct script_function buildin_func[] = { BUILDIN_DEF(setunitdata,"iii"), BUILDIN_DEF(unitwalk,"iii?"), BUILDIN_DEF2(unitwalk,"unitwalkto","ii?"), + BUILDIN_DEF(unitwalk_wait,"iii?"), BUILDIN_DEF(unitkill,"i"), BUILDIN_DEF(unitwarp,"isii"), BUILDIN_DEF(unitattack,"iv?"), diff --git a/src/map/script.hpp b/src/map/script.hpp index b7beb8483..4c499359a 100644 --- a/src/map/script.hpp +++ b/src/map/script.hpp @@ -2231,6 +2231,7 @@ int conv_num(struct script_state *st, struct script_data *data); const char* conv_str(struct script_state *st,struct script_data *data); void pop_stack(struct script_state* st, int start, int end); TIMER_FUNC(run_script_timer); +int script_sleep_resume(struct script_state* st); void script_stop_sleeptimers(int id); struct linkdb_node *script_erase_sleepdb(struct linkdb_node *n); void script_attach_state(struct script_state* st); diff --git a/src/map/unit.cpp b/src/map/unit.cpp index a643967a0..339a025a1 100644 --- a/src/map/unit.cpp +++ b/src/map/unit.cpp @@ -488,6 +488,15 @@ static TIMER_FUNC(unit_walktoxy_timer) ud->state.force_walk = false; + if (ud->walk_done_script) { + ud->state.walk_script = false; + struct script_state*st = (struct script_state* )idb_get(st_db, ud->walk_done_script); + + if (st) { + script_sleep_resume(st); + } + } + if (ud->walk_done_event[0]){ char walk_done_event[EVENT_NAME_LENGTH]; diff --git a/src/map/unit.hpp b/src/map/unit.hpp index cfd932615..76ccc0c8f 100644 --- a/src/map/unit.hpp +++ b/src/map/unit.hpp @@ -61,6 +61,7 @@ struct unit_data { bool force_walk; ///< Used with script commands unitwalk/unitwalkto. Disables monster idle and random walk. } state; char walk_done_event[EVENT_NAME_LENGTH]; + int walk_done_script; ///Script OID to run after a walking event is over char title[NAME_LENGTH]; int32 group_id; I find this command much easier to use. You can also mix it up with the original "unitwalk" when multiple NPCs walk at the same time, as long as unitwalk_wait is the last one. Anyhow, you'll figure it out if you do end up using this command (or whoever else does). unitwalk_wait.diff
  15. Heya, There hasn't been a whole lot of changes in the map format. So... just open up BrowEdit 3 > load the map > data\map.rsw > Edit ... > Change the rsw version from 0x206 to... 0x204 would be good enough. Or you could also just lower the build number from 214 to 185. You could lower it all the way down to 0x201 and it wouldn't change much at all (0x201 is the most common map format, practically any client can read this one). Then just save the map. (Edit: Updating your client is the proper way obviously, but if you're not ready to update yet, then the above will work just fine.)
  16. Long story short, you need to use this: As for why, palette 0 uses the palette provided in the sprite file. That's why it shows up properly in Act Editor. Custom palettes, which is probably what you're using like everyone else, uses a different placement for the gradients. They do that because that way, they're compatible with 3rd classes (plus it's better organized than whatever fuck up Gravity did). The mount is transparent because the mount gradients redirect to the pink lines, which is wrong. Using the corrected version from Haziel should fix that.
  17. Care to elaborate on that? I'm always up for improving GRF Editor, but without any information, this doesn't really mean anything for me.
  18. Heya, I've been receiving a lot of messages regarding encryption (again), so I wanted to clarify a few things: Your PMs regarding encryption matters will be redirected to this post. I will not be making a paid version of GRF Editor for encryption. Yes, I am aware there is someone selling a decryption tool. No, this has nothing to do with GRF Editor being open source. GRF Editor has been open source for a very long time. No, I will not be updating the encryption DLL for the foreseeable future. GRF Editor now has an updated encryption tool to block this decryption tool. GRF Editor is open source (https://github.com/Tokeiburu/GRFEditor), there's no need to pay to see the source (a few people made this request already...). As of GRF Editor 1.8.7.2 and above, it is now possible to make your own custom encryption library and link it with GRF Editor (this is meant for developers). Here is the project file: comp_x86.rar This is a C++ project made for Visual Studio 2022. You can use a lower toolset for older clients as it was originally made using Visual Studio 2010, which should be more than enough. The respective Visual C++ Redistributables should be installed (x86 for the client and x64 for GRF Editor). When making the encrypt.dll for GRF Editor: Use "Release" and "x64". The DLL will be compiled at comp_x86\Release\x64_GrfEditor\encrypt.dll "#define GRF_EDITOR" in cps.h must be defined in cps.h. This exposes the encrypt/decrypt functions for GRF Editor. The DLL can then be loaded in GRF Editor from Tools > Settings > Application > Encryption method... GRF Editor uses these methods for encryption/decryption: DLL int encrypt(BYTE* key, UInt32 key_len, BYTE* compressed_data, UInt32 compressed_len, UInt32 uncompressed_len); DLL int decrypt(BYTE* key, UInt32 key_len, BYTE* compressed_data, UInt32 compressed_len, UInt32 uncompressed_len); The compressed_data length cannot be modified. The uncompressed_len can be used for your encryption algorithm, but otherwise it isn't useful for anything. The encryption happens after the compression (raw data > zlib/lzma compression > encryption). It is the final method applied on the data. You can make a custom compression library to change the compression data length. The "key" parameter is the one used by GRF Editor when loading an encryption key from the software. You're free to ignore this parameter if you're not going to use this. The key from GRF Editor is 256 bytes in length. When making the cps.dll for your client: Use "Release" and "Win32". The DLL will be compiled at comp_x86\Release\x86_Client\cps.dll "#define GRF_EDITOR" in cps.h must NOT be defined. Comment it out if it isn't automatically undefined (using Win32 should undefine it by default). The most important function is "void decryptSub(BYTE* key, UInt32 key_len, BYTE* data, UInt32 data_len, UInt32 seed)" in cps.cpp. The function decrypts the data in the GRF using the key provided by GetGrfEncryptionKey(). This part needs to be defined on your end as the key provided is just a dummy one. Hide it, do whatever you want. By default, the encryption uses an RSA encryption method. This project has no protection on the DLL whatsoever. That part is entirely up to you. Make sure the client-side version of your cps.dll cannot be loaded in GRF Editor directly as a custom compression library as this would... make the whole thing rather pointless. The methods of interest for the client side would be: DLL int uncompress(BYTE* output_data, UInt32* output_len, const BYTE* compressed_data, UInt32 compressed_len) int _encryptedUncompress(BYTE* uncomp, UInt32* uncompLength, const BYTE* comp, UInt32 compLength) If you're a developer and you think your version is better than the one provided by GRF Editor, feel free to PM me as I'll make it as a default option (I will request to see the source however). Likewise, you're also free to sell those encryption DLLs if you want to, that's entirely up to you (I will not be responsible for issues related to third party encryption libraries).
  19. You're trying to define a function within a script; those two should be separated. Usually you'd put functions in a different script file, but that's me nitpicking a little. Your script would look like this: script function F_res... NPC scripts below You have to put the script functions before the NPC scripts otherwise they won't be recognized while parsing the script. But... clearly, you wanted to use callsub rather than callfunc, which is slightly different. "callfunc" functions are global and can be used by all scripts, in different files, while "callsub" are functions that can be used within the current script only. A callsub would look like this: //============================ // functions //============================ // F_restore F_res: .@rental_item = getarg(0); .@item_1 = getarg(1); .@item_2 = getarg(2); .@item_3 = getarg(3); if(rentalcountitem(.@rental_item) < 1){ //... return; // Then you call the function with: callsub "F_res",40081,40225,40016,40082; Your script also relies way too much on gotos. If the goto isn't used more than once, then it has no reason to be. It's harder to follow your script the way it is now than simply putting the code in the right place it belongs to. Well, not that it matters too much, just a general suggestion. Anyway, using callsub, your script would look like this: moc_para01,46,36,4 script Guild Blacksmith 826,{ if(countitem(40056) < 1){ goto notmember; } // Text //============================= soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Oh hello there, how can I help you ?"; switch(select("- Restore:- Upgrade Item Grade:- Cancel")){ case 1: goto restore1; case 2: goto upgradeclass; case 3: end; } end; // Restore //============================= restore1: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Oh, you need restore?"; switch(select("- Restore Information:- Restore Item:- Cancel")){ case 1: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Well, your item usually comes"; mes "with a 7-day rental period,"; mes "but if you wish to extend"; mes "that rental period,"; mes "you can come to me"; mes "for a complete restore."; next; clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "To restore weapons or armor, I will need"; mes "- 1 Restore Token"; mes "- 10 Durengo Coins"; next; clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Just for the record, I will only"; mes "restore Tier 2 and above items."; next; clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "For tools, I will just need"; mes "- 1 Restore Token"; mes "- 5 Durengo Coins"; close; case 2: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Ok then, what item do you need to restore?"; switch(select("- Weapon:- Armor:- Tools:- Cancel")){ case 1: goto wep_restore; case 2: goto armor_restore; case 3: goto tool_restore; case 4: end; } case 3: end; } wep_restore: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Select Weapon Tier"; switch(select("- Tier 2:- Cancel")){ case 1: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Select the weapon you wish to restore"; switch(select("- Improvise Dagger:- Cancel")){ case 1: goto improdaggres; case 2: end; } case 2: end; } end; //============================ // functions //============================ // F_restore F_res: .@rental_item = getarg(0); .@item_1 = getarg(1); .@item_2 = getarg(2); .@item_3 = getarg(3); if(rentalcountitem(.@rental_item) < 1){ clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "You don't have a "; mes ""+ getitemname(.@rental_item) +"."; close; } if(countitem(.@item_1) < 1){ clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "You don't have a "+ getitemname(.@item_1) +"."; close; } if(countitem(.@item_2) < 10){ clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "You don't have 10 "+ getitemname(.@item_2) +"."; close; } clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Great, here is your new "+ getitemname(.@item_3) +"."; delitem (.@rental_item, 1); delitem (.@item_1, 1); delitem (.@item_2, 10); getitem (.@item_3, 1); close; improdaggres: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Select Dagger Grade"; switch(select("- Grade A:- Grade B:- Grade C:- Grade D:- Grade E:- Cancel")){ case 1: callsub "F_res",40081,40225,40016,40082; break; case 2: end; case 3: end; case 4: end; case 5: end; case 6: end; } armor_restore: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Work In Progress"; close; tool_restore: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Work In Progress"; close; // Upgrade //============================= upgradeclass: clear; soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "Work In Progress"; close; // Members //============================= notmember: soundeffect "menu.wav",0; mes "^ce7e00 === Hafiz === ^000000"; mes "You need to be a member before I can assist you."; close; }
  20. This is related to an attempted fix for position lag, and therefore it is intended. It comes from the "clif_fixpos" packet which is triggered after the player uses a skill, as you've noticed. It synchronizes the client position with the server position. You will only see the character moving back if the client and server positions are different (so... it's a good thing?). The above won't help you too much though, sorry! There's a bigger issue at play here, which is to fix the difference between the client and server position instead of relying on the clif_fixpos packet. Even if you stop sending the clif_posfix packet so that the character doesn't change position, the position between the server and client still won't be a match. So fixing the "walking back" issue will just end up moving the problem elsewhere. If you still want to have a feel for it, you can change the line below with a 0, that way your character will no longer walk back. unit_stop_walking(src, 1); // Even though this is not how official works but this will do the trick. bugreport:6829 to unit_stop_walking(src, 0); // Even though this is not how official works but this will do the trick. bugreport:6829 But I don't recommend it.
  21. Well, normally you'd use what you did "--en--", or "--ja--". But if you want to pick the names yourself, you can do this: prontera,99,101,1 script Demon Tomb 565,{ mes "stuff..."; setarray .@mobIds, 1002, 1003, 1004, 1005, 1006; setarray .@mobNames$, "Poring 1", "Poring 2", "Poring 3", "Poring 4", "Poring 5"; .@mobIdx = rand(getarraysize(.@mobIds)); .@mobId = .@mobIds[.@mobIdx]; .@mobName$ = .@mobNames[.@mobIdx]; monster .@map$,99,107,.@mobName$,.@mobId,1,.@label_boss$,2; end; } If you prefer, you can use this approach below as well, same thing (I find it easier to handle in larger scripts, but there's really no difference). prontera,99,101,1 script Demon Tomb 565,{ mes "stuff..."; setarray .@mobIds, 1002, 1003, 1004, 1005, 1006; .@mobId = .@mobIds[rand(getarraysize(.@mobIds))]; monster .@map$,99,107,'mobNames$[.@mobId],.@mobId,1,.@label_boss$,2; end; OnInstanceInit: 'mobNames$[1002] = "Poring 1"; 'mobNames$[1003] = "Poring 2"; 'mobNames$[1004] = "Poring 3"; 'mobNames$[1005] = "Poring 4"; 'mobNames$[1006] = "Poring 5"; end; }
  22. A couple things to learn here: OnInit applies when the script is loaded, while OnInstanceInit applies when the instance is created. So in your case, you want to use OnInstanceInit. "demon_lair" is the base map, but that's not the map inside your instance. You want to use instance_mapname("demon_lair"); Likewise, when you're setting up the event label, it is attempting to call it on the base NPC, not the instanced NPC. So you want to use instance_npcname(strnpcinfo(0)) + "::OnKilledAzhura" "set" is a very much outdated command, use "=" instead. "$AzhuraSpawned" is a global, permanent, variable. It is kept when your server restarts and it is also shared between instances. You want to use 'variable instead. So your script would look something like this (and using menu is weird): demon_lair,99,101,1 script Demon Tomb 565,{ mes "[Tomb]"; mes "This is the resting place of a great demon named Azhura."; next; mes "[Tomb]"; mes "You can summon him through Nightmarish Ornament and Evil Energy."; next; mes "[Tomb]"; mes "Put the Nightmarish Ornament and Evil Energy."; next; switch(select("Yes.:No.")) { case 1: if ('AzhuraSpawned) { mes "The Great Demon Azhura is already here!"; close; } if (countitem(41001) < 2) { mes "You don't have the required items!"; close; } delitem 41001, 2; mapannounce .Map$, "Prepare for the unleashed evil!!", bc_map; 'AzhuraSpawned = 1; monster .Map$, 99, 106, "Great Demon Azhura", 1973, 1, instance_npcname(strnpcinfo(0)) + "::OnKilledAzhura"; close; case 2: mes "[Tomb]"; mes "Come back when you have decided."; close; } end; OnKilledAzhura: 'AzhuraSpawned = 0; end; OnInstanceInit: .Map$ = instance_mapname("demon_lair"); end; }
  23. Hmm, you probably wanted to write countitem(40039) > 0 Since > 1 means that you need 2 items in your inventory for the condition to be true.
  24. I haven't tested this, but you could just remove the 4th job flag (instead of adding the 3rd job flag): changebase roclass(eaclass()&~EAJL_FOURTH); Though I'm sure you'll run into issues somewhere. You could also just use the class costumes, though it's definitely more annoying to setup.
  25. It's a silly error either way. A signed value is converted to an unsigned value in this scenario, and since there is a comparison of n > -1 right prior the other check, then the comparison cannot fail (as both values are positive). Plus it's not like a string length would be 2B+ in length to begin with... you'd probably have to be more concerned about memory usage at this point. As @Chaos92 mentioned, the issue is on rAthena. It'll get fixed eventually.
×
×
  • Create New...