PDA

View Full Version : Some stuff


KLS
10-16-2006, 06:20 PM
Gonna try to keep most of my future small posts in this thread to avoid my name over-running the development forum.

Was rather quiet in IRC tonight so I'm gonna post the ideas I came up with tonight.

Looking at the recourse code and something seems not quite right with it, I use a SK for testing a lot and noticed torrent of pain doesn't hit either my self or my group, I think it's because of how the target types are done with the recourse code. Also I think it's not well placed in the casting sequence; the recourse code is put in before the resist check so even if your target resists the spell the recourse will still go off and this seems wrong to me.

Reference Spells:
http://lucy.allakhazam.com/spell.html?id=2486 Group v1 (ST_GroupTeleport)
http://lucy.allakhazam.com/spell.html?id=2480 Group v1 (ST_GroupTeleport)


Recourse was in Mob::SpellFinished():

at the top:

int recourse_spell=0;

little further down approx ln 1423

// Recourse means there is a spell linked to that spell in that the recourse spell will
// be automatically casted on the casters group or the caster only depending on Targettype
// solar: this is for things like dark empathy, shadow vortex
recourse_spell = spells[spell_id].RecourseLink;
if(recourse_spell != 0)
{
if(spells[recourse_spell].targettype == ST_Group) {
if(IsGrouped()) {
Group *g = entity_list.GetGroupByMob(this);;
g->CastGroupSpell(this, recourse_spell);
} else {
SpellOnTarget(recourse_spell, this);
#ifdef GROUP_BUFF_PETS
if (HasPet())
SpellOnTarget(recourse_spell, GetPet());
#endif
}
} else if(spells[recourse_spell].targettype == ST_GroupTeleport) {
// EverHood - Necro Epic 2 Pet Proc Recourse
if(HasOwner()) {
if(GetOwner()->IsGrouped()) {
Group *g = entity_list.GetGroupByMob(this->GetOwner());
g->CastGroupSpell(this, recourse_spell);
} else {
SpellOnTarget(recourse_spell, this->GetOwner());
}
}
} else {
SpellOnTarget(recourse_spell, this);
}
}


First I'd move it down to spellontarget() I suppose, right after the resist check to make sure the spell landed before the recourse goes off.

Then I'd change the code a bit to be something like:

// Recourse means there is a spell linked to that spell in that the recourse spell will
// be automatically casted on the casters group or the caster only depending on Targettype
// solar: this is for things like dark empathy, shadow vortex
int recourse_spell=0;
recourse_spell = spells[spell_id].RecourseLink;
if(recourse_spell)
{
if(spells[recourse_spell].targettype == ST_Group || spells[recourse_spell].targettype == ST_GroupTeleport)
{
if(IsGrouped())
{
Group *g = entity_list.GetGroupByMob(this);
g->CastGroupSpell(this, recourse_spell);
}
else if(HasOwner())
{
if(GetOwner->IsGrouped())
{
Group *g = entity_list.GetGroupByMob(GetOwner());
g->CastGroupSpell(this, recourse_spell);
}

}
else
{
SpellOnTarget(recourse_spell, this);
#ifdef GROUP_BUFF_PETS
if (HasPet())
SpellOnTarget(recourse_spell, GetPet());
#endif
}

}
else
{
SpellOnTarget(recourse_spell, this);
}
}

Another thing I want to change is a bit of code in Client::GetFocusEffect() because the focus is calculated even for duration spells, like it should be but you always get the glow message as well which is really annoying, you should really only be getting it for non duration spells, heals, nukes etc.

you'll find the lines
if (realTotal > 0 && UsedItem) {
Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name);
}

I just replaced it with
if (realTotal > 0 && UsedItem && spells[spell_id].buffduration == 0) {
Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name);
}

I think that should be effective in eliminating redundant spell focus spam.

Also I noticed in the last version I pulled up from source the first few lines in Client::Attack() remained unchanged despite what the changelog says. Really I think the first log statement needs to be changed because of it's potential to cause a crash.

bool Client::Attack(Mob* other, int Hand, bool bRiposte)
{
_ZP(Client_Attack);

mlog(COMBAT__ATTACKS, "Attacking %s with hand %d %s", other->GetName(), Hand, bRiposte?"(this is a riposte)":"");

//SetAttackTimer();
if (
(IsCasting() && GetClass() != BARD)
|| other == NULL
|| ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead))
|| (GetHP() < 0)
|| (!IsAttackAllowed(other))
) {
mlog(COMBAT__ATTACKS, "Attack canceled, invalid circumstances.");
return false; // Only bards can attack while casting
}

Perhaps it could be changed to something like

bool Client::Attack(Mob* other, int Hand, bool bRiposte)
{
_ZP(Client_Attack);

//SetAttackTimer();
if (
(IsCasting() && GetClass() != BARD)
|| other == NULL
|| ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead))
|| (GetHP() < 0)
|| (!IsAttackAllowed(other))
) {
mlog(COMBAT__ATTACKS, "Was trying to attack someone with hand %d, but the attack was canceled because of invalid circumstances.", Hand);
return false; // Only bards can attack while casting
}
else{
mlog(COMBAT__ATTACKS, "Attacking %s with hand %d %s", other->GetName(), Hand, bRiposte?"(this is a riposte)":"");
}

This would make sure we verify other before we attempt to access it, avoiding a potential crash.

Also in Client::Message()

void Client::Message(uint32 type, const char* message, ...) {
va_list argptr;
char *buffer = new char[4096];

if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee)
return;
if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self...
return;
if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits)
return;

