Jump to content

Tero

Members
  • Posts

    58
  • Joined

  • Last visited

  • Days Won

    16

Posts posted by Tero

  1. The idea of critical spell damage doesn't really exist within classic Ragnarok.  If you look at what criticals do, they have two main effects:

    - They ignore the target's physical defense

    - They always hit

    Magic already has both of those properties all the time.  You could potentially make a magic critical ignore the target's magical defense, though there's already cards and equipment which have the effect of ignoring a portion of the target's magic defense.  It also wouldn't be possible to implement the blue critical graphic shown there without modifying the client, since the damage types are specified in the client damage packet.

    Anyway, if you actually wanted to implement something like this, you'd mainly have to change the "is_attack_critical" method.  There's a check to determine if a given attack can critical that looks at whether the attack is a normal attack or one of a small list of skills that can crit, you'd have to add a check to see if the skill is magical, and if it is, it looks at your magic critical rate.  You'd also have to modify the defense calculation later to ignore magic defense on a critical.  It could certainly be done, though I feel like it'd be a slightly weird mechanic.

  2. 7 hours ago, Bringer said:

    @Tero can you make free src for Wizard have ability goes Critical Magic when using Current item or card weapon? Just like at ROM Eternal Love? 

    I think you're saying you want an item or card script that can trigger the Wizard's Magical Amplification ability?  That's fairly straightforward.

     

    If you want a consumable item that gives you magical amplification, put this script on any usable item (the 10 is the level, it could be set to something else).

    { itemskill "HW_MAGICPOWER",10; }

     

    For a card or weapon that triggers it, you could do something like this.  This gives a 10% chance to autocast level 10 magical amplication when using magic.  The "100" is the chance, so if it was instead 500 you'd have a 50% chance to get it.

    { bonus5 bAutoSpell,"HW_MAGICPOWER",10,100,BF_MAGIC,0; }

     

    For more detail on how these types of scripts work, see the item_bonus.txt file in rathena's doc folder.

  3. Well, I'm not quite done yet.  Whenever I'm away from Ragnarok for a while, I tend to come up with new ideas for skills, so I figured I'd do one last round of custom skills for classes that were a bit low in terms of total skill count.  Pretty much every one of these skills required some kind of unique nuance to get it working, so it was a good test of all I've learned coding this game so far.

     

    Assassin:

    Assassin is one of the most changed classes on my server, as I feel it is a problematic class in a number of ways.  It is simultaneously super strong and extremely basic to play, with among the fewest skills in the game and a large portion of its skill tree that no one ever uses.  It also has the second-highest base HP of any class in the game for some reason, despite being extremely hard to hit.  As such, I've endeavored to flesh out their skill tree to encourage build diversity while simultaneously scaling back their tankiness (they now have the lowest max health of any non-mage class).  I think this has mostly worked, but it's had the side effect of making it a significantly harder class to play, and in particular it feels much harder to level at endgame because it's far less self-sufficient now.  I wanted to restore a little bit of that feeling of being a one man army without turning them back into a one man army in guild war, so I came up with this.

    image.png.e1ba3bbfc0cdc4835d89c6234fa69f80.png

    Soul Absorption is a fairly simple passive that restores some HP and SP when you kill things, significantly increasing the levelling efficiency of the class, though it's also now a prerequisite for Soul Breaker, which also scales back the strength of that build to some extent (it was somewhat outperforming the other Assassin builds, especially at low levels).  If you think the idea behind this sounds very similar to the Wizard skill Soul Drain, you're not wrong, and mechanically it's implemented very similarly, though unlike Soul Drain it works with any type of attack, not just single target skills.  This skill does also work in PVP, though I doubt it would be worth the skill points in guild war.  It is fun to grimtooth down a bunch of enemies and get a full heal, though.

     

    Rogue:

    Besides build diversity, something else I want to encourage within the game is class diversity.  In vanilla Ragnarok Online, there's quite a lot of classes to choose from to construct your party.  Most of the classes can do just fine for DPS.  For tanking, you'll usually use a Swordsman class or an Assassin, or maybe a Blacksmith.  Then for healing, you bring a Priest.  See the problem here?  Pretty much no matter how you construct your party, the Priest is kind of essential.  As such, one of the main things I've tried to address is to give various classes more options in terms of healing and support to make non-Priest parties more viable.  For example, Summoner, Bard, and Alchemist all have better healing options, Monk is better for support, and Star Gladiator and Blacksmith are better at tanking.  However, I wanted one more healing option available, but I didn't just want to give another class a standard heal spell.  So I came up with a fairly unique healing option for everyone's favourite jack of all trades, the Rogue.

    image.png.3617e920110951b1b2c10ff31cdc747b.png

    Herbology is a unique skill in a bunch of different ways.  For starters, it's a heal spell that has cast time and is interruptible, so it's not super great to spam during combat.  It also requires an item to use, specifically a Hinalle Leaflet for Levels 1-5, and an Aloe Leaflet for Levels 6-10.  The power of the heal actually resets at level 6, but the level 6+ version is an area heal that can heal multiple people close together, while the former version is single target.  It also removes certain statuses at certain levels, level 3+ removes Bleeding, level 7+ removes Paralysis, and level 9+ removes Infection (which is a new status that Assassin has access to).  Notably, these are all statuses that are hard to get rid of since many status heals don't affect them.  It's intended to be used as a kind of backline heal spell, so it's part of the Bow Rogue skill tree, giving them a bit more support power to compliment their backline DPS, though you can certainly also use it as an out-of-combat heal for standard Rogue if you're willing to spend a few points for it.  It's probably not a particularly class-defining skill but I feel like the idea behind it fits well with the class.  From an implementation standpoint, this is actually kind of complicated due to the fact that it requires different items at different levels, which is not a concept that rathena supports natively (the only other skill that works this way is Potion Pitcher).  Also, you might remember from before that the list of skills that target allies is defined client-side, so I had to cannibalize an existing skill for this.  There was actually only one ally-targeting skill left in the game that wasn't already used, which was GC_Antidote, which was kind of a funny coincidence since this skill actually also heals Assassin's special poison status (infection is actually a modified version of SC_Toxin).

     

    Wizard:

    Of all the classes in the game, Wizard was probably one of the ones I touched the least.  It was always a pretty good class, but with all the other classes getting new skills, it had kind of fallen behind in terms of the number of skills (I was actually a bit shocked by just how small its skill tree was).  For some time now, I've sort of wanted to give Wizard some kind of utility spell, but I hadn't come up with any idea I really liked.  However, the new Assassin ability gave me an idea.  Soul Drain has always been kind of mediocre ability for Wizard, the class doesn't tend to have problems with SP and it doesn't use a ton of single-target spells either, and that ability just felt like it made way more sense for Assassin, so I completely reworked the ability into something totally different.

    image.png.69de067e805a8b7686f14ebf5745c775.png

    Power Drain is now a regular spell with a totally new effect.  It's basically Marionette Control but backwards, when you cast this on an enemy you steal some of their stats for a little while, with the level of the spell determining which stat is stolen.  Like Marionette Control, there's both a status for the caster and the victim, and the caster's stats are increased by 1/10th of the victim's value for the chosen stat, while the victim's stats are decreased instead.  Casting this on an enemy with high stats is very powerful, for example you could steal 12 Dex from an enemy who had 118 Dex, which is a ton, though the effect doesn't last especially long.  I think it's still fun to play around with though.

    image.png.c22070ac8b2f0cc65bf7f71425eebbb2.png

    High Wizard also gets another attack spell in Explosion.  This is just a reworked version of Warlock's Soul Expansion, where it's a slow-casting single target fire spell that hits really hard.  Not too much to say about this, I just always felt that Wizard's fire spells seemed kind of weak.  If you stone curse someone and then do magical amplification before this spell it does bonkers damage, so I guess this increases Wizard's dueling potential in PVP.

    image.png.9d15d777e9af5dcbc9ac935ed7cfb9d1.png

    That's not all for Wizard, though, they also get another new attacking spell in Static Field.  This is kind of like a wind-element version of the Ninja's Blaze Shield spell, in that it creates a zone of damaging magic that can also inflict a status, in this case Paralyse.  It doesn't hit as frequently, but it's still a decent way to slow down attackers.  This skill is actually a reworked version of Shadow Chaser's Chaos Panic skill.  It looks much more like an electric attack to me.

    image.png.c811d1babe7f6728c5f348763da765c8.png

    Speaking of Shadow Chaser skills there's also this.  This is actually what I always expected Shadow Chaser's Dimension Door would do, as similarly-named spells in other games usually have this effect, but instead what it does is it creates a zone on the floor that simply teleports you to a random location if you step on it, which is kind of a troll spell that you can't even use in GVG.  Instead, my version of the spell creates an exit portal wherever you cast the spell, and an entrance portal wherever you were standing.  This results in you warping to the new location immediately, but your allies can use it too.  This is a fun way to take shortcuts or bypass traps in War of Emperium, though Dex does not reduce the spell's casting time and the portal doesn't stay open for very long, so it's not super abusable.  Actually, Wizard also gets Teleport and Warp Portal as well, which are both prerequisites to get this spell, so even if this doesn't have a lot of use outside of WoE they still gain some extra utility.  I think this is enough new tools to play with that Wizard doesn't feel neglected anymore.

    This was probably the hardest one to program.  The entrance portal is actually Manhole, which didn't otherwise exist on my server, but obviously its functionality is totally changed.  The exit portal is the actual Dimension Door spell, but it no longer does anything at all since the trip is one-way (save for the fact that it can be removed by spells like Ganbantein, which also eliminates the entry portal as they are linked to each other).

     

    Anyway, that's probably it for new stuff for a bit.  The classes are looking pretty good now, there's probably mainly just going to be relatively small balance adjustments to skill power and enemy skill lists and such for the next little while.

    • Upvote 1
  4. I'm glad that people enjoyed reading it.  When I started, I figured that maybe I'd somehow find a way to allow rebirth for the classes that couldn't do it (always my pet peeve with the original game), I never figured it would turn into this.  Still, my journey is probably nearing its end.  I know I've said that before, but with any project there does come a time when you have to say "it's done!".  At a certain point, there's simply not much you can add any more that meaningfully adds to the game.  And I've pretty much run out of stuff I can feasibly add.  There are no more maps I can harvest from Renewal.  I've used almost every distinct weapon sprite in the game.  There aren't really that many more skills that can be added without compromising the identity of the classes.  For a few months, updates on the server have slowed as we've approached something resembling "stability".  However, there remains one final frontier to tackle, and that's Guild War.

    image.png.7f7335e9d43c4849f936ca6f3008db35.png

     

    I've always considered Guild War to kind of be the glue that holds Ragnarok Online together as a game.  Ragnarok (at least PRE-RE) does not really have any story of defined goal.  Sure, you can grind your way to max level, and my server does include significantly expanded end-game content in the form of the past, but ultimately the grind on its own can feel a little hollow at times.  One of my beta testers who is new to the game asked me "sure, we can kill some of these bosses now, but what do we really need their items for other than to kill more bosses?".  Of course, we all know the answer is "TO CRUSH OUR ENEMIES WITH AN IRON FIST!".  Ragnarok Online is an incredibly complex game with a nearly infinite number of possible builds you can make, but the complex interplay of these builds can only shine when they're pitted against each other.  The War of Emperium is an incredibly deep and interesting mode that takes all of the elements of the game and gives them their own unique and interesting way to shine.  It is also an absolute nightmare to balance.

     

    I'm not actually talking about the actual gameplay balance of the mode itself.  That is a concern too, of course, but it's something I've had in mind since the very beginning when designing and tweaking skills, so I don't think it should actually be too bad.  No, what I'm talking about are the rewards from the mode that serve as the incentive to play it.  On the official servers, the main incentive to play WOE is to get the God Items.  The God Items are deliberately monstrously overpowered, which is (somewhat) balanced out by the fact that you need rare items from many different castles to assemble them.  (As an aside, I see that in recent patches the god items have actually been nerfed, but to me Mejingjord will always give +40 STR).  This is a problem for smaller servers, though, because you need a massive population to run War of Emperium with many castles at once.  What a lot of small servers do is they only open one castle at a time.  This works well, but it makes the god items impossible to get, which, to be honest, is probably a good thing because they're so gamebreaking, but it also kind of feels lame since most of the other rewards are fairly common things that you can get easily.  No one is really that excited by slotted chainmail anymore.

    image.png.88fd379c2e16aca1bd5f5de00c88df21.png

     

    It was clear from the start that Guild War was going to need new, exclusive rewards.  This was not easy, because I already added a ton of new items to the game.  Clearly, the rewards from the guild war have to be good enough that you actually want them, but not so strong that gaining control of the guild castles quickly becomes an insurmountable advantage.  This is a very delicate balance.  Beyond this, there's the question of how to distribute the rewards.  If we're adding new weapons, different classes equip different weapon types.  We don't want to intentionally favour certain classes or builds by mainly adding good equipment for a single class.  Each castle can spawn 2 distinct treasure boxes, which are unique to that castle.  If only one castle is used, this gives us a total of 16 item slots to work with, which is not even enough for 1 of every weapon type in the game (and we want some lesser rewards like eluniums and OCAs as well).  1 castle isn't enough.

     

    My clue for how to handle this came from the classes in the game.  The game has 10 base classes: Swordman, Acolyte, Mage, Archer, Thief, Merchant, Taekwon, Ninja, Gunslinger, and Summoner.  We can pair these up two classes to a castle, which allows for roughly one box per class.  This sounds pretty good!  Under this scheme, 5 castles would be needed, which would presumably be available on a rotating schedule.  We're getting somewhere, but there's just one more problem.  There are four different guild war realms, Prontera, Geffen, Al De Baran, and Payon (for now, we're leaving War of Emperium 2 out of the equation).  It seems to make sense to pick one castle from each.  But what about the fifth castle?  Well, clearly a sane person would just pick a second castle from one of the realms, but you should know by now that's not going to happen.  No, of course the answer is that we're going to create a totally new fifth castle in its own realm.  The cats have been disrespected for too long, so it's finally time for Malangdo to get its own guild castle.

    image.png.1780dbf0bb1aff53abae2e462a9f3dab.png

     

    Well, maybe "totally new" is overselling it a bit.  It's more like a "significantly altered version of a castle that already exists".  Of the castles I didn't pick, the castle with by far the most interesting layout was payg_cas04, so this will become the basis for malg_cas01.  To the map editor!

     

    image.png.c124b7409cef1e4d597e3d09de04182a.png

    There's a few things I like about this layout, but I think the most interesting thing about it has got to be this Emperium room.  It's huge, there's two routes you can take, you can snipe across the gaps, and there are little barriers that protect you in certain areas.  The coolest thing about it though are those double entry warps at the top!  Spamming spells and traps on warp points is a super strong strategy in WoE, so having a second warp in to the emperium room is a huge deal.  However, in payg_cas04, only one of the warps is actually used!  This won't do, so the first thing we needed to do was to add a new route that could let you get to the second warp point.

     

    image.png.52082c2dba5fba4f34d55a7600d88b0d.png

    Looking at the layout of the castle, which I wasn't planning to change too drastically, there was really only one feasible place that could lead to a new warp, and that's this corridor here at the bottom of this map (the red square indicates where the warp would go).  So this is where the new route would start.  However, this is actually the first interior room in the castle, the warp on the far left leads to another interior room, so clearly this can't just skip you right to the emperium room.  A totally new room is needed that leads to the emperium room.

     

    image.png.829763dff9084683f287c7d28d72d10c.png

    I decided to keep things fairly simple.  The new room is a kind of back hallway that leads to a storage room, with a staircase leading down to the emperium room.  Rather deliberately, this room kinda sucks for the attackers.  It's super narrow, and it's very easy to trap the one-cell passage by that little barricade, or camp behind it.  You can hide in the storage room to avoid someone walking through the hallway with Sight and try to slip past, but it's not hard to check the storage room since it has only one exit.  However, having a second route that leads to the emperium room is already very powerful, as the defenders now need to cover both routes if they don't just want to try to hold the final room.  I think there's an interesting strategy tradeoff if this route is clearly worse than the other route.  Do you check it less often because clearly people won't take the bad route?  Then that kind of makes it actually the good route, because you might encounter much less opposition that way.  In any case, I think this layout is interesting.

     

    image.png.df9829ef152ecafc753eecf6c2bdd341.png

    Of course, I couldn't just slap in a new room and call it a day, I also had to Malangdo up the place.  It now has much more of a tropical theme.  Interestingly, there are actually some GATing errors on this map (the GAT defines where you can and can't walk).  There's a wall on the lower side of this map that you can simply walk through on the official servers, as well as a few misplaced snipe tiles.  I fixed them.  Still, it kind of made me feel good in a way to know that the original map creators are human too.

     

    image.png.3c58686b2c78b21e976aa1b885fd9b1b.png

    I also retextured the entire map and replaced all of the objects with Malangdo-themed ones, which no one will probably ever notice because you really don't have time to sit and enjoy the scenery in the middle of guild war.  Putting all that food on those plates is a huge pain!  Oh well.  I feel like if you can't tell that I made something, it means I probably did a good job of it.  The gold standard is for it to look like something that could have been in the game originally.  I also continue to be impressed by the ingenuity of the original level designers.  Did you know that those apples on the table beside the bananas are actually christmas tree ornaments from Lutie?  You'd never guess.

     

    So we have our new map, all that remains is to rig it up to work in the game.  Surprisingly, Rathena provides space in agit_controller for you to insert your own custom guild castles.  Has anyone other than me ever used this?  Adding the new castle is surprisingly easy, you just have to remember to put it into castle_db.  And here we are!

    image.png.f497655277e28995af54789e09e4b00f.png

    Shh, no one give away my awesome hiding spot.

     

    Oh, and of course, Malangdo also needs its own Guild Dungeon!  Luckily, we happen to have an unused cat-themed dungeon left over in lasagna dungeon!  It's like I planned it all along!  This place also plays the unused track 175.  Now let's all enjoy Cat on Bullet.

    image.png.5673d589c9c2f83728a8211f263ddc81.png

     

    Only one question remains.  How the heck do we actually get here?  Well, the first couple screenshots may have given a clue.  See, it's an intentional balancing mechanic that the guild realms aren't just inside the town.  The necessity to walk back to the entrance to the guild castle increases the length of time it takes the attackers to respawn, compared to the defenders who can simply warp into the castle.  So we need a spot that takes a little bit of time to walk to.  How about the otherwise pointless boat in the middle of Malangdo?  (I don't apologize for this dialogue).

    image.png.bb69d5f4eb1810f59912378394f84e77.png

     

    And there we have it, 5 castles (or four castles and one cattsle).  All that remains is the rewards.  Here's a taste of a few of them (there's actually 30 new equipment items in total)

    image.thumb.png.d7601e4a65ea03aaa702480d4a2f7dd7.png

    Which one do you think is the most gamebreaking?  (Incidentally, "Finale" mentioned in the Dancer weapon is Amp.  Someone complained that calling it Amp made no sense, which is true).

    Actually, a key element of this is that most of the gear is significantly restricted in terms of which classes can use it, so even if an item like the Light Manteau maybe outclasses the Heavenly Wings (Dex +1, 3 Def), the fact that only a few classes can use it prevents it from making the other item useless.  In any case, they're not really out of line with a lot of the gear that's already in the game (and I don't think they're nearly as bad as a lot of the nonsense that was added in renewal), but they are probably things you would actually want, which of course is the point.

     

    Anyway, that wraps it up for this time.  it might be a long time before another update, if there even is one, but I hope everyone has enjoyed reading this thread.

    • Love 3
  5.  

    Oh, it's possible the change from range -1 to range might have been something else I tweaked at some point, because it looks like the version you have is the same as mine.  In any case, you'd just have this.

    // Remember the skill request from the client while walking to the next cell
    if(src->type == BL_PC && !battle_check_range(src, target, range)) {
    	if (ud->walktimer == INVALID_TIMER) {
    		unit_walktoxy(src, target->x, target->y, 8);
    	}
    	ud->stepaction = true;
    	ud->target_to = target_id;
    	ud->stepskill_id = skill_id;
    	ud->stepskill_lv = skill_lv;
    	return 0; // Attacking will be handled by unit_walktoxy_timer in this case
    }

     

    For the second block, you can actually leave that one.  That is for ground-targetted skills, which cannot experience this issue.

     

    The third block looks like this.

    // Remember the attack request from the client while walking to the next cell
    if(src->type == BL_PC && !battle_check_range(src, target, range)) {
    	if (ud->walktimer == INVALID_TIMER) {
    		unit_walktoxy(src, target->x, target->y, 8);
    	}
    	ud->stepaction = true;
    	ud->target_to = ud->target;
    	ud->stepskill_id = 0;
    	ud->stepskill_lv = 0;
    	return 0; // Attacking will be handled by unit_walktoxy_timer in this case
    }
    • MVP 1
  6. Well, after a while of no major changes, I'm back at it again.

     

    Sage's Dragonology has always felt like one of the most lame skills in the game to me.  It's a mastery skill that only works against Dragons, which is even worse than most mastery skills.  Sure, if you max it, you get 3 INT, which is ok, but it still feels really lame, it's basically just "dump 5 skill points for 3 int".

     

    It seems to me, that if you were going to spend all that time studying dragons, rather than just becoming marginally stronger against them, wouldn't you want to become a dragon instead?

    image.png.cac6c601975ff3f56f52e34d9bc4dc3e.png

     

    Now this is more like it!

    image.png.d5789a05f3640ba39cf79b392b866a8a.png

     

    Dragon Form is essentially Mini Berserk for Professors, albeit with a number of different nuances.

     

    For starters, you can only use Dragon Form when over the four elemental field spells, Volcano, Deluge, Violent Gale, and Gaia Power.  (Astute readers might note that Gaia Power doesn't exist in the base game, but it's an Earth-version of the other field spells).  Which Dragon you turn into depends on which field you're standing on when you activate it.  Here's the Wind dragon after casting it on top of Violent Gale.

    image.png.035d1203fa85e88b933e8148eb56a7b8.png

     

    Becoming a Dragon gives you a boost to Attack Power, Maximum HP, and Movement Speed, albeit to a way lesser extent than Berserk (only 150% HP at max level compared to Berserk's 300%).  It also sets your attacking and defending element to be the same as the Dragon.  This not only means that you'll instantly get the buff from the elemental field spell you're standing on, but it's also one of the only sources of Level 2 elemental armor for players, so if you become Water Dragon, you're instantly immune to Storm Gust.  Take that spamming Wizards in WOE!

    image.png.13be5b903cc90783e7cd98dea46c2a28.png

    Of course, a dragon with level 2 water armor would not enjoy Jupitel Thunder very much, so there are drawbacks.  Also, don't think you're going to get sneaky and change your weapon or armor element after becoming a Dragon because you can't, like with Berserk your ability to change your equipment is locked and it also blocks all spells that change your weapon or armor element.

     

    In Dragon Form, you also have Large Size and are Dragon Race, which is interesting.  This means that Hydra and Thara Frog cards instantly become useless against you, which is pretty great, though it does mean that the various Anti-Dragon equipment becomes a problem.  Probably still worth it though.  It also takes a lot of skill points to get Dragon Form, as Dragonology now has 10 levels and it must be maxed, and you need at least one elemental field spell too.

     

    As a Dragon, you can still cast spells.  You gain access to the new Dragon Breath skill, which can only be used as a Dragon, and does strong physical damage and knockback, but your regular spells work just fine too.  There's nothing stopping you from casting spells that don't match your element, either.

    image.png.2ff1a8f39611ab9219688cc23fee07e7.png

     

    This is probably kind of a silly change, but I think it's fun.  Who doesn't want to run around as a Dragon?  It also finally gives battle Professor the survivability and damage it needs to be competitive with the traditional caster Professor, and gives more value to the elemental weapon / field side of the skill tree, which is often overlooked.

     

    Under the hood, the transformation into the monster is actually very simple, it just uses the built-in SC_MONSTER_TRANSFORM.  It's not difficult to check to see if you're over one of the elemental fields, as it can simply check to see if you have any of the elemental statuses like SC_VOLCANO, which are granted while standing in those fields.  The biggest pain was getting it to properly apply the field bonus after you become a dragon and your armor changes, as the original implementation does not allow this (if you do not have the right armor type when entering the field, it does not check to see if you acquire it later), this took a bit of re-implementing.

    • Upvote 3
  7. No problem, that's an easy one to fix, though I'll also explain what's happening.  In rathena, a block_list, or bl, represents an entity in the world, like a player, a pet, or an enemy.  Since this type of variable can represent many different types of entities, the information available about them is very general.  You can determine what map they are on and what their position is, but you can't, say, check their equipment because many types of entities don't have equipment.  As such, something that is frequently done is to check the type of entity, then perform something called a "cast" to convert it to a more specific variable type so we can get more information out of it.

     

    When you see session_data, or sd, that specifically represents a player, so it's a specific type of bl.  The code "if (sd)" basically checks to see if the entity in question is a player, which is relevant to this logic because we only want players to get the new pathfinding logic.  However, to run this statement we first have to declare sd.  In the version of rathena I have, this was declared at the top of the method, but they moved it further down in the method in the newest version, and it's now below the if statement where we were looking for it, which causes that error.  This is no problem though, we can easily move it back to the top without impacting anything.

     

    First, find and cut this line from later in unit_walktoxy:

    TBL_PC *sd = BL_CAST(BL_PC, bl);

    Then paste it under this line:

    unit_data* ud = unit_bl2ud(bl);

     

    It should now work, but we might also want to make one more tiny change just for consistency with their code.  Where we have this:

    if (sd && !(flag & 1)) {

    Let's instead make it this:

    if (sd != nullptr && !(flag & 1)) {

    I'm fairly certain this makes no difference, it's probably just a stylistic choice, but we might as well keep the codebase clean.

    • MVP 1
  8. Having now mostly finished with custom content for the time being, I decided to take a look at some player complaints that I previously ignored saved for later.

     

    Among players who are new to Ragnarok, by far the most common complaints involve the game's netcode and pathfinding.  I've played Ragnarok for so long that I've learned to just automatically adjust for it.  For example, I know I don't need to tell anyone from here this, but when moving around in RO you generally want to keep the mouse cursor about this far from your character and hold the mouse button down.

    image.png.6106376a188a8aea09ab4c00c33bbabe.png

    This works pretty well, as long as you keep the mouse cursor about that close you shouldn't have any issues with your character not going where you want them to.  I imagine this is probably kind of a holdover from the very early days where you couldn't zoom the screen out much at all.  An RO vet would know you definitely wouldn't ever try to do something like this to move:

    image.png.61351ffa0c4234182f2be77b40d0043b.png

     

    But it's hard for people who are used to more modern games to get used to this, no matter how many times I pointed out that it was all their own fault.  So I decided to see if I could do something with the pathfinding algorithm to make it work a bit better. 

     

    For starters, it turns out there's actually something incredibly easy you can do to improve pathfinding a lot!

     

    All you have to do is disable this line in core.hpp:

    #define OFFICIAL_WALKPATH

    And instantly pathfinding is way better!  It turns out that rathena's pathfinding algorithm is way better than the one that the official game uses, but they include a reproduction of the original for the sake of accuracy to the originals.  Just disable this line and suddenly you can walk about twice as far.  The example above works now!

     

    However, there are still some situations where it doesn't work, generally when walking a long distance in a straight line, oddly enough.  I think this is because the way the pathfinding algorithm works is it tries to explore all possible options to get to a destination, so if there are too many choices (like in a very open area) it kinda gets stuck.  You can see this in action in a more compact area like clock tower where the pathfinding is actually really good now.  However, we can improve it further.

    The code that handles pathfinding is in unit.cpp, in unit_walktoxy.

    We can see that it has two algorithms that it uses, an easy one that attempts straight line movement, and a hard one that is more complex.  However, it turns out there are some situations where the easy path finds the answer and the hard one doesn't.  As such, what we really want is to try both, but currently it never does this.

     

    We want to replace this code:

    if (!path_search(&wpd, bl->m, bl->x, bl->y, x, y, flag&1, CELL_CHKNOPASS)) // Count walk path cells
    		return 0;

    With this:

    if (sd && !(flag & 1)) {
    	// check easy path, but don't give up if it fails
    
    	if (path_search(&wpd, bl->m, bl->x, bl->y, x, y, 1, CELL_CHKNOPASS)) {
    		ud->state.walk_easy = 1;
    	} else {
    		if (path_search(&wpd, bl->m, bl->x, bl->y, x, y, flag & 1, CELL_CHKNOPASS)) {
    			ud->state.walk_easy = flag & 1;
    		}
    		else return 0;
    	}
    }
    else {
    	if (path_search(&wpd, bl->m, bl->x, bl->y, x, y, flag & 1, CELL_CHKNOPASS)) {
    		ud->state.walk_easy = flag & 1;
    	}
    	else return 0;
    }

    The idea here is that if the flag is not set to try the easy path, it will do it anyway, but if it fails, it will try the hard path.  If the flag is set to try the easy path, it only tries the easy path, as before.  Also, it only applies this logic to players and not enemies.  You can remove the sd in the first if statement to apply it to enemies as well, but this actually makes the game a little harder as the AI is now better at chasing the player.  In any case, pathfinding is now drastically better, you can click at least twice as far away and automatically path around many obstacles.  Try it out for yourself, it's very noticeable.

     

    Next on the list of complaints was that sometimes when you click on an enemy, you don't attack.  This is one that I've encountered a fair bit myself.  I'm pretty used to automatically clicking near the enemy to move closer to account for it, but it is pretty annoying and sometimes it can get you killed.  It turns out this happens at extremely precise ranges where you're too far to attack but too close to move closer automatically, and then attack.  It took me ages to isolate the exact problem, but I finally got it, and the details can be found in this thread:

     

    After running a boatload of tests against this I determined it to be fixed, but when partying with a Dancer we were encountering the problem again!  How frustrating!  But it turns out, this was actually not the same problem at all, but rather a limitation in the client.

    See, if you read a couple posts above this one, I mentioned that I added a skill to the game that allows Bards and Dancers to enthrall monsters and use them as minions for a little while.  The Alchemist also has something similar to this with Bio Cannibalize.  In both cases these create mobs that are friendly towards the player because they have their master_id set to the player's id.  The server knows that these monsters are not valid targets for the player because the routine battle_check_target checks the master_id, but the client doesn't know this, and so it lets you attempt to target them like any other monster.

     

    Note the sword icon on the captivated monster and targeting caret:

    image.png.9723b38397cfce991ed132b7a5e10a89.png

     

    This is not so much of a problem when out of combat, but when fighting enemies, the minion can kind of get in the way and "absorb" your clicks in a way that another type of minion like a pet or homunculus can't (because they aren't considered targetable by the client).  Strictly speaking, this is an unfixable problem because there's no way to tell the client that you shouldn't be able to target that monster, there simply isn't any field that does this within the monster packet.  So I opted for a more creative solution.  Instead of preventing you from clicking on the monster, the game will simply try to guess what you meant to click on instead, and change your target to that.  The way it does this is that if you try to use an enemy-targetting skill or normal attack on a minion (which isn't allowed), it will instead assume that you mean to target whatever the minion is targetting, so it sets your target_id to the minion's target_id.

    I doubt anyone else would actually need this code (it doesn't really matter for bio cannibalize, it's mostly just for the custom skill on my server, but it's accomplished by this code snippet here in unit_skilluse_id2, which goes right before the comment at the end.

    if (inf == 1 && battle_check_target(src, target, BCT_ENEMY) <= 0) {
    	if (target->type == BL_MOB && ((TBL_MOB*)target)->master_id && ((TBL_MOB*)target)->target_id && battle_check_target(src, map_id2bl(((TBL_MOB*)target)->target_id), BCT_ENEMY)) {
    			target = map_id2bl(((TBL_MOB*)target)->target_id);
    			target_id = target->id;
    	}
    }
      
    // Fail if the targetted skill is near NPC [Cydh]

    For some reason that's not totally clear to me, this also requires making this change slightly later when it sets the stepskill:

    ud->target_to = target->id;

    It was originally set to target_id instead.  If this change is not made it sometimes wrongly sets the target_to to 0.

     

    Yet one more complaint was that when you reset your skills, it resets everything, and not just the skills for your current job.  I agree that this is annoying, but I looked at it a long time ago and decided it was too hard to fix.  See, you would think this would be straightforward, but actually the way the skill trees for the classes are specified, each class knows all the skills it can potentially learn, including those from its previous classes, and there's no real distinction for which skills are new and which ones aren't.  So to figure out what skills you can only learn in your current job we have to check the previous jobs and this requires a ton of bitflags and such.

    Once again, I made a separate thread that has this version of the NPC, if you want this functionality on your server:

     

    Finally, I took one more pass on the achievements.  I had previously overhauled the achievement system to actually work, but the treasure chest achievements were still a bit lacking.  In particular, there were none in many of the newer dungeons (not just the ones I added myself, but many of the official dungeons too), and of course the past area had no treasures at all.  So I revamped the treasures, including consolidating some of the categories so I could add a new one for the past areas.  This is a lot of work for a feature no one really cares about!  Actually, I do kind of think these treasures are useful for discovery - the achievements let you know that certain dungeons exist, so you can keep exploring if you haven't found them all.

    image.png.e9693d2faf19083aaf400b18bd282841.png

    And then on top of that I also fixed many other maps in the past that had the same map culling issue I previously mentioned for pro_fild01.  Geez, that was hard work, I need a vacation. 

    What do you mean I already had my vacation and I spent it all working on Ragnarok?

    • Love 1
  9. My server started out simply by implementing content into PRE-RE that already existed in the game, but was exclusive to Renewal, and then when I ran out of Renewal content I started harvesting content from the Korean version, and then the alpha, but we've reached the point where there simply isn't any more content that is usable for what I want to do.  Actually, we probably reached that point a few months ago.  Unfortunately, I still have new ideas.

     

    While the "present" on my server is pretty well filled with stuff, there are still a few places in the past that feel a bit sparse.  As such, I had an idea for a castle in the middle of the desert that would feature the "abyss arthur" boss, which is one of the only MVPs that isn't in my server already.  I knew there was an unused desert castle map at moc_vilg00 that I don't use because I don't think the past version of Morroc particularly needs to have a castle (and Morroc's Fortress is south of it), so it was just sitting around unused in the GRF.  There are also a couple maps representing the inside of the castle, which also might be usable.

     

    The outside map is actually kinda usable!  It'll only be enterable from one side (the bottom) and I'd like to move the castle up towards the top of the map so there's more space, but this is a good start!

    image.png.2206f666488e5f4d27176063c192dbf2.png

     

    The inside maps, though, are just a mess. 

    image.png.8905769f906e1874901d4d337355c26a.png

    image.png.7cdab40c5d1de8a50c6618d00080f9b1.png

    Renewal actually has a few maps like this (ie, Octopus Cave, Misty Forest Labyrinth), but they simply don't play well (neither of these maps are on my server).  Having a map that's made up exclusively of extremely small rooms with teleporters every few feet gives you no space to fight, and it's very common to teleport into a room full of enemies and get ganked immediately.  These maps are also designed solely as indoor maps and thus only have walls on the northeast side, as the indoor camera doesn't allow you to rotate the map any other way.  These are not usable.

     

    So if I wanted to make this dungeon, I'd have to create the indoor maps from scratch, which is far more complex than anything I'd ever done with the map editor before.  However, at least I have the old maps as a general guide for the aesthetics of the map, as I do like the general feeling of these maps, they're just not suitable for gameplay.

     

    The first step was to alter the outdoor map to move the castle.  This was comparatively fairly straightforward.  There's no kind of "multi select and drag" feature here (at least, not in this old version of Browedit), so I had to move the terrain and objects manually, but having the old version as a guide this was not overly hard.  I did have to learn a fair bit about how walls worked for this (as the moat around the castle has a ton of walls, which was useful practice for later.

    Here's my version of the outdoor map.  It's a little more open, befitting its status as a combat map, but it's still pretty similar overall.

    image.png.20cdc7f4fe14689ec1664eaadfc3565b.png

     

    Now came the hard part, creating the indoor maps.  For each one, I started out with the floorplan, just laying out the rooms and hallways using texture tiles, then building walls around them, and finally filling them with objects.

     

    Here's my version of the upper floor of the castle.  This has a sort of generally similar feel to the original, and in particular I liked the idea of the water room, which I kept and expanded for my version.  It feels appropriately decadent for a desert castle.

    image.png.4f5a4d47c203ac5aea2be06f78622415.png

     

    And here's the bottom floor.  This part of the castle has more of a pyramid feel.  I like the idea that perhaps the castle was built on top of an ancient tomb or some such.  It's also divided into two halves that are linked by a portal, making it feel a bit more like it's in three pieces.  The boss always spawns in the big room in the upper right.

    image.png.8a1fb29cd909efecd586bf9fc88b4c70.png

     

    I learned a ton about the way Ragnarok maps are constructed while working on this.  For example, let's take a look at this guest bedroom on the lower right of the upper floor.

    image.png.173eac728d63d263fb82952a9581c072.png

    You might think that those paintings on the walls are objects, for example, but they are actually simply wall textures, and ditto for the windows.  The wall graphics contain a ton of different variants for different wall textures, all shoved into the same texture file, and the walls are made out of one section of that larger texture.  Similarly, those carpets are floor textures, but do you think they store the entire carpet?  Oh no.  Only the corner is actually part of the texture, which you then have to rotate and overlay upon itself to create the full thing.  These sorts of little efficiencies are everywhere.  Many of the objects you see in the maps are actually made up of multiple objects.  

     

    For example, let's appreciate this little shelf in Payon:

    image.png.7e68500353b5b75bfe45f686e192b00f.png

    You might think this is a single object, but no, this is dozens of objects meticulously aligned together.  Each one of those little sets of 2 doors is an object.  So the bottom layer is made up of 3 objects, and so is the second, the third layer is 4 objects,  The top 4 rows are made up of 10 objects each!  It probably took an hour to create this shelf, and this house has two of them on opposite walls.  For as many crazy things as I've done with Ragnarok, I can't even come close to what the original creators did.  It's amazing how much they were able to do with such a simple engine.  It's probably part of why the game feels as compelling as it does, and how it's still interesting to tinker with even 20 years later.

     

     

    With the maps finally done and added to the game, it was time to fill them with enemies.  Having gone to all that work, simply adding effects to clone monsters using monster_size_effect_sak_new.lub didn't feel like enough, so I used my newfound knowledge of how to recolour sprites to create some entirely new palette swaps.

    image.png.7b3a4972797c3776be6ca1885ee6e7f2.png

    I quite like the new blue bird.  I also went back and created new sprites for a few enemies who just had effects before, like this harder version of Isis from Past Pyramid.  Dang, I kind of want a Green Isis pet now.

    image.png.21d8a1b296082e3fade9500e13fc7c12.png

    Adding new sprites to existing monsters is quite easy, you just have to change their sprite file in jobname.lub and add the new act and spr files to the monsters folder in your GRF file.  

     

    I also went back and improved the dungeon I made out of 1@jorchs some time back.  It now has significantly more objects.  I had wanted to put lab objects in here before, but slabw doesn't load in my browedit for some reason.  However, 1@lab and biolab both load, so I was able to steal some of their objects.  This map is much creepier now.

    image.png.f612fd1fa81f2691b81e52c838e46362.png

     

    Anyway, that's enough map editing for now, and hopefully for quite some time, as I think the past finally has enough dungeons now.  It definitely gave me a deeper appreciation for the map design in this game, and makes me even more impressed with Toy Factory, which I've always considered to be the best-looking map in the game.  Look at how many different texture pieces are used in this tiny section of the map.  The attention to detail is crazy.  Seriously, open this map up in the editor, it's worth seeing how this was put together.

    image.png.6145308f4a5942e7d92f9c9703b04b03.png

    It just goes to show that there'll never be another Ragnarok.  The amount of love and care that went into this game isn't something you see every day.

    • Upvote 2
    • Love 1
    • Like 1
  10. So it turns out there's actually another version of this glitch where sometimes your character will fail to attack because they're at an extremely precise range where they are out of range to attack, but too close to move closer and then attack.  This seemingly requires some degree of client desync and is rare, but it annoyed me enough that I spent many hours tracking down the exact problem.

     

    Find this in unit.cpp: 

    if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && (!battle_check_range(src, target, range-1) || ignore_range)) {

    (it occurs twice in the file)

    In both instances, replace it with this:

    if(src->type == BL_PC && (!battle_check_range(src, target, range) || ignore_range)) {
    	if (ud->walktimer == INVALID_TIMER) {
    		unit_walktoxy(src, target->x, target->y, 8);
    	}

    Do not touch anything below this code, leave everything about the stepskills untouched.

     

     

    If your rathena is old, the ignore range part is not present so you are instead looking for this:

    if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle_check_range(src, target, range)) {

    Which you replace with this:

    if(src->type == BL_PC && !battle_check_range(src, target, range)) {
    	if (ud->walktimer == INVALID_TIMER) {
    		unit_walktoxy(src, target->x, target->y, 8);
    	}

     

     

    The problem is that if the user is too close for the client to execute a move to attack instruction, but not close enough to attack, the walktimer will not be initialized, but the range check will fail.  It should execute a stepskill in this situation, but it does not because it requires a valid walktimer to execute a stepskill.  This handles this boundary case by forcing the user to approach the target, as the client would do automatically under all other circumstances.  This is maybe not the most graceful solution (maybe they should just move one tile closer to the target?) but it works and the problem can no longer occur.

    • MVP 2
    • Like 1
  11. This has been a request that I think everyone has made at one point or another, but I initially looked at it and said "too complicated".  Well, today I decided to finally code the thing.

    image.png.0d8ca1e323e3f158f721e02f055831dc.png

     

    Basically, this is an adjustment to the Reset NPC that gives you the option to only reset your current class, leaving your previous classes untouched.  

     

    This requires code changes to support it, and you'll have to recompile your server afterwards.  A quick caveat, though this includes code to support third and fourth classes (for Renewal), that code is untested.  It's similar to the second class logic so it should work, but if you have some kind of weird issue it's probably there.

     

    In pc.cpp, find the pc_resetskill method

     

    Find this line:

    int i, skill_point=0;

    Replace it with this:

    int i, j, k, l, baseClass, secondTransClass, thirdClass, currentClass, skid, skill_point = 0;
    bool previousClassSkill, isSecondClass = false, isThirdClass = false, isFourthClass = false;

     

    Find this line:

    if( flag&4 && (sd->class_&MAPID_UPPERMASK) != MAPID_BARDDANCER )
    		return 0;

    Insert this below it (don't replace the code above).  If your server has fourth classes, uncomment that line.

    baseClass = pc_mapid2jobid(sd->class_ & MAPID_BASEMASK, sd->status.sex);
    secondTransClass = pc_mapid2jobid(sd->class_ & MAPID_UPPERMASK | JOBL_UPPER, sd->status.sex);
    thirdClass = pc_mapid2jobid(sd->class_ | JOBL_THIRD, sd->status.sex);
    currentClass = pc_mapid2jobid(sd->class_, sd->status.sex);
    
    if ((sd->class_ & MAPID_BASEMASK) != sd->class_ && (sd->class_ & MAPID_BASEMASK | JOBL_UPPER) != sd->class_ && (sd->class_ & MAPID_BASEMASK | JOBL_BABY) != sd->class_) isSecondClass = true;
    if ((sd->class_ | JOBL_THIRD) == sd->class_ && (sd->class_ & MAPID_BASEMASK) != sd->class_ && (sd->class_ & MAPID_UPPERMASK) != sd->class_) isThirdClass = true;
    //if ((sd->class_ | JOBL_FOURTH) == sd->class_ && (sd->class_ & MAPID_BASEMASK) != sd->class_ && (sd->class_ & MAPID_UPPERMASK) != sd->class_ && (sd->class_ | JOBL_THIRD) != sd->class_) isFourthClass = true;
    
    currentClass = pc_class2idx(currentClass);
    baseClass = pc_class2idx(baseClass);
    secondTransClass = pc_class2idx(secondTransClass);
    thirdClass = pc_class2idx(thirdClass);

     

    Find this line

    if (lv == 0 || skill_id == 0)
    			continue;

     

    What you need to add next depends on your version of eathena.  If you are on latest, which has the new skill tree, add this below that line:

    if (flag & 16) {
    	previousClassSkill = false;
    
    	if (baseClass != currentClass) {
    		std::shared_ptr<s_skill_tree> firstTree = skill_tree_db.find(baseclass);
    	
    		if (tree != nullptr && !tree->skills.empty()) {
    			for (const auto &it : tree->skills) {
    				skid = skill_get_index(it.first);
    				if (skid == skill_id) previousClassSkill = true;	
    			}
    		}
    	}
    
    	if (isThirdClass) {
    		std::shared_ptr<s_skill_tree> secondTree = skill_tree_db.find(secondTransClass);
    	
    		if (tree != nullptr && !tree->skills.empty()) {
    			for (const auto &it : tree->skills) {
    				skid = skill_get_index(it.first);
    				if (skid == skill_id) previousClassSkill = true;	
    			}
    		}
    	}
              
        if (isFourthClass) {
    		std::shared_ptr<s_skill_tree> thirdTree = skill_tree_db.find(thirdClass);
    	
    		if (tree != nullptr && !tree->skills.empty()) {
    			for (const auto &it : tree->skills) {
    				skid = skill_get_index(it.first);
    				if (skid == skill_id) previousClassSkill = true;	
    			}
    		}
    	}
    }

     

    If you are on an older version, which has the old form of skill_tree, add this below that line instead:

    if (flag & 16) {
    	previousClassSkill = false;
    
    	if (baseClass != currentClass) {				
    		for (j = 0; j < MAX_SKILL_TREE && (skid = skill_tree[baseClass][j].skill_id) > 0; j++) {
    			if (skid == skill_id) previousClassSkill = true;
    		}
    	}
    
    	if (isThirdClass) {
    		for (k = 0; k < MAX_SKILL_TREE && (skid = skill_tree[secondTransClass][k].skill_id) > 0; k++) {
    			if (skid == skill_id) previousClassSkill = true;
    		}
    	}
    
    	if (previousClassSkill) continue;
    }

     

    We're pretty much done with pc_resetskill, but if you want, you can add this to the comment at the top of the method, as the final line:

     * if flag&16, Only reset skills that the current class can learn

     

    Almost done with code changes, now we just need to add the script function.

     

    The remainder of the changes are in script.cpp:

     

    First, find the definitions of the script functions by searching for this:

    struct script_function buildin_func[]

    And add the following line somewhere, ideally below the one for resetskill

    BUILDIN_DEF(resetskillcurrentonly, "?"),

     

    Now add this method somewhere, ideally below resetskill but it can in theory go anywhere.

    /**
     * Reset player's skill, leaving previous classes untouched
     * resetskillcurrentonly({<char_id>});
     **/
    BUILDIN_FUNC(resetskillcurrentonly)
    {
    	TBL_PC* sd;
    	if (!script_charid2sd(2, sd))
    		return SCRIPT_CMD_FAILURE;
    	pc_resetskill(sd, 17);
    	return SCRIPT_CMD_SUCCESS;
    }

     

    With this done, you can recompile the client and everything should be set.  Now all you need is the new version of the resetnpc script below and you'll have the new functionality.  Man, this was a lot harder than it should have been.  Rathena should really just add this option by default.

    resetnpc.txt

    • Upvote 2
  12. Well, break time's over, I'm back to it with more crazy new mechanics.

     

    I know what you're thinking: "I love the Bard / Dancer class, but I'm a total loser with no friends so the party support aspect of the class isn't useful to me!".  Well, there's an easy solution to that problem, we'll just have to make some friends!  Not through tedious social interaction, but with a new skill!

     

    Introducing Captivate, a new skill for Bard / Dancer.  Just find a friendly-looking mob and cast the skill on them to make them your new friend!

    image.png.a3fef34978eb86d41146f73b84fcb8e7.png

    (This spell does have a lengthy cast time so if you're planning to cast this on a hostile mob or a monster that can cast-detect you might want to find a source of uninterruptable cast)

     

    After captivating your target, they'll follow you around and attack things that attack you or that you attack, kind of like a pet.  If you'd prefer to take a more active approach, you can also direct them using the Conductor skill:

    Go my minion!

    image.png.7d0005c1df15fb97e8259d27586c911c.png

     

    You can even support your new friend with your song and dance abilities to enhance their fighting abilities or heal.

    image.png.93ad11693f6b3b61daa3ae421a1bee5d.png

     

    Of course, nothing lasts forever.  After the skill's duration expires you'll be all alone again.  But there's plenty of mobs in the dungeon!

    image.png.f5f28f380d87c722815b330695eb6fba.png

     

    Now to have my new friends do my dirty work in PVP.

    image.png.488af5ac9dd494d9dd513189e9b80442.png

     

    The way this works under the hood is that when you use the spell on a monster, they are despawned and recreated as a minion using "mob_once_spawn_sub", similar to AM_CANNIBALIZE.  Most of the heavy lifting is already done by the follower AI, though some tweaks to "battle_check_target" were needed to allow mobs and players to attack them.  At any rate, I think this is a fun ability.  It's not actually super powerful (of course, you can't use it on bosses or monsters that are higher level than you, and it has a cooldown so if you're going through minions fast you'll have to wait), but it's unique and I think it fits with the character.

     

    Speaking of minions, Blacksmith also now has the ability to summon these.

    image.png.68f19f3a33e796c9bc192492e9f0e700.png

    Nothing crazy here, this was already a Mechanic ability that has few changes.  For Blacksmith, you need the forging abilities to learn this skill, which gives forgers a little more offensive presense.

     

    Also, I fixed the Magic ones.  In the base game, they're all Yellow, regardless of element, so I recoloured them to match their element.  Here are the fire and earth ones.

    image.png.ba5193ba0067437b9dbf48e78ed8c73e.png

    It turns out this is actually super easy.  Using Act Editor, you can use the "Quick Palette Edit" to instantly swap out the colours across the entire Act file.  It only took a couple minutes to create the palette swaps.  I encourage anyone else who wants them recoloured to learn how to do this, because you could easily use this to recolor other sprites too.  Makes you wonder why Gravity didn't bother.

    • Love 1
  13. Apparently my forum account is now 1 year old, which means it's also been a year since I started working on my server.  I actually haven't made a ton of big changes since the last post, mostly small tweaks like bug fixes and the occasional new item or enemy.  Still, I wanted to put up some sort of update, so I decided to make a video showing some gameplay of the past area.  I've thought about doing a video before but I've held off on it because Gravity is apparently pretty aggressive with takedowns, so it's unlisted (and all of my videos are unmonetized anyway).  I still may not leave it up forever, so enjoy a brief look at past gameplay (make sure to set the video to 1080p in case it doesn't do that automatically).

     

  14. Another small update, but no less time-consuming.

     

    I got my hands on the GRF file with the information from the beta, and I was able to harvest the last removed map, etc_cave01.  This map did not initially load properly as its textures are looking at some nonexistent location, but hex editing the GND file easily allowed these to be replaced.  It seems to use the same textures as Dungeon001, so I've added it to the game as another floor to that dungeon.

    image.png.897a09cb9f8d4fc27d15fefd32a92550.png

    There's a few new enemies here, like Papare and that Black Bat enemy.  I found a new item I wanted the black bat to drop, the Black Devil Mask.  It's a neat-looking mask that covers one eye.

     

    However, there's a big problem with it!  If you turn in certain directions, the eye that it covers changes!  This looks super stupid and I wasn't willing to put it in the game like that.  So, because I'm way too picky about stuff like this, I decided to see if I could fix the sprite myself.  There's a pretty good tool that can be used to do this called Act Editor which I used a little bit before for editing enemy sprites.  A naive person (ie, me before I did this) would probably think that because the character can be displayed from 8 directions, this will only require 2 new sprites (only 2 directions are messed up).  Oh no.  The game manually specifies the position of the headgear for every possible animation frame the character can have.  I'm sure there's probably some automatic way to update this but I didn't find it, I just did them all manually by calculating how much the sprite moved each frame.  There are also different sprites for male and female characters since they have different animations.  This took a while.

    image.png.9b29e31d6e27592627e7e98b5ca421e4.png

     

    Since I was already spending my whole evening on this, I decided to see if I could fix the Kitty Band as well, which has a strange issue where the inside of the ear always faces outward regardless of which direction you face, causing people on my server to joke about it being the worst headgear in the game.  This was comparatively much easier because I didn't have to reposition anything, I just had to add some new sprites and then swap out the sprite index in the animations.  Interestingly, the male act file for the Kitty band already specifies a back sprite, it's just identical to the front sprite, but the female act file does not.  Did two different people work on these?

     

    At any rate, here's what they look like once they're fixed.

    image.png.a1bb76823397545ba88e09d7310a5125.png

     

    Should you want these sprite edits for your own server, I've attached a GRF file that contains the updates.  You'll probably just want to merge this into one of your grf files, since it's very small.

    headgearfix.grf

    • Upvote 1
    • Love 2
  15. A very small post, but I wanted to share something in case anyone else ever runs across it.

     

    As I've mentioned previously, my server contains the maps from the alpha, albeit with new monsters and warp points and such.  However, there's a strange problem on one of the alpha maps.

    On pro_fild01, there's this bizarre culling issue where parts of the map will sometimes disappear while you're walking through it (look at the top part)

    image.png.c16c79d446514319aac3fe6b16d32839.png

    It flickers on and off while you walk through the map and is generally very annoying.  But what's going on?  No other map in the game does this.  I decided to try to investigate it in the map editor.

     

    Everything looks normal enough.  There's nothing in the map to indicate any "culling zones" or anything strange like that.  I even tried replacing the rsw with one from another map of the same size and the problem persists.  The one thing I noticed is that if you look at the map boundary, the map is actually sunk below it (notice how the yellow walls are above the map rather than below), which is a bit weird.  Actually, most of this map is sunk pretty far into the ground, with many parts having Y values in the 30s and 40s.

    image.png.d5ac50f77ecd2c055ea9558b9cf73648.png

     

    I decided to try moving the entirety of the map up by 50 units, to see if the height of the map was the problem.  Note that the map now sits above the yellow wall.

    image.png.4f9724f9e8269ec8871bc141cd24d2f2.png

    This is not too hard using the "global height edit" function, which also thankfully adjusts the GAT tiles.  However, it was also necessary to manually adjust the height of each object, which was moderately time consuming but not difficult using the object list.

     

    And surprisingly, it works!  The weird culling effect is eliminated and the map displays normally now.  So it seems like the game has issues if map geometry goes too far below the ground, though this isn't an issue on any other official maps that I've seen.  I wonder if this bug existed in the alpha?  My guess is that the client worked slightly differently back then so this issue didn't occur, but who knows.

    • Upvote 1
  16. On 8/11/2023 at 12:31 AM, Virtue said:

    Interesting. How did you make the pity system

    It's not the greatest implementation ever but I can show you the gist of it.

     

    In the struct mob.hpp, there is a new field in the struct mob_db:

    unsigned int pity;

     

    In mob.cpp, this value is initialized to 0 in mob_parse_dbrow, near the bottom (its exact position doesn't really matter)

    entry.pity = 0;

     

    Also in mob.cpp, in the method mob_dead under "player specific drop rate adjustments", we add bonus drop rate based on the pity value, if the item is a card.  This is the part that is kind lackluster.

    				// Increase the drop rate based on pity if it's a card
    				if (it->type == IT_CARD)
    					drop_rate = drop_rate * (50 + md->db->pity) / 50;

     

    The way this formula works is that the value "50" here is the number of kills required to double the drop rate of the card.  On my server, cards drop at a rate of 1/200, so this feels kinda reasonable, but if you rate was, say, 1/1000, this would be way too high and you'd probably want to set this value to like 200 instead (make sure both numbers are set to the same value).  For an optimal implementation this value should probably derive from the server's card drop rate in some way but I kinda hastily coded this one in.

     

    Also in Mob_dead, we need to make a slight change later on to increase or reset the pity based on if the card drops or not.

    			// attempt to drop the item
    			if (rnd() % 10000 >= drop_rate) {
    				if (it->type == IT_CARD) {
    					md->db->pity++;
    				}
    				continue;
    			}
    
    			// Reset the pity if the card drops
    			if (it->type == IT_CARD) {
    				md->db->pity = 0;
    			}

     

    And that's really all there is to it, recompile the code and you have the pity system.  Note that, as I mentioned when I first brought it up, the pity is shared among all players, and it also gets reset every time you reset the server.  But for midrate servers, it does help make hunting for cards feel more tolerable.  I haven't had to kill 1000 Santa Porings with no card drop since I implemented it.

    • Love 2
  17. The way it works on a technical level is that it trades your item for an item in the range of 19432-20500 that has an equivalent view id.  Let's look at a quick example in SDE:

     

    image.thumb.png.ab375d426b803df6eb833af72623b201.png

     

    As you can see, the view ID for both of these items is 169, and Costume Small Ribbons is between 19432-20500, so it gives you this item if you give it Small Ribbons.  Note that it does not modify the contents of your item database in any way, so if you had a script attached to Costume Small Ribbons, it would execute, but I believe the default item DB does not have any scripts attached to costume headgear (you could always take a quick look through the Item DB just in case if you're worried).

  18. It's been a while, I've been busy with other projects, but I'm finally back for possibly a last Ragnarok update (granted, I've said that before).

     

    For starters, I finally found out how to properly configure BrowEdit to show all the textures properly.  This is not really that hard, I was just lazy before.  It's all in the config file and making sure it has the right paths.

    image.thumb.png.b44886420f538010386b49341b6891b5.png

    Finally proper textures and objects!

     

    This caused me to fall down a rabbit hole of map editing, now that it's actually not a total pain to work on.

     

    Something I've always felt is crucial to the identity of Ragnarok Online is the amount of freedom that you have in the game.  Unlike many MMORPGs where you basically just follow a linear questline, in Ragnarok you are free to go anywhere and do anything you want, at any time.  This is something I've tried to maintain and enhance throughout my work on the game, by working to increase the number of options you have to level at various parts of the game.  When it comes to the "present" phase of the game, I think this works pretty well.  But the past world map is much smaller, and although it has a fair number of dungeons, for low levels in particular your options are kinda limited.  So I figured it might need a few new areas.

     

    So the first thing I did was that I completed the Past Geffen map.  There is actually a map for Geffen in the Alpha, but it's extremely incomplete, so I polished it up.  I think it looks pretty good now.

    image.png.0a596cf0490ee719d3274cd6bc118dac.png

     

    To reach the past version of Geffen, you have to cross a forest map, which is actually a heavily edited version of 1@mjo1.  There's a few new enemies here, like Papilla and Verporta, but they are weak, this is a decent low level grinding area.

    image.png.7a40371239da6b9fbd73c44cb775771f.png

     

    Geffen of course leads to the past version of Geffen Dungeon, which in turn leads to Past Geffenia.  Past Geffenia is based on ordeal_a00 and ordeal_a02.  ordeal_a00 is a weird map, I assume this is another map that is full of warps or something in the official, but I instead changed it to have bridges.  There are new enemies here too, the beta guards and workers.  They drop Carnium, which is very useful for something we'll get to in a minute.

    image.png.681151942ae03d4ec0028611ce121d75.png

     

    Adding Bridges through browedit is a huge pain, by the way.  You have to raise all the GAT tiles perfectly for the bridge to work.  I eventually got good at this but it takes a long time:

    image.png.6ac7b8aa4a3990fccb2c5c2741a6030f.png

     

    Carnium Ore is much of the reason to visit Past Geffen.  Carnium can be used to forge enchantments!

    image.png.5821d7a45de043d7102c3b1ba3add6b0.png

    If you bring 10 Carnium, you can add an enchantment to your armor.  You can choose the armor piece using a very similar interface to the standard refiner, and you can choose which stat you want to add.  Something I think is interesting is that the strength of the enchantment depends on the refine level of the armor.  If your armor is +6 or less, you get +1 to the chosen stat.  +7 or +8 gives +2 to that stat, and a +9 or +10 armor gets a +3.  

    image.png.3c0a30927b2a40cd5751599a08c0f7f1.png

    I think this is an interesting mechanic because to some degree it acts as a counterbalance to the strength of the transcendent armor.  Another feature I've always felt is key to the success of Ragnarok Online is that gear doesn't really scale based on level.  Even a simple pair of shoes can become a very good piece of armor if it's very heavily upgraded.  This is a great mechanic because it means that finding even a common item like Shoes is never worthless, which is important because it ties into the freedom of the game.  Even if you're playing a high level character, this means you can still find useful items in areas that are below your level.  This is important because it makes it easier to play with your friends.  In many games, if you're even 5 levels up, everything in an area will be useless to you, but because of the card and equipment systems this is not the case in RO.  It's just one of many small things that makes this game such a masterpiece.  But transcendent armor somewhat goes against this idea.  Items like the Goibne set, for example, are actually a fair bit more powerful than something like a pair of Boots.  On my server, these items are actually a little weaker than they are in the base game, but they're still strong.  However, they're also much rarer, so getting a Goibne set to +7 is a heck of an investment.  So the enchantment system kind of lets the standard gear catch up to some degree.  Of course, if you're super dedicated you could make a Goibne set at +9 and it would be incredibly powerful, but if you're willing to work that hard you probably deserve the 2 extra stat points.

     

    I've attached the script for the enchanter to the bottom of this post in case you'd like something similar on your server.  Of course, you'd probably have to move it and you'd need to add a source of Carnium to your game, or simply change the item it uses.

     

    This isn't all the new stuff, though.  Instead of heading south to Past Geffen, you can instead head north from the forest map, which leads to a new area.

    image.png.6b516c1d43a299464732585a0f8d778c.png

    Even if you're familiar with the Korean version, you probably don't recognize this map.  This is actually gw_fild02, but I completely retextured it because I'm a crazy person and I wanted it to fit better visually with the forest.  You can find the Pitayas here, which are interesting little goobers.  There are 5 colours of Pitayas, and they drop a related set of cards.  They're all accessory cards that increase your resistance against a certain status by 50%.  For example, Green Pitaya gives 50% resistance against Stone Curse.  If you wear 2 different Pitaya cards, you get 100% resistance to those statuses instead.  So you could wear, say, Red and Blue Pitaya to be immune to Burning and Freeze, albeit at the cost of both accessory slots.  This is interesting on its own, but there's also King Pitaya, an MVP that spawns on this map.  King Pitaya drops a dagger and an armor that also get bonuses based on any equipped Pitaya cards.  The armor is mediocre, but gains stat bonuses depending on which Pitaya cards are equipped.  For example, Green Pitaya gives it Def +4.  The weapon has a chance to inflict whichever status your pitaya cards make you resistant too.  King Pitaya's own card is an armor card that lets you inflict the statuses of the Pitaya cards that you have equipped on attackers when being hit, though of course it's a boss card so it's hard to get your hands on.  It's an interesting set of items that can be pretty powerful if you get a lot of them.

     

    You can keep going to reach oz_dun, which is a pretty high level dungeon that contains the Airboat enemies, like Grave Worm and Brain Sucker.  By the way, if you're wondering where these enemies are coming from, they're in the latest Korean GRF, which I've been harvesting for more content.  At the end of oz_dun you can meet the Dragon Wraith, who is another very difficult MVP.  I think it's kind of cool that this boss is sort of similar in concept to the Zombie Dragon, a cancelled MVP from a long time ago.

    image.png.2dd3156506aa68aaa0544acb7da4a396.png

    Of course, none of this stuff was in the Alpha, but I think it helps to flesh out the Past area so it feels like there's appropriate variety now, and the enchantment system also gives you another reason to want to get here (as well as something you can pass back to lower-level characters via the storage to make getting this far with future characters somewhat easier.  That said, adding new content is starting to take a very long time, as I'm running out of maps and enemies to repurpose from the main game.  The new version doesn't tend to add many more field maps, they mostly just add single-map instanced dungeons that don't really fit with my version of the game.  I have to pretty heavily edit almost all new maps I add to the game, and there aren't many more that I could feasibly use.  Plus, it's reaching the point where coming up with new ideas for cards is getting to be really tough (I'm beginning to sympathize with the dev team now).  Still, I think it's reached a pretty good state where I no longer have many issues with any of the content in the game and there's a lot of cool new stuff to play around with.

    enchanter.txt

    • Upvote 1
    • Love 1
  19. I did some testing based on your video.  The double deletion of embyros is legitimately a glitch, though it only occurs on the front end, you'll get your embryos back if you relog.  The problem is that in the hom_call method, it was calling clif_delitem after pc_delitem, which it doesn't need to because pc_delitem also calls that method.  The answer is just to remove those two lines of code, which I've done in the source code above for the hom_call method.

     

    For the window not closing, I can't reproduce that on my side, even if you trigger the "multi-deletion" glitch above, it still closes.  Closing the window is linked to the "clif_menuskill_clear(sd);" call at the end of "clif_parse_SelectEgg".  This is totally unchanged in latest rathena so I'm not sure what could cause this not to function.  If you do figure it out please post it here because Whiteeagle seems to have the same issue.

     

    By the way, as for having multiple embryos of the same type, this is generally intended to be possible, and probably isn't feasibly preventable.  I agree it is kind of confusing to tell which is which if you have several, though as far as I can tell through my testing the window does behave properly, for example if you have 2 Filir Eggs and pick the second one, you do consistently get the one in the second position in your inventory, though of course as the items look identical keeping track of this is difficult.

  20. Been a bit of a slow time on Ragnarok lately, been super busy with work.  Making a few small polish updates here and there, like changes to minimaps and such.

     

    The biggest change is that I overhauled the game's achievement system.  To be honest, I'm not really that convinced the game needs an achievement system, but if it's going to be in the game, it might as well at least be functional.  In its current state it was clearly an afterthought, with like 80% of the achievements being broken and it not giving you any rewards even for the ones you can complete.  So I pretty much rewrote the entire thing from scratch.

     

    For starters, there's actually a glitch in current Rathena that makes a lot of the objectives impossible to complete, such as "Human's Greed Has No End", which just requires you to fail to refine once.  Obviously this should be easy to get, but you'll probably find that you don't have it.  This is because of an error in achievement.cpp:

     

    Find this block in "achievement_update_objectives"

    if (!ad->condition)
    	return false;

    This block tells it to tell the game to never approve an achievement that has no condition.  Obviously this is wrong, as many achievements have no conditions.  Outright remove that block.

    Amend the block below to be this to avoid a null pointer exception:

    if (ad->condition && !achievement_check_condition(ad->condition, sd, current_count)) // Parameters weren't met
    	return false;

    Achievements with no conditions now work, as Hollgrehenn will be happy to demonstrate:

    Achievements1.png.3d3ac5a8122925051db31369b372d9d3.png

     

    Some of the other achievements require you to find various Treasure Boxes in the field maps.  These work, but in PRE-RE, the script is not enabled by default.  The script that contains them is npc/re/other/achievements.txt.  

    This now works and you can get the achievements, but they're quite obnoxious to find, because the treasure chests only appear if you're within a 5 cell range of them.  This is the last two numbers on the npc line.  I increased this to 30.  Most of them are still hidden pretty well, but I moved some of them that were either totally out in the open, or hidden so obnoxiously well that you'd never see them without using a guide.  Now, many of the odd nooks and crannies on the various maps have a purpose, like this weird spot in the map below Prontera:

    Achievements2.png.3fab09b277c5e80d7bef81979efe3453.png

     

    This is a good start, but the system still had a long way to go.  Of the achievements that remain, many are unobtainable in PRE-RE (ie, reach level 100), are tied to quests that aren't in the game, or are just obnoxious (like defeating Celine Kimi 25 times).  So I basically purged most of the rest of them and made my own.

     

    The achievements are stored in both the achievement_db.yml in the database folder and achievement_list.lub in the System folder, which need to match.  Here's an example of one of my new achievements in case you ever wanted to make your own.

    This is the entry in achievement_db.yml

      - ID: 128010
        Group: "AG_BATTLE"
        Name: "Mother's Love"
        Target:
          - Id: 0
            MobID: 1147
            Count: 1
        Score: 10

    This is quite straightforward.  AG_BATTLE achievements simply require the named mob to be killed COUNT times.  Since the count here is 1 and the MobID is 1147, this requires killing Maya once.

     

    And here's achievement_list.lub, which contains the front-end information for that achievement:

    	[128010] = {
    		UI_Type = 1,
    		group = "BATTLE",
    		major = 5,
    		minor = 0,
    		title = "Mother's Love",
    		content = {
    			summary = "Defeat Maya",
    			details = "Defeat the MVP in Ant Hell"
    		},
    		resource = { [1] = { text = "Eliminate Maya", count = 1 } },
    		reward = {},
    		score = 10
    	},

    The structure here is not too hard to understand.  The major and minor tell the game what group it is in, this one is in the "MVP" category and the "Midgard" subcategory.   Here's the achievement in-game:

    Achievements3.png.4f35c8f683cda057fe58334001a0a400.png

     

    I've changed some of the labels in this window (for example, the "Past" category is new), this is in the msgstringtable inside the grf file.


    Almost all of the MVPs in the game now have an achievement tied to them, which only requires you to beat them once.  They are almost all dumb puns or references, like this one for Dracula.  Actually, this isn't a very good screenshot since his death animation already ended, but soloing him with this character was a pain so I'm going to post it anyway.

    Achievements4.png.23b1725d0b209cbd34a15ed69a3e06ba.png

     

    Finally, there needed to be some reward for actually doing these achievements.  A few achievements had buffs or titles associated with them (many of these were commented out, but that's easy enough to re-enable), but for the ones that are tied to your overall achievement level, I've put a few decently worthwhile things in there.  The first few levels are super easy to get and only reward very basic things like OBBs, but if you get to higher levels you can start getting a few nice things like OCAs and some boxes that contain a few of the premium refining materials, which are otherwise very hard to get on my server.  It's nothing game-changing, but it's something.  Incidentally, I also changed the achievement level thresholds because they were very weird initially, this is also in achievement.cpp.

    Achievements5.png.64bbe4c2b5a7de83520c309c5eaf967f.png

     

    With that, I think this system is now decently workable and fun.  I still kinda don't like the fact that some achievements are mutually exclusive so you can't get them all (ie, there are some tied to changing to certain jobs, which you can only do some of on any given character), but there's so many achievements that I doubt anyone would be crazy enough to try to get them all anyway (you hit achievement level 20 long before you run out of achievements to get).

     

    Speaking of things that are mutually exclusive, I also added something to the Summoner class that I think is interesting:

    NewCatSkills.png.e54ef9f6a9bcf13b1b25ae9926420687.png

     

    Previously, the Doram had the "Soul Attack" skill, which causes your normal attack to become a ranged attack skill.  I felt this was too much of a no-brainer for one skill point, so I turned it into a 5-point skill, though it then felt a little weak.  I had also been toying with the idea of the Doram getting some new buffs that aren't tied to their skill trees, though I didn't want to significantly increase the power of the class since it was already pretty good, so I finally came up with this.  In addition to Soul Attack (which is called Spirit Attack here), the Summoner now has two other buffs in Spirit Shield and Spirit Focus.  However, you can only take one of them, once you know one of them, the others are no longer available (this required quite a bit of custom code as nothing in the game currently works this way).  Each provides a pretty decent benefit at level 5, but you have to choose.

     

    Spirit Shield grants VIT Def and at level 5 it reduces incoming damage by 50% if your HP is full.  This is nice for preventing one shots, but it's even more powerful if combined with Tuna Party, which gives you barrier HP.  Yes, the damaging halving effect also halves damage done to the barrier, which is kind of bananas.  To stop this from being ludicrously OP, the barrier has a bit less health than it had originally, costs more SP, and you can't recast it if you already have it, it has to expire or be broken first.

     

    Spirit Focus reduces cast time and gives a 50% chance to maintain concentration if being hit while spellcasting at level 5.  Of the three, the level 5 benefit is probably the weakest here (there's plenty of ways to get uninterruptable cast that works all the time), but the reduced cast time effect is very powerful.

     

    Spirit Attack grants mastery attack (which is better on my server than on most others because I use Renewal's mastery attack formula where it's added at the beginning of the damage calculation), and at level 5, it also makes your normal attack ranged.  However, it has another bonus - it also adds +2 range to all ranged attacks, similar to Vulture's Eye, except that it affects EVERYTHING.  Ranged attacks, heals, buffs, skills you get via cards or equipment, you name it, if it has over 4 range, it gains +2 range.  I think there's a lot of interesting applications for this.

     

    But of course, whichever one you choose, you have to give up the others.  Which to take?  I think it's an interesting dilemma that helps make the class feel unique.

    • Upvote 2
    • Love 1
  21. I know what you're probably thinking, "sure, I've got lots of great body colours for cats now, but what about cat heads?".  The Doram have a decent selection of hair colours available by default for the doram heads that have hair, but the animal heads have no variations, which is lame.  Well, I've got you covered, with a set of 11 new colours for the Doram.  There's a healthy mix of "feasible colours for an actual cat" and "absurd fantasy colours" to choose from.

    Catheads.png.aa7099f8c87181709dbaa5d3fe5c4014.png

     

    With the client I have, only 6 Doram heads are supported, if you try to choose a head higher than 6 it just displays head 6, so only 6 heads for male and female are included.  As some of the 6 default heads are just recolours of the same sprite, I've shifted their positions around a little bit with the heads from the latest korean version to get more variety, though the other 4 heads are still included in the GRF for compatibility if your client supports them (though they have no new palettes).

     

    To use this, add this to the top of your data.ini file (unlike with the body palettes, we're overriding the default palettes here, so it needs to go before your data.grf file).  Alternatively, merge it with an existing grf or data folder.  I've also included a new version of the stylist that takes into account the new hair limits for Doram, and also has some minor upgrades (it allows you to select colour 0 for hair and starts you on your current palette rather than at the beginning).

    doram-hair.grf

    stylist.txt

    • Upvote 3
    • Like 1
  22. I don't think you should have to disable that.  The lines above should do that for you.

     

    //quick option to disable all renewal option, used by ./configure
    #define PRERE
    #ifndef PRERE

    Make sure those lines are not commented out.  It looks like the "configure" script checks specifically to see if PRERE is enabled.

  23. Well, this won't solve the problem, but an easy way to try to track it down is to use Warning statements.

     

    Add something like this to the code below the line that calculates the aspd.

    ShowWarning("Calculated Aspd as %d\n", status->amotion);

     

    Then watch the map server window when the game calculates your aspd (which it will do when you log in) to see what calculation it's getting.  If it seems normal, try checking it after it applies the status changes (this applies shortly after this code).

×
×
  • Create New...