28. Juni 2010

Alright, after a few more hours of reverse engineering i gave up for now on trying to trigger the traps client side. I tried everything i could think of, like i thought that a player had a hitbox associated in the zoneentry struct - i even found a new value, the players size (strangely, shrink/grow could theoretically persist between zones fine) but it do anything. I also found another strange value, basically it will automatically pan your first person camera up or down very slowly automatically when you login. Depending on the value this can be done faster, but the issue is after a certain degree is reached your mouse will have the left and right side mixed, you are basically staring at the roof and in first person it looks like you are walking on the roof.

I also found that the client had a check when you got near a trap, it removes any invisibility spell - the funny thing is, this only triggers when im standing at very specific locations at a trap. This initially let me believe that there must be some kind of radius i need to set how big the area should be the trap effects, but it was still hopeless =/.

Anyway, i finished the sense trap skill and the disarm trap skill. There are two possibilities to use the disarm trap skill - either click like mad on the trap itself, or use the disarm trap skill button, on the server the nearest trap will be located and then it will be tried to be disarmed. Once a trap is disarmed, it will stop for about a minute and you will not "sense" it anymore since it is disarmed, you will sense another trap if any is nearby, same applies for disarm.

Currently whats missing is obviously that no damage is generated from traps, i had to do that server side anyway for the kunark/velious traps which were just invisible mobs which triggered on proximity instead of "on hit" like the few dozens classic traps in SolA+B and paw.

Instead of 99%, i am now 99.99% certain that there is no damage trigger client side and it was all generated server side back in the days.

~ 840min
= 4380min (73h)

25. Juni 2010

I started to work on Traps and their associated skills sense traps & disarm traps. I figured out what kind of packet the server has to sent to the client, that he will turn into the direction of the next trap.

I also figured out the disarm trap skill so far, it seems there are two possibilities here - a rogue with at least 1 in disarm trap skill can click on a trap OR just use the skill disarm traps to execute the disarm action. In the first case it is similar handled to opening a door (client sents to the server a request with the door/trap ID), in the latter case its just the info that the client wants to use the disarm trap skill without any associated trap. The clicking on the trap is rather easy at first, but you had to figure out what to sent back that the trap visually stops moving. Its another value in the open/close door action which we already use for closing/opening doors on the client.

So visually disarm trap for clicking a trap works, i need to do some server side code for using the skill button to figure out which is the nearest trap next to the player, same goes for sense traps. This is the easy part.

However, i spent most of my time debugging the client and figure out why no damage is generated from traps when you walk into them. The damage should be done client side i think, its the same with drowning or falling in lava - the client recognizes this event and will do damage locally and only sent the info (and the damage done) to the server. I went as far as logging into EQL and going to SolusekA to take a look how the traps work, the damage of each trap was hardcoded and always the same for each different type. What is funny is that using the damage value information i was able to find the client side function, which returns a different trap damage value depending on the trap type (pendulum, spears, saw). I spent most of my time reversing were this function is called from - but there was no indication that the client would do a check if the player is near a trap - damage him.

I will spent some more time reversing the client and trying to find out why the trap doesnt do damage, might be still a specific byte in the door/trap struct we sent to the client to spawn these door/traps - who knows. I currently think that the older client didnt trigger the damage, it was done server side and later changed to be client side - it would be easy to do this on the server (similar to sense traps, if a client gets near a trap the server would damage him). However it would have the visually effect of doing damage to the client, even when the pendulum is currently not hitting the client since the animation is done by the client the server cannot possibly know when it would hit the player, it can only know that the client is standing where a trap is.

When you think about it, that there are only 20 real traps in classic in 3 dungeons (soluseka,b and paw) - thats alot of engineering going into such a small part of the game =).

~ 660min
= 3540min (59h)

23. Juni 2010

Pretty boring bug hunting, trying to figure out why the zone crashes when it is statically loaded, i.e. all NPCs/Stuff already started before any client ever connects. Had something todo with the Perl Interpreter not being available to the EntityList, i have no idea why this happends as it only happends when the zone is statically loaded. When it is loaded dynamically (i.e. when the client wants to connect to a zone, the zone is loaded with NPCs and stuff) it works fine. Maybe because the instance of the interpreter was created in the main thread instead of sub threads? I fixed it but sadly im not sure why it didnt work before, this happens when you are more a generalist instead of a specialist in one specific programming language. This alone took over a day =/

I additionally added the last missing things for range attack, rogues deadly strike, i guess range attack should be done for now. Also some refactoring on some ugly code like melee kick attack. Warriors at or above lvl55 now have a chance to stun their target when kicking.

~ 720min
= 2880min (48h)

21. Juni 2010

I just wanted to try something out, but only ended up creating some work for myself.
I wanted to check some things out in kedges keep and teleported there, of course i forgot to cast an enduring breath spell - but that wasnt the major issue. As soon as i zoned into underwater i started to drown, which should normally not happend because you got an air bar as soon as you dive into water.