va_start(argptr, message);
vsnprintf(buffer, 4096, message, argptr);
va_end(argptr);

uint32 len = strlen(buffer);

//client dosent like our packet all the time unless
//we make it really big, then it seems to not care that
//our header is malformed.
//len = 4096 - sizeof(SpecialMesg_Struct);

uint32 len_packet = sizeof(SpecialMesg_Struct)+len;
EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet);
SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer;
sm->header[0] = 0x00; // Header used for #emote style messages..
sm->header[1] = 0x00; // Play around with these to see other types
sm->header[2] = 0x00;
//sm->msg_type = type;
sm->msg_type = 0x0A;


Is there any reason that we fix the type to 0x0A? And I'm guessing it's preferable to use the preformatted messages where it's possible as well, since I'm guessing the packet they send is smaller?

Don't have time to compile and test this stuff tonight, I'll try to get around to it tomorrow but in the mean time if you see anything wrong with my ideas feel free to let me have it.

KLS
10-17-2006, 07:43 PM
Got a little work done tonight, nothing I wrote about above, was more looking at the endurance implementation. Some things:

The calc max endurance formula used is wrong, while trying to simplify it certainly makes it easier to read it also leads it to some pretty bad rounding errors. My warrior lost a good 800 endurance with your simplified version.
This is the new version I wrote that didn't use non standard min/max() and added in commenting so the logic is understandable

//Info taken from magelo, it's a *little* off but accurate enough.
void Client::CalcMaxEndurance()
{
//"Stats" the total of (Str + Dex + Sta + Agi)
int Stats = GetSTR()+GetSTA()+GetDEX()+GetAGI();

//"Levelbonus" your Level * .075
//Endurance = level * 15 plus

//Levelbonus times the sum of the next 4 lines (this is calculated on each line, not at the end because of rounding errors otherwise)
max_end = GetLevel() * 15;
//plus lesser of Stats and 800, divide that by 4.
max_end += int((Stats>800?800:Stats)/4)*0.075*GetLevel();
//plus bigger of (lesser of Stats and 800)-400, and 0. all of that /4
max_end += int((((Stats>800?800:Stats)-400)>0?((Stats>800?800:Stats)-400):0)/4)*(0.075*GetLevel());
//plus bigger of (lesser of Stats and 800)-400, and 0. all of that /8
max_end += int((((Stats>800?800:Stats)-400)>0?((Stats>800?800:Stats)-400):0)/8)*(0.075*GetLevel());
//plus bigger of (Stats - 800 and zero) / 8
max_end += int((Stats>800?Stats:0)/8)*(0.075*GetLevel())*2;
//plus bigger of (Stats - 800 and zero) / 16
max_end += int((Stats>800?Stats:0)/16)*(0.075*GetLevel());
//plus our endurance from items and spells.
max_end += spellbonuses.Endurance + itembonuses.Endurance;
//Maurice of Magelo fame explained that we can't simplify the statements
//because we have to round every step of the way.
}


Also something appears up with how stats are calculated for clients, I'm not sure exactly where there's a problem but well let me show you:

Sarrie 65 War naked
http://i8.photobucket.com/albums/a2/kimmysprite/naked.jpg

Sarrie 65 War with her full gear on
http://i8.photobucket.com/albums/a2/kimmysprite/notnaked.jpg

It appears almost like itembonuses are being counted twice serverside. Ex. Sarrie had +86 wisdom fully geared and had a base of 70 shoulda been 156 serverside like clientside but was 242. 70 + (86*2) = 242. And obviously stats aren't capped correctly which is gonna mess with hp/mana/endurance calculations, among other things.

Not exactly sure what causes it though.
Also though I haven't gotten around to testing it I'm pretty sure this is the case:

In the itemfield encoding for titanium I believe

/* 083 */ I(AugSlotType[0])
/* 084 */ I(AugSlotUnk[0])
/* 085 */ I(AugSlotType[1])
/* 086 */ I(AugSlotUnk[1])
/* 087 */ I(AugSlotType[2])
/* 088 */ I(AugSlotUnk[2])
/* 089 */ I(AugSlotType[3])
/* 090 */ I(AugSlotUnk[3])
/* 091 */ I(AugSlotType[4])
/* 092 */ I(AugSlotUnk[4])

is actually backwards with the unknown spot coming before the slot, I believe that's why aug slots don't appear on items for titanium.

I'll try to look into all this a little bit more and get some more info.

KLS
10-18-2006, 04:42 AM
//Info taken from magelo, it's a *little* off but accurate enough.
void Client::CalcMaxEndurance()
{
//"Stats" the total of (Str + Dex + Sta + Agi)
int Stats = GetSTR()+GetSTA()+GetDEX()+GetAGI();

//"Levelbonus" your Level * .075
//Endurance = level * 15 plus

//Levelbonus times the sum of the next 4 lines (this is calculated on each line, not at the end because of rounding errors otherwise)
max_end = GetLevel() * 15;
//plus lesser of Stats and 800, divide that by 4.
max_end += int((Stats>800?800:Stats)/4)*0.075*GetLevel();
//plus bigger of (lesser of Stats and 800)-400, and 0. all of that /4
max_end += int((((Stats>800?800:Stats)-400)>0?((Stats>800?800:Stats)-400):0)/4)*(0.075*GetLevel());
//plus bigger of (lesser of Stats and 800)-400, and 0. all of that /8
max_end += int((((Stats>800?800:Stats)-400)>0?((Stats>800?800:Stats)-400):0)/8)*(0.075*GetLevel());
//plus bigger of (Stats - 800 and zero) / 8
max_end += int(((Stats-800)>0?(Stats-800):0)/8)*(0.075*GetLevel())*2;
//plus bigger of (Stats - 800 and zero) / 16
max_end += int(((Stats-800)?(Stats-800):0)/16)*(0.075*GetLevel());
//plus our endurance from items and spells.
max_end += spellbonuses.Endurance + itembonuses.Endurance;
//Maurice of Magelo fame explained that we can't simplify the statements
//because we have to round every step of the way.
}

Was two typos in the calcmaxend I made, what I get for working while tired.

The double stats comes from functions that use the Mob::Get<somestat>() that instead of return <somestat>; return <somestat> + itembonuses.<somestat> + spellbonuses.<somestat>. Not what solution there would be other than making sure code that involves clients uses client::getstat instead of mob::getstat in the code, which will probably be a little tedious given the amount of times they're referenced.

The aug thing, I'm not sure how they're serialized in the code yet so take that with a grain of salt but I confirmed when I set in the database:

augslottype1 = 0;
augslot1unk = 7;
That I created an item with an aug slot of 7 clientside, trying to apply it to an item was a little troublesome. I successfully inserted the augment into the item, however after you finish the insert the items in the pool are not deleted immediatly and attempting to remove them causes the client to crash, so obviously some work to be done there.

The glow message thing appears to work, and tested recourse; took out old recourse code and replaced it with this in spellontarget rightbelow where resists are calculated.


if(spell_effectiveness == 100)
{
// Recourse means there is a spell linked to that spell in that the recourse spell will
// be automatically casted on the casters group or the caster only depending on Targettype
// solar: this is for things like dark empathy, shadow vortex
int recourse_spell=0;
recourse_spell = spells[spell_id].RecourseLink;
if(recourse_spell)
{
if(spells[recourse_spell].targettype == ST_Group || spells[recourse_spell].targettype == ST_GroupTeleport)
{
if(IsGrouped())
{
Group *g = entity_list.GetGroupByMob(this);
g->CastGroupSpell(this, recourse_spell);
}
else if(HasOwner())
{
if(GetOwner()->IsGrouped())
{
Group *g = entity_list.GetGroupByMob(GetOwner());
g->CastGroupSpell(this, recourse_spell);
}

}
else
{
SpellOnTarget(recourse_spell, this);
#ifdef GROUP_BUFF_PETS
if (HasPet())
SpellOnTarget(recourse_spell, GetPet());
#endif
}

}
else
{
SpellOnTarget(recourse_spell, this);
}
}
}

KLS
10-19-2006, 07:37 AM
Now some actual changes:

-Fixed the atk mlog bug, again.
-Changed how recourse works
-Made glow messages only go off for instant spells
-Changed how haste is calculated to account for caps and stacking correctly and to make sure we account for overhaste spells(ex warsong of the vahshir). Should fix one of the current dev issues with wonderous rapidity and nature's melody etc etc.
-The way hundredhands is calculated isn't correct, but I wasn't sure how to address it so I kept it the way it was implemented for now but replacing the true/false with actual effect values.
-Changed the end calculations slightly (but in a big way) GetLevel()*0.075 is added in every line to address some pretty massive rounding errors if you try to simplify it, we're talking a loss of 8-900 endurance when you get up in pop gear kinda rounding errors.
-Special Messages should be able to show a type other than 0x0A.

And svn created me a nifty little patch file
http://hmproject.org/files/haste.patch

KLS
10-21-2006, 03:50 PM
Another quick change. Did this pretty quickly so tell me if I messed any part of it up.