Well i figured that how much air the player has left has not been identified in the playerprofile, so we probably always sent 0 when the player zones. I searched with the debugger in the client to identify the function, where the "air is left" check is done. At first i thought i was quick finding it, i set the value to 255(0xff) server side and tried to relogin while being underwater, but it didnt help - i started to drown again. Confused i tried some other unknown fields we had - same result. A few hours later i figured, on the client there was another function to sanitize the air left value while checking - 255 wasnt a good value. So i started with the first field in the playerprofile i thought i had found. I set it to 10, and zoned in - i didnt start to drown immediately but the air left window at least popped up and was almost zero. After a saw that the client had 2 max values for this field - it was either 100 or 127 for iksars (they can hold their breath pretty long =). So after this field was identified, i could save it when the client requests a save - and the next time the client zones it will take the last saved value. So, you should drown instantly as soon as you dive from qeynos to qcat =). The time on this field is approx in seconds, it seems the client generates this value out of the endurance/stamina the client has. So normally a client can dive over one minute, or iksar over 2 before the air runs out - pretty long i must say, didnt remember that. Fun fact: the client substracts 0.125 * YourHP every drowning interval, so you are pretty much dead after 8 intervals, no matter how much HP you have left.

Next i added the broadcasting of tradeskill combine/failures to the group, as it was classic as pointed out in the forum - the part was already there when i reworked tradeskills last year but i left it outcommented because i wasnt sure it was classic.

Finally, i added a new gm method #listentities to view all the npcs that are currently spawned in the zone along with XYZ. Along with this its easy to use /goto a_mob_00 to teleport to him.

~ 300min
= 2160min

18. Juni 2010

Well, after some more testing with the items i committed the changes, but only after fixing another issue. I was trying out the summon spells, since they summoned a wrong amount of charges. I got to try out gift of xev, it summons a bag and bandages and food and stuff. So i put the bag in the inventory, opened it, and wanted to put in the food,drink and bandages i got on my cursor.... well but the client said item to big..lolwut? Then i tested other bags, and even tinkerer bags were "tiny" - i.e. only tiny items could be put in.

I checked the loading code from the database and it seemed fine, i didnt understand why the bagSize was always 0.... well after a while it turns out it was a really stupid mistake. I was always fixing the race/class bitmask (see earlier posting) at the very end of the loading code for each item. Well, however these fields in the item struct are only used by "normal" items, not by books or bags. When you set the race data in the struct, you effectivly overwritting the bagSize property cause they are both at similar position in the itemstruct bytewise. So it went like this: load item from DB -> ok its a bag -> set bag attributes -> do stuff -> finally set race/class bits == same position in struct as bag attributes -> overwrite these values again (with 0 in this case = 0 = tiny bag size).

It must have been a design decision by verant very early to missuse the same struct position of an item depending on the item type - to save some network traffic i guess.

As far as summoning items is concerned i only found one spell which really have a level based code for the amount of charges returned -> summon bandages (level/2) - any other spell would always return the max value regardless of level (20 arrows/daggers/food/drink for Cornucopia and co).

~ 240min
= 1860min

17. Juni 2010

I figured out the issue, that for example bows were not showing a 3d graphic on the model. The newer item table has other "idfiles" associated with many items. An idfile tell the client for each item, which model it should display when it is equipped. Now the newer table had idfiles like IT10649 - a very high number - the classic client doesnt know about newer model files. In this case, this is a newer sword graphic, but in the blob table the idfile was still IT1.

I started to search for items, where the idfile attribute in the item differs. After about an hour and more then 15 different idfile mappings i came to the conclusion that proceeding a manual mapping for each item would be an insane job, which no real programmer would do =).

So i wrote a program which compares each item from the blob and non blob table, and takes the idfile information from the old table. When an item is not found in the blob table but in the newer one, i used the idfile of a similar item which had a similar new_idfile->old_idfile mapping. The program procued an SQL delta update file which sets the idfile for all items which existed in the blob table. This way we should have a relatively complete item table but with the correct idfile mappings of older clients.

I verified alot of things i could think of with the new item information

- clickies
- trades
- buying
- equip
- check item description

i havent found an issue, the client doesnt complain. There should be no issues for new characters, but maybe some items are missing for existing ones, therefor before i do a commit i let YL test it on his chars if everything works out.

I also toyed around with the loginserver, i did not try it out yet. I got it to compile quite easily thanks to neorabs description txt. For developing, we just skip the loginserver and connect directly to our local server, you are directly transfered to char selection instead of going to the server select.
Was funny to see the old interface, i clicked on Create Account and remember the screen where you needed to put in your CD key, name, address etc to get your account approved. There wasnt a webpage to register in classic, everything was done using the client.

~ 480min
= 1620min

16. Juni 2010

I started migrating the non blob item tables into the main code. Currently we use an old item table which contains like 2 rows, the item id and a binary blob of the data which represents the item. Last year i was successful in creating items (fill our item structs) from a non blob table, i.e. this table contains a row for every attribute of an item. This makes it easier to fix or add missing items.