Dunno if it bothers anyone else as much as it bothers me but the small quickly used timers for the hard coded client skills keep bugging out on me, my sneak is down for the next 11 days according to my database for instance =(

Not sure at all what causes these short timers to bug out so I added a rule here on whether or not to enforce them, I mean do we really need to enforce them? They're hard coded into the client and it's going to be pretty rare that someone hacks into the client to change the timers, most are only a few seconds anyway.

Also threw in another rule that helps server admins modify the endurance regen since there really isn't any way to raise your regen outside of +endurance regen items, and def seems like something admins might want to change, just like an exp modifier.

new rules:

RULE_BOOL( Character, EnforceSkillTimers, false )
RULE_REAL( Character, EnduranceRegenBonus, 1.0 )

http://hmproject.org/files/timers.patch


Also would like to add that I love this rule system, it's a brilliant concept.

mattmeck
10-21-2006, 04:01 PM
People will hack the client for the timers, trust me. Instant Back Stab for instance.

This was added in because of the amount of people that were hacking the client.

vales
10-21-2006, 04:02 PM
I love you man!

These are some seriously bad-ass fixes. I'm gonna patch up the server and see the changes. Will report back if I see something out of the ordinary. :)

Keep up the awesome work!

KLS
10-21-2006, 05:18 PM
I'll look for what's happening to the shorter timers, I don't notice it happening with any of the longer ones, my disc timers for instance but it's certainly happening with some of the shorter ones kick sneak hide Feign. That's just there till I figure it out.

Edit: Alright, here's something I found with persistant timers, first I misread the timer in my database last time that led me to the rule conclusion it was really set to 0, 0. If a timer is set to duration 0, enable 0 in the database it wont count as expired, and apparently sometimes when removing timers instead of them being removed from the DB they get set to 0, 0 thus that skill is now bugged for that character(gg). Not sure of a fix yet but yeah thanks for not letting me take the lazy way out with my rule I guess.

fathernitwit
10-21-2006, 05:55 PM
overall the changes look good.

make sure you default any new rules to how they were before you added the rule... aka EnforceSkillTimers to true.

interesting about the item bonuses being counted twice... have you checked #showbuffs to see how it compares?

also in the endurance calculation, what I put in should have matched your original version here:
http://www.eqemulator.net/forums/showthread.php?t=21661
I am assuming that the int() operators in your original code were being used in place of floor()... but in any light, the 0.075 * GetLevel() in your oringinal code was outside the int(), so it should have had the same rounding effects as my code. If you are really not intending to floor() those numbers, than instead of multiplying out each time like you did, just change the variables in that function to be floats and then use max_end += bonus_sum * GetLevel()*0.075; at the end. (converting an int to a float or vise versa is generally a rather computationally expensive operation, and should be minimized)

KLS
10-21-2006, 06:49 PM
I think I've found the issues that affect timers. A timer that is not enabled and as a 0 duration will return false on get expired and never be cleared from the database, sometimes they do(I dunno why? but can we put a work around in the expired code?).

Also some of the hard coded combat skills are affected by haste clientside but not taking that into account server side. Ex just timed my backstab clientside with a stopwatch, about 8 seconds without haste 5.5 with.

The issues with get stats is it uses the Mob:: version of stats instead of Client:: we need to be very careful as a result when things can be both for clients and npcs. Get stats for instance uses the mob version and the mob version counts the item and spell bonuses twice for clients because they aren't counted at all for non clients outside of that.

I'm not sure what's with my head and the endurance sorry, I really had documented a major loss in it the other day but just went to go make sure I wasn't crazy and apparently I am, oh no =( I work too late, sorry mate, if I actually find the problem again I'll bring it back to you in excrutiating detail.

I'm editing this post way too much but yeah also I'm gonna go through and see about getting close to the actual times on the client side skills since some seem off, some by a lot(track/forage!).

KLS
10-22-2006, 08:54 AM
Alright after being informed last night how common the hackers were I looked into the timers further. Instead of the enforce rule I did this.

Checks to see if the timer is enabled where we check to see if timers are expired ex:

- if(!p_timers.Expired(&database, pTimerCombatAbility, false)) {
+ if(p_timers.Enabled(pTimerCombatAbility) && !p_timers.Expired(&database, pTimerCombatAbility, false)) {
If a timer is disabled for whatever reason we'll count it as expired for these situations, while this really shouldn't be happening it has happened a few times to me and made the skill that was timer enforced completely unusable unless I modified the database to fix it.

Also applied haste to the timers for combat, tested it somewhat and seemed to work fine.

http://hmproject.org/files/timers2.patch

KLS
10-22-2006, 02:15 PM
Notice you redid some of the special attack code to be more consistant with regular attacks. Looking over it I saw a few things, not really much from what you changed but what was already there in the orig. design.

In avoid damage there are several calls to functions Mob::CanThisClass<Whatever>() that always return false for npcs meaning they never dodge parry riposte except under certain conditions.

Example:
bool Mob::CanThisClassParry(void) const
{
// Trumpcard
switch(GetClass()) // Lets make sure they are the right level! -image
{
case WARRIOR:
{
if(GetLevel() < 10)
return false;
break;
}
case ROGUE:
case BERSERKER:
{
if(GetLevel() < 12)
return false;
break;
}
case BARD:
{
if(GetLevel() < 53)
return false;
break;
}
case RANGER:
{
if(GetLevel() < 18)
return false;
break;
}
case SHADOWKNIGHT:
case PALADIN:
{
if(GetLevel() < 17)
return false;
break;
}
default:
{
return false;
}
}

if (this->IsClient())
return(this->CastToClient()->GetSkill(PARRY) != 0); // No skill = no chance
else
return false;
}
We return false if it is not the correct level and get past the switch statement if we are, then we check if we are a client and have skill if so we're okay if not we're not a client we get a false return 100% of the time from that last return false which really should be true. Happens in the following functions:

bool Mob::CanThisClassParry(void) const
bool Mob::CanThisClassDodge(void) const
bool Mob::CanThisClassRiposte(void) const


Also the riposte code is seperate from avoid damage somewhat in that we only do the auto attack in the Attack() functions instead of in the avoid damage, as a result being riposted with special skills wont ever get you hurt because we don't do the counter attack in the special attack code.

I also question how we do the input damage to that code with regards to AC mitigation. What we do now is take a random number from the min and max hit in terms of combat and then throw that into the avoid damage which has a sort of confusing mitigation formula and this gives us an illusion of real AC mitigation with the random numbers being spit out at us. Really though we should be throwing in the max value and the mitigation should be throwing us back a new value that seems random based on our pure AC from items and our attackers str+atk+offense_skill, then checking the out number versus the min hit we can make.

Then there's also critical hits, currently nothing but auto attack can crit, and the code really just makes the attack() function look messy. Special attacks should be able to crit too so what if we had something like this that we could throw in after every time we do anything with melee:

void Mob::CriticalHit(Mob* defender, int16 skillinuse, sint32 &damage)
{
if(damage <= 0)
return;
//defender isn't used right now but it's nice to have incase we need to use
//it for the future.
float critChance = 0.0f
int critMod = 2;
if((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12 && IsClient())
{
critChance += 0.03f
if(CastToClient()->berserk)
{
critChance += 0.06f
critMod = 4;
}
}

switch(GetAA(aaCombatFury))
{
case 1:
critChance += 0.02f;
break;
case 2:
critChance += 0.04f;
break;
case 3:
critChance += 0.07f;
break;
default:
break;
}

critChance += ((critChance) * (spellbonuses.CriticalHitChance + itembonuses.CriticalHitChance) / 100.0f); //crit chance is a % increase to your reg chance
if(critChance > 0)
if(MakeRandomFloat(0, 1) <= critChance)
{
damage = (damage * critMod);
if(IsClient() && CastToClient()->berserk)
{
entity_list.MessageClose(this, false, 200, 10, "%s lands a crippling blow!(%d)", GetCleanName(), damage);
}
else
{
entity_list.MessageClose(this, false, 200, 10, "%s scores a critical hit!(%d)", GetCleanName(), damage);
}
}
}

fathernitwit
10-23-2006, 03:27 PM
I have always thought that the whole enabled/disabled concept was strange for ptimers. I think the best solution for them is to completely remove the concept... either they exist and are running, or they do not exist.

good eye on the CanThisClass* stuff.

as for mitigation, basically it isnt implemented for shit because nobody has been able to find reliable equations for it.

as for the special attacks/riposte stuff, we can keep patching the holes, and end up with a duplicate of Client::Attack(), but really we just need to refactor the attack functions to support special attacks, etc such that we are not duplicating code. I took the special attack code about as far as I thought was reasonable before crossing into duplication land.

I think the crit function looks reasonable, I would rename it to TryCriticalHit.

KLS
10-23-2006, 04:38 PM
I've come up with what I think is a reasonable AC mitigation formula but I'm still tweaking it, I have a question though, I'm using extensive rules in it as part of the tweaking so I don't have to recompile to push a value up or down a point or two, is the overhead low enough with rules that this is okay?

Example I'm using about oh:

RULE_INT( Combat, TankACModifier, 14 )
RULE_INT( Combat, MediumTankACModifier, 11 )
RULE_INT( Combat, LightTankACModifier, 9 )
RULE_INT( Combat, PureCasterACModifier, 6 )
RULE_INT( Combat, IksarACModifier, 1 )
RULE_INT( Combat, NPCACModifier, 10 )
RULE_INT( Combat, AttackCalcModifier, 9 )
RULE_INT( Combat, ACBase, 65 )
RULE_INT( Combat, RollDiffACMod, 6 )
RULE_INT( Combat, RollDiffLevelMod, 5 )

all those per AvoidDamage call, some more than once, is there going to be some kind of speed hit where I should hard code those values in the end or is using the rules fine?

The crit function seems the most reasonable solution to me since it can later be expanded to let NPCs crit under certain circumstances, via crit buffs or pets if their owners have petcrit AAs etc; as well as being easily plugged into the special attack code.

KLS
10-25-2006, 07:13 PM
Some more stuff. Mostly aggro changes and one change to corpses.

Adds Rules:
RULE_INT (Spells, SpellAggroModifier, 100)
% Aggro spells generate 100% default
RULE_INT (Spells, BardSpellAggroMod, 3)
Bard Aggro From Spells / BardSpellAggroMod
RULE_INT (Spells, PetSpellAggroMod, 10)
Pet Aggro From Spells / PetSpellAggroMod

Today was the first time I dual-boxed since I did corpses which is why I didn't really see this earlier. Was testing my AC mitigation and heal aggro and thought this was fixed.

There's this is Client:: Death()

entity_list.AddCorpse(new_corpse, GetID());

//send the become corpse packet to everybody else in the zone.
entity_list.QueueClients(this, &app2, true);

Which should be

entity_list.AddCorpse(new_corpse, GetID());
SetID(0);
//send the become corpse packet to everybody else in the zone.
entity_list.QueueClients(this, &app2, true);

I'm not 100% on the logic behind it but I can't argue with results, without that meager line if someone is around when you die and sees you fall your corpse will immediatly disappear on their client but will be there server side and they'll have to zone in and out to see it. Should be included in the diff.

http://hmproject.org/files/Aggro.patch

I surely hope some of my previous changes make it in, I notice some haven't yet. Really think haste affecting combat timers, change to how haste is calculated, recourse and glow messages are nice changes.

Also have been working on Mitigation recently as we don't really have any real mitigation to speak of. Here's the formula I'm working with atm:

RULE_CATEGORY ( Combat )
RULE_INT( Combat, AttackCalcModifier, 15 )
RULE_INT( Combat, ACBase, 100 )
RULE_INT( Combat, RollDiffACMod, 12 )
RULE_INT( Combat, RollDiffLevelMod, 4 )
RULE_INT( Combat, MaxClientDamageMultiplier, 100 )
RULE_INT( Combat, ACRollSecondaryChance, 50 )
RULE_INT( Combat, PrimaryReductionBase, 25 )
RULE_INT( Combat, SecondaryReduction, 25 )
RULE_CATEGORY_END()

if(damage > 0){
int Mit_AC;
int Power_ATK;

Mit_AC = RuleI(Combat, ACBase) + (defender->GetAC());
Power_ATK = (attacker->GetSkill(OFFENSE) + attacker->GetSTR())*RuleI(Combat,AttackCalcModifier)/10;
Power_ATK += attacker->GetATK();

int Roll1, Roll2;
int RollDiff = 0;
Roll1 = MakeRandomInt(0, Power_ATK);
Roll2 = MakeRandomInt(0, Mit_AC);

if(Roll1 < Roll2){//this is success roll for mitigation
RollDiff = Roll2 - Roll1;
int32 PotentialReduction = RuleI(Combat, PrimaryReductionBase) + (100*((RollDiff/RuleI(Combat, RollDiffACMod))))/((20+RuleI(Combat, RollDiffLevelMod)*GetLevel()));
int32 MinReduction = PotentialReduction/20;
if(PotentialReduction > 100)
PotentialReduction = 100;

if(MinReduction > 100)
MinReduction = 100;

int32 DamageReduced = (damage * MakeRandomInt(MinReduction, PotentialReduction)) / 100;
if(DamageReduced < 1)
DamageReduced = 1;
mlog(COMBAT__DAMAGE, "PowerATK: %d, MitAC: %d, PowerATKRoll: %d, MitACRoll: %d, Potential Red: %d, Min Red: %d, Dmg Reduced: %d",
Power_ATK, Mit_AC, Roll1, Roll2, PotentialReduction, MinReduction, DamageReduced);
damage -= DamageReduced;
}
else if(MakeRandomInt(0, 100) <= RuleI(Combat, ACRollSecondaryChance)){
//we fail the initial roll but get lucky and reduce damage by up to a fixed percent
int32 DamageReduced = (damage * MakeRandomInt(0, RuleI(Combat, SecondaryReduction))) / 100;
if(DamageReduced < 1)
DamageReduced = 1;

damage -= DamageReduced;
}
else{
//we completely fail the roll /cry
}

if (damage < 1)
damage = 1;
}

It puts out some decent results, it's a pain to balance at the high level versus the low level though and I'm still working on it.

But for anyone who's interested a parse as of last night:
http://hmproject.org/files/parserogue.txt
Moderatly High AC Mob with a rogue with just under 300str and not much +atk.

OH before I forget:
Both the GetAC() and GetATK() are unsigned int values but can attempt to return signed values. Ex with an AC debuff on you can get -100 AC and it will try to return -100 AC but the return type is unsigned = bad stuff.

fathernitwit
10-26-2006, 01:51 AM
havent had time to give this the attention it needs yet, but.. rules are designed to be fast, it basically works out to this much work:
rules_array[index].value

also, your change for groups seems to have caused more harm than good, people are reporting that once a group is formed, you cannot invite anybody else (which means to me that nobody is the leader, client side). Im going to revert it, in case you want to work on it more.

KLS
10-26-2006, 09:27 AM
Odd, I'll have to look at it some more, I admit I don't really have more than myself to test groups with most times and I can't really support more than dual boxing with my server on my comp but I'll see what can be done, might have to recruit some people in IRC for this one =/

Nice about the rules though that means I can make any new mitigation system I put it be totally tweakable without needing to recompile, which seems nice considering I doubt many people have put much thought into str and ac currently in their databases when making the monsters.

John Adams
10-26-2006, 11:54 AM
Something else up with grouping that I do not remember happening before. If I am solo, I can buff myself up no problem. I then join a another character to my group, and buff them, also no problem. But if I target myself again and try to re-buff, it only casts on the other player in the group. Even if they leave the group. It never seems to forget them. Even if they logout. I cannot target myself and get a buff to land.

This is the first I've seen of this. Also using 871 binaries.

KLS
10-26-2006, 12:49 PM
I've seen that too John sorta, if I leave the group I'm fine and it only happens for group buffs for me, meaning to look at it. Not sure I'm getting the same problem as you however, think what's happening with me is a group spell casts on the leader first and goes down from there, and I think something's happening where if the spell wouldn't take hold cause of stacking somewhere it just cancels for everyone. If you can give me more info on your problem, detailed step by step instructions for reproducing etc, I can look into it.

This group thing is one of the most frustrating things I've ever done. The client knows it's part of a group and knows it's the leader but some piece of info is missing or inconsistant with the rest and the packet for invite isn't sent by client. On the other end, the client knows it's the leader and is in the group but again the same thing except this time it wont send group chat, sony set this up in a very confusing manner. I guess I just assumed at the beginning that the client wasn't completely retarded.. last time I'll have faith in that =P

I would go ahead and reverse it, not being able to talk is better than not being able to invite till you zone and I've tried about 10-15 different group packet combos with no luck so far.

John Adams
10-26-2006, 03:33 PM
Let's see if I can step this through...

Leader char casts Speed of the Brood on himself, it lands ok.
Leader invites player to group, and player joins.
Leader then casts same Speed of the Brood on the new group member.
Leader then cancels the effect so he has no buffs.
Targets himself.
Casts Speed.
Nothing lands. Message in the window says the other player feels faster (or whatever).
If that player leaves the group, same thing. Seems stuck on targetting that player.
If the player logs off, leaving only the Leader in the zone, the buff casts, you see the glowies for it, but no message, and no effect icon.

Let me run through a few other tests with this. I might have been casting a group buff on myself, though I should still get it, that might be the key. I'll report back in a few.

KLS
10-26-2006, 03:36 PM
Hum another thing I noticed something seems up with Character rules, they aren't loading from the DB correctly for me and trying to list them with #rules list Character returns: Failed to list rules!

I'll do a #rules setdb Character:MaxLevel 70 but when I zone it's back to the default in code of 65, same with Character:LeaveCorpses and Character:LeaveNakedCorpses.

All the other rules seem to work fine so long as they aren't in the Category character.

John Adams
10-26-2006, 03:40 PM
Hum another thing I noticed something seems up with Character rules, they aren't loading from the DB correctly for me and trying to list them with #rules list Character returns: Failed to list rules!
Almost sure we saw this error before the recent changes. It's in another thread about the rules system, I believe.


Back to groups... I found one thing which you might have mentioned. If the leader sends an invite to all party members before any one of them accepts, they can all join, and chat/buffs/etc all seem to work ok. If you invite 1 person, then they accept, then invite another, they get no invite.

As for my previous report, I was mixing how Group Buffs and Single Target buffs work, apparently. I think everything is just fine there, so I was way wrong! There still may be something in the message reporting, but I mostly played melee's on Live, so I cannot recall what message is given if you cast a group buff while targetting someone who is not in your group. No message at all, or should I still get the buff?

Sorry KLS. No issue with buffs and the new code, pretty sure.

KLS
10-26-2006, 04:32 PM
Something with the #rules list:
RuleManager::CategoryType RuleManager::FindCategory(const char *catname) {
int r;
for(r = 1; r < _CatCount; r++) {
if(strcasecmp(catname, s_categoryNames[r]) == 0)
return((CategoryType) r);
}
return(InvalidCategory);
}Any reason we start the index at 1 instead of 0? I changed that and rules seem to list fine.

Rule loading appears just a matter of me being a newbie, wasn't using #rules store default for default rules like I should have been.

On live if you cast a group buff on someone not in your group without /tgb on it will target your group else it will target theirs. /tgb seems to be a little iffy on the emu though, was gonna say I couldn't reproduce your issue at all. Unsure of the message you should be getting.

paaco
10-27-2006, 03:22 AM
I tryed to use #rules to set my server to lvl 50, it told me that same message about failing. But it worked. I thought that was odd myself.

KLS
10-27-2006, 01:40 PM
Was noticing current end wasn't saving right with #save and zoning when I came across this:

in Client::FinishConnState2(DBAsyncWork* dbaw) (at about line 5980)
CalcBonuses();
CalcMaxHP();
CalcMaxMana();
if (m_pp.cur_hp <= 0)
m_pp.cur_hp = GetMaxHP();

SetHP(m_pp.cur_hp);
Mob::SetMana(m_pp.mana);
SetEndurance(m_pp.endurance);
We calculate maxhp and max mana in calcbonuses.
void Client::CalcBonuses()
{
_ZP(Client_CalcBonuses);
memset(&itembonuses, 0, sizeof(StatBonuses));
CalcItemBonuses(&itembonuses);
CalcEdibleBonuses(&itembonuses);

RecalcWeight();

CalcSpellBonuses(&spellbonuses);

CalcMaxHP();
CalcMaxMana();
CalcMaxEndurance();

CalcAC();
CalcATK();
CalcHaste();

CalcSTR();
CalcSTA();
CalcDEX();
CalcAGI();
CalcINT();
CalcWIS();
CalcCHA();

CalcMR();
CalcFR();
CalcDR();
CalcPR();
CalcCR();

rooted = FindType(SE_Root);
}
An issue I see is we calc the hp/mana/end before we calculate the stats which could potentially cause problems, could we not change this stuff to:
CalcBonuses();
if (m_pp.cur_hp <= 0)
m_pp.cur_hp = GetMaxHP();

SetHP(m_pp.cur_hp);
Mob::SetMana(m_pp.mana);
SetEndurance(m_pp.endurance);
void Client::CalcBonuses()
{
_ZP(Client_CalcBonuses);
memset(&itembonuses, 0, sizeof(StatBonuses));
CalcItemBonuses(&itembonuses);
CalcEdibleBonuses(&itembonuses);

RecalcWeight();

CalcSpellBonuses(&spellbonuses);

CalcAC();
CalcATK();
CalcHaste();

CalcSTR();
CalcSTA();
CalcDEX();
CalcAGI();
CalcINT();
CalcWIS();
CalcCHA();

CalcMR();
CalcFR();
CalcDR();
CalcPR();
CalcCR();

CalcMaxHP();
CalcMaxMana();
CalcMaxEndurance();

rooted = FindType(SE_Root);
}
That way we would only calculate hp and mana once at that point and max endurance would calculate correctly on connection.

KLS
10-28-2006, 06:08 PM
Here's what's up with the groups:

When we do it the way I do it, what I assume is probably the correct way the client switches from using GroupInvite opcode to GroupInvite2 opcode and GroupFollow opcode to GroupFollow2 opcode. Of course no one ever figured those opcodes out so they're all set to 0x0000.

Here are the titanium opcodes, I don't have the 0.6.2 opcodes yet because I don't have a 0.6.2 client atm.OP_GroupFollow2=0x42c9 #this is a 2nd version of follow, that's used with invite version 2 is used
OP_GroupInvite2=0x12d6 #this is a 2nd version of invite

Tested it with 3 people and it worked fine, leader could talk and could invite the 3rd person correctly. Such a simple problem and it only took me 2 days to figure out.

John Adams
10-28-2006, 06:18 PM
lol, well at least you CAN figure it out. Maybe if I stick with this a few years, I might learn something. :)

KLS
10-29-2006, 12:32 PM
For 6.2

OP_GroupInvite2=0x1f27

I wasn't able to find the OP_GroupFollow2 for 6.2, but the client appears to only send a OP_GroupFollow2 on accepting a group they were invited to with OP_GroupInvite2. So I made a little work around in Handle_OP_GroupInvite2 that recreates the incoming packet but always sets it to a GroupInvite opcode before sending it to the target.

void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app)
{
if (app->size != sizeof(GroupInvite_Struct)) {
LogFile->write(EQEMuLog::Error, "Invalid size for OP_GroupInvite: Expected: %i, Got: %i",
sizeof(GroupInvite_Struct), app->size);
return;
}

if(this->GetTarget() != 0 && this->GetTarget()->IsClient()) {
//Make a new packet using all the same information but make sure it's a fixed GroupInvite opcode so we
//Don't have to deal with GroupFollow2 crap.
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupInvite, sizeof(GroupInvite_Struct));
memcpy(outapp->pBuffer, app->pBuffer, outapp->size);
this->GetTarget()->CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
return;
}
/*if(this->GetTarget() != 0 && this->GetTarget()->IsNPC() && this->GetTarget()->CastToNPC()->IsInteractive()) {
if(!this->GetTarget()->CastToNPC()->IsGrouped()) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupUpd ate_Struct));
GroupUpdate_Struct* gu = (GroupUpdate_Struct*) outapp->pBuffer;
gu->action = 9;
strcpy(gu->membername,GetName());
strcpy(gu->yourname,GetTarget()->CastToNPC()->GetName());
FastQueuePacket(&outapp);
if (!isgrouped){
Group* ng = new Group(this);
entity_list.AddGroup(ng);
}
entity_list.GetGroupByClient(this->CastToClient())->AddMember(GetTarget());
this->GetTarget()->CastToNPC()->TakenAction(22,this->CastToMob());
}
else {
LogFile->write(EQEMuLog::Debug, "IPC: %s already grouped.", this->GetTarget()->GetName());
}
}*/
return;
}
Tested on 6.2 earlier and it worked fine, will need to go test on titanium tonight but don't see why it shouldn't work.

John Adams
10-29-2006, 11:41 PM
will need to go test on titanium tonight but don't see why it shouldn't work.
Ok, now I know you are an engineer IRL. ;)

If I had a dime...

KLS
11-06-2006, 10:22 PM
Just some more stuff, I posted this to the irc but it keeps getting lost, been asked to repost it a few times to irc but it always seems to get lost again, so I'm gonna post it here too.

-Redid the Critical Hit code to be portable
-Fixed Cleave effects in the process of redoing critical hits
-Added criticals to special attacks
-Applied haste to combat ability timers
-Cleaned up a lot of special attack code
-Rewrote most of the backstab function, and pretty much all of the MonkSpecialAttack one
-Redid some of the npc::doclassattacks, namely moved Harmtouch/Layonhands to a seperate timer from bash/kick
-Added a rule that lets server ops decide when NPCs can bash/kick
-Raised the base damage of kick/bash slightly, mostly so it doesn't do so little damage on the lower end, was getting kicks for 3 damage at level 15.
-Monk attacks can now be missed as well as avoided
-Attacks wont avoid so often, they were being avoided twice in the code.
-Removed the pvp reductions from special attacks, we already do this in the client::damage function, no need to do it twice.

http://hmproject.org/files/spcatk.patch

Damilis
11-06-2006, 10:26 PM
Ok, now I know you are an engineer IRL. ;)

If I had a dime...

Now now, there's nothing wrong with engineers :P

KLS earns two thumbs up IMHO. Give that man a Klondike Bar!