The first issues i had where the number of items, newer item table dumps contain alot of items (which we will not use), and the eqclient only supports 16bit integer MAX number of items. The item # in the item struct is just 16bit, since i forgot about that i was seeing all sorts of weired things happening when i logged into the zone, corpses had 20 items on them which looked like gloves, my equipment vanished mostly, i was getting alot of "bogus item received, item deleted" messages from the client. The glove item was in the item table exactly at # 16bit int max, so it overwrote the array each time =).

The next thing with the bogus items was, the race and class bits on each item. EQ uses a bitmask to know which class/race can use an item, each class/race represents one bit like 0001, another class is 0010, now when you have 1111 it means all class/races can equip this item. So, the issue here was that the newer item table dumps included a higher bitmask for all classes/races - because in the years more classes and races have been added, so i just needed to remove the higher bits. That means the items use a bitmask of 11111111 11111111 - but the client only knows up to
01111111 11111111 for classes, and 00011111 11111111 for races. After this fix, you wouldnt get any messages about bogus items any more.

Now i tried a few things, like shopping, dropping, trading etc the newer item tables
seems to work fine so far, i only found one issue - some bows do not show a graphic when you equip them. The item struct contains like over 100 attributes, and only a few bits are still unknown to us, these seem to have a meaning and are causing this issue.

~ 420min
= 1140min
I refactored the range attack code this time, it looks much clearer now. I did quite some research on some hard numbers for the damage calculation. I found some info on alakazahm and the monkly business forum. The rangers glade forums have not been archived, only the front page so i did not find anything worthwhile there. The information i gathered is that range attack calculation is similar to melee combat, except that it uses DEX as a primary attribute and not STR. Therefor i used the miss calculations from the melee combat, if we do any changes there it will automatically apply to range combat.

I didnt know that there where range items with a proc, but since i was testing bow damage i found one of the top bows from a PoA quest - windstiker which has a cool proc effect Whirlwind. The mob spins around itself like M.J. dance. Turns out there wasnt any code for handling proc damage from a range item, which i therefor added.

As i was equipping my ranger with some nice tolans gear, i saw that they all had nice spell effects. The bracers had summon arrows, i tried it out and for the long casting time it had - only one arrow came out. That couldn't be right so i tried some mage spells which summon 20 charges of food - the same issue, only one item. Turns out that the max charge info was not honored in the spell code for summon items, i fixed that and the correct amount should be summoned now (the information is found i nthe spdat spell info and seems to be correct, i checked summon bandages which should summon 5 bandages and all other mage spells, seemed fine).

~ 300min
= 720min

8. Juni 2010

I tried (again) to figure out why the first spell gem ignores the recast timeout sent from the server to the client. The recast timeouts are an array of int with milliseconds till cooldown is done. It works flawless for spell gems 2-8, but not for the first. Its frustrating to see it should be so easy, yet it is not. Its similar to the issue i had with disciplines not working (executing /disc xxx never reached the server) because one bit in the profile was not set (one of 10000 =p).

Anyway, in my reverse engineering i figured that the client has 2 different checks for the recast delay, one is executed for the velious fullscreen ui, one for the classic UI (it can drive you insane when you just found a breakpoint, but for no reason it doesnt work the next time you start the eqclient). The check points to some memory location depending on the spell gem number, if the value is 4, the spell is on a cooldown. Well, now the issue is i havent found where this value is set - there must be a separate thread which countdown the recast delay and set this.

So finally, i came to the conclusion that it mostly aint an error in the playerprofile, why?

When you set the value in the profile, which is loaded once you zone in, the spell gems are greyed out the second you are logged in, except for the first one. There is a similar client side mechanism when you cast a spell - the client knows what the recast timer the spell has. So i did a small test finally, I cast a spell with a recast of 12sec, like bind affinity - on the first gem slot the spell was available immediately (after global cooldown), the same spell however had the correct recast cooldown in any other slot. So this leads me to believe that this is indeed a bug in the client.

There is a slim chance its something else, but it doesnt look like it - i could only be certain when i had the executeable from either a later patch or an earlier velious one (we currently use the 22/08/2001 patch exe).

Its actually not even worth investing so much time into this, because its purely cosmetically (you cant cast the first gem anyway when the server prohibits it, but its not greyed out). However, issues like these are like riddles which just scream out loud to be solved by reverse engineering to get a better understanding of the client.

~ 300min
= 420min

2. Juni 2010

Ever wondered why the endurance bar went up and down very fast in the showcase videos?

Well, it turns out that the clients check for food/drink excluded GMs (thanks IDA Pro). They are never thirsty, however the eqc server did not know that, so GMs were mostly always thirsty/hungry server side and we would sent 0 endurance update to the client. However, the client thought otherwise (he also temporary updates the endurance client side), thats why - when the client updates the endurance bar a small bit of endurance was shown, but every few seconds the server sent 0 endurance back to the client.

I also added a check to remove endurance while being in water during the regular endurance calculation. It actually was already there but not enabled, because when Taz implemented the logic 2008, there were no water check support in the eqc server, i added that in late 2009.

~ 120min