Dralanna
11-09-2006, 06:07 AM
Looking at your code for backstabs, and I'm a little confused.

the old code for max damage is

skillmodifier = (float)bs_skill/25.0; //formula's from www.thesafehouse.org
max_hit = (int)(((float)primaryweapondamage * 2.0) + 1.0 + ((level - 25)/3.0) + ((GetSTR()+GetSkill(BACKSTAB))/100));
max_hit *= (int)skillmodifier;


the new code is

skillmodifier = (bs_skill*100)/25; //formula's from www.thesafehouse.org
if(level > 25){
max_hit = ((primaryweapondamage*2) + 1 + ((level-25)/3) + ((GetSTR()+bs_skill)/100));
}
else{
max_hit = ((primaryweapondamage*2) + 1 + ((GetSTR()+bs_skill)/100));
}
max_hit *= skillmodifier;
max_hit /= 100;


So unless I'm missing something, for rogues over 25th level, the code is the same. In the new code you're multiplying the backstab skill by 100 in the skillmodifier variable, but then dividing by 100 in the end. Seems like an unneeded calculation.

KLS
11-09-2006, 09:13 AM
The backstab formulas are wrong, Rogean is working on more accurate ones at the moment. The current ones pretty much under estimate backstab damage across the board, I just rewrote the formula for under 25 because part of it becomes increasingly negative for every 3 levels under 25 you become.

Let me explain what's happening with the new code with the * 100 / 100 later thing.

skillmodifier = (bs_skill*100)/25;

this makes our skill modifier for say 90 skill 360, in the old code we used floats for accuracy but we don't like to mix float and int math where we can avoid it. The old mod for a 90 skill would have been 3.6 but if we tried to simply switch that over we would lose the accuracy of using floats.. but when working with larger numbers you don't lose that accuracy when dividing.

Essentially we don't lose a .6 in the calculation doing it this way and don't have to convert from ints to floats to ints again.

I may not of explained it as well as I could have but meh.

Dralanna
11-09-2006, 09:42 AM
Nope, that explains it perfectly... makes sense.

Thanks,

bufferofnewbies
11-10-2006, 08:49 AM
Essentially we don't lose a .6 in the calculation doing it this way and don't have to convert from ints to floats to ints again.

You know, the more I watch you guys in action. The more I am impressed with the things you do. Very insightful work around on the (non-)conversion. I wish I would have had more people like you on my crew back in the Navy.

Damilis
11-10-2006, 09:23 AM
I wish I would have had more people like you on my crew back in the Navy.

Woohoo I'm not the only squid here!

mattmeck
11-10-2006, 09:47 AM
Woohoo I'm not the only squid here!


EQEmu coveres all branches, lots of everyone but the marines...

bufferofnewbies
11-10-2006, 11:12 AM
<<-- Bubblehead. Those who know will understand. :